如何使用 JEP 412 从 Java 17 调用 C 函数:外部函数和内存 API
How to call a C function from Java 17 using JEP 412: Foreign Function & Memory API
有人有关于如何从 Java 17 调用 C 函数的简单示例,包括创建 C 库以及如何设置 MethodHandle 吗?
https://openjdk.java.net/jeps/412 中的 JEP 描述确实有一个示例,但我一直在努力理解它。
我认为可以使用 Project Panama (https://jdk.java.net/panama/) jextract 来实现,但由于此功能已包含在 JDK 中,所以我不想使用 Panama。
我将从 JEP412 / Panama 读到的各种代码片段整理成这个简单的示例 class,它演示了如何在 Java 17 中设置方法句柄以调用 C 运行时间库并显示 Java->C 和 C->Java 调用的使用。
它没有以任何方式进行优化。长期使用 jextract
比从 Java->C 和 C->Java 手动编码 up/downcalls 的各种方法句柄要好得多,而且它将大大减少努力为您自己或其他本机库构建 Java API 映射。
示例将 运行 在 JDK17 Windows 10 和 GNU/Linux(我使用 OpenJDK 17),尽管 GNU/Linux 您可能需要调整库加载设置,例如 -Djava.library.path=/lib/x86_64-linux-gnu
.
运行 命令示例:
java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign Example17.java
请注意外部内存 API 处于孵化阶段,因此 API 经常更改。稍加努力即可在 JDK16 或 JDK Panama Foreign 的最新版本上调整 运行。 C 运行time 调用是内置的,但如果您试验其他库,则需要在从 xyz.dll
或 libxyz.so
.[=17= 进行符号查找之前调用 System.loadLibrary("xyz");
]
import static jdk.incubator.foreign.CLinker.*;
import java.lang.invoke.*;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
import jdk.incubator.foreign.*;
import jdk.incubator.foreign.CLinker.VaList.Builder;
/**
* Example calls to C library methods from Java/JDK17.
* Run on Windows with:
java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign Example17.java
* Run on GNU/Linux with:
java -Djava.library.path=/lib/x86_64-linux-gnu --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign Example17.java
*/
public class Example17 {
private static final CLinker CLINKER = CLinker.getInstance();
// LOADER is used for symbols from xyz.dll or libxyz.so
// For every external library dependency add: System.loadLibrary("xyz");
private static final SymbolLookup LOADER = SymbolLookup.loaderLookup();
// SYSTEM is used for built-in C runtime library calls.
private static final SymbolLookup SYSTEM = CLinker.systemLookup();
static {
System.out.println("os.name="+System.getProperty("os.name"));
System.out.println("java.library.path="
+String.join(System.lineSeparator()+"\t", System.getProperty("java.library.path").split(File.pathSeparator)));
}
/** Find native symbol, call System.loadLibrary("xyz") for each dependency */
static MemoryAddress lookup(String name) {
return Objects.requireNonNull(LOADER.lookup(name).or(() -> SYSTEM.lookup(name)).get(), () -> "Not found native method: "+name);
}
/** Example calls to C runtime library */
public static void main(String... args) throws Throwable {
getpid();
strlen("Hello World");
printf();
qsort(0, 9, 33, 45, 3, 4, 6, 5, 1, 8, 2, 7);
vprintf("ONE=%d\n", 1234);
vprintf("A=%d B=%d\n", 2, 4);
vprintf("%d plus %d equals %d\n", 5, 7, 12);
}
// get a native method handle for 'getpid' function
private static final MethodHandle GETPID$MH = CLINKER.downcallHandle(
lookup(System.getProperty("os.name").startsWith("Windows") ? "_getpid":"getpid"),
MethodType.methodType(int.class),
FunctionDescriptor.of(CLinker.C_INT));
private static void getpid() throws Throwable {
int npid = (int)GETPID$MH.invokeExact();
System.out.println("getpid() JAVA => "+ProcessHandle.current().pid()+" NATIVE => "+npid);
}
private static final MethodHandle STRLEN$MH = CLINKER.downcallHandle(lookup("strlen"),
MethodType.methodType(long.class, MemoryAddress.class), FunctionDescriptor.of(C_LONG_LONG, C_POINTER));
public static void strlen(String s) throws Throwable {
System.out.println("strlen('"+s+"')");
// size_t strlen(const char *str);
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
MemorySegment hello = CLinker.toCString(s, allocator);
long len = (long) STRLEN$MH.invokeExact(hello.address()); // 5
System.out.println(" => "+len);
}
}
static class Qsort {
static int qsortCompare(MemoryAddress addr1, MemoryAddress addr2) {
int v1 = MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr1.toRawLongValue());
int v2 = MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr2.toRawLongValue());
return v1 - v2;
}
}
private static final MethodHandle QSORT$MH = CLINKER.downcallHandle(lookup("qsort"),
MethodType.methodType(void.class, MemoryAddress.class, long.class, long.class, MemoryAddress.class),
FunctionDescriptor.ofVoid(C_POINTER, C_LONG_LONG, C_LONG_LONG, C_POINTER)
);
/**
* THIS SHOWS DOWNCALL AND UPCALL - uses qsortCompare FROM C code!
* void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
* @param toSort
*/
public static int[] qsort(int ... toSort) throws Throwable {
System.out.println("qsort() "+Arrays.toString(toSort));
MethodHandle comparHandle = MethodHandles.lookup()
.findStatic(Qsort.class, "qsortCompare",
MethodType.methodType(int.class, MemoryAddress.class, MemoryAddress.class));
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
MemoryAddress comparFunc = CLINKER.upcallStub(
comparHandle,FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER),scope
);
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
// comparFunc = allocator.register(comparFunc);
MemorySegment array = allocator.allocateArray(CLinker.C_INT, toSort);
QSORT$MH.invokeExact(array.address(), (long)toSort.length, 4L, comparFunc.address());
int[] sorted = array.toIntArray();
System.out.println(" => "+Arrays.toString(sorted));
return sorted;
}
}
private static final MethodHandle PRINTF$MH = CLINKER.downcallHandle(lookup("printf"),
MethodType.methodType(int.class, MemoryAddress.class, int.class, int.class, int.class),
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_INT, C_INT)
);
/** This version hard-codes use of 3 int params as args to the string format */
public static void printf() throws Throwable {
System.out.println("printf()");
int a = 10;
int b = 7;
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
MemorySegment s = CLinker.toCString("%d times %d equals %d\n", allocator);
int rc = (int)PRINTF$MH.invokeExact(s.address(), a, b, a * b);
System.out.println(" => rc="+rc);
}
}
private static final MethodHandle vprintf = CLINKER.downcallHandle(lookup("vprintf"),
MethodType.methodType(int.class, MemoryAddress.class, CLinker.VaList.class),
FunctionDescriptor.of(C_INT, C_POINTER, C_VA_LIST));
/**
* vprintf takes a pointer to arg list rather than arg list as for printf
*/
public static void vprintf(String format, int ... args) throws Throwable {
System.out.println("vprintf(\""+format.replaceAll("[\r\n]{1,2}", "\\n")+"\") "+Arrays.toString(args));
// Weird Builder callback mechanism to fill the varargs values
Consumer<Builder> actions = builder -> {
for (int v : args) builder.vargFromInt(CLinker.C_INT, v);
};
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
CLinker.VaList vlist = CLinker.VaList.make(actions, scope);
MemorySegment s = CLinker.toCString(format, allocator);
int rc = (int)vprintf.invokeExact(s.address(), vlist /* ????? .address() */);
System.out.println(" => rc="+rc);
}
} }
有人有关于如何从 Java 17 调用 C 函数的简单示例,包括创建 C 库以及如何设置 MethodHandle 吗?
https://openjdk.java.net/jeps/412 中的 JEP 描述确实有一个示例,但我一直在努力理解它。
我认为可以使用 Project Panama (https://jdk.java.net/panama/) jextract 来实现,但由于此功能已包含在 JDK 中,所以我不想使用 Panama。
我将从 JEP412 / Panama 读到的各种代码片段整理成这个简单的示例 class,它演示了如何在 Java 17 中设置方法句柄以调用 C 运行时间库并显示 Java->C 和 C->Java 调用的使用。
它没有以任何方式进行优化。长期使用 jextract
比从 Java->C 和 C->Java 手动编码 up/downcalls 的各种方法句柄要好得多,而且它将大大减少努力为您自己或其他本机库构建 Java API 映射。
示例将 运行 在 JDK17 Windows 10 和 GNU/Linux(我使用 OpenJDK 17),尽管 GNU/Linux 您可能需要调整库加载设置,例如 -Djava.library.path=/lib/x86_64-linux-gnu
.
运行 命令示例:
java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign Example17.java
请注意外部内存 API 处于孵化阶段,因此 API 经常更改。稍加努力即可在 JDK16 或 JDK Panama Foreign 的最新版本上调整 运行。 C 运行time 调用是内置的,但如果您试验其他库,则需要在从 xyz.dll
或 libxyz.so
.[=17= 进行符号查找之前调用 System.loadLibrary("xyz");
]
import static jdk.incubator.foreign.CLinker.*;
import java.lang.invoke.*;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
import jdk.incubator.foreign.*;
import jdk.incubator.foreign.CLinker.VaList.Builder;
/**
* Example calls to C library methods from Java/JDK17.
* Run on Windows with:
java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign Example17.java
* Run on GNU/Linux with:
java -Djava.library.path=/lib/x86_64-linux-gnu --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign Example17.java
*/
public class Example17 {
private static final CLinker CLINKER = CLinker.getInstance();
// LOADER is used for symbols from xyz.dll or libxyz.so
// For every external library dependency add: System.loadLibrary("xyz");
private static final SymbolLookup LOADER = SymbolLookup.loaderLookup();
// SYSTEM is used for built-in C runtime library calls.
private static final SymbolLookup SYSTEM = CLinker.systemLookup();
static {
System.out.println("os.name="+System.getProperty("os.name"));
System.out.println("java.library.path="
+String.join(System.lineSeparator()+"\t", System.getProperty("java.library.path").split(File.pathSeparator)));
}
/** Find native symbol, call System.loadLibrary("xyz") for each dependency */
static MemoryAddress lookup(String name) {
return Objects.requireNonNull(LOADER.lookup(name).or(() -> SYSTEM.lookup(name)).get(), () -> "Not found native method: "+name);
}
/** Example calls to C runtime library */
public static void main(String... args) throws Throwable {
getpid();
strlen("Hello World");
printf();
qsort(0, 9, 33, 45, 3, 4, 6, 5, 1, 8, 2, 7);
vprintf("ONE=%d\n", 1234);
vprintf("A=%d B=%d\n", 2, 4);
vprintf("%d plus %d equals %d\n", 5, 7, 12);
}
// get a native method handle for 'getpid' function
private static final MethodHandle GETPID$MH = CLINKER.downcallHandle(
lookup(System.getProperty("os.name").startsWith("Windows") ? "_getpid":"getpid"),
MethodType.methodType(int.class),
FunctionDescriptor.of(CLinker.C_INT));
private static void getpid() throws Throwable {
int npid = (int)GETPID$MH.invokeExact();
System.out.println("getpid() JAVA => "+ProcessHandle.current().pid()+" NATIVE => "+npid);
}
private static final MethodHandle STRLEN$MH = CLINKER.downcallHandle(lookup("strlen"),
MethodType.methodType(long.class, MemoryAddress.class), FunctionDescriptor.of(C_LONG_LONG, C_POINTER));
public static void strlen(String s) throws Throwable {
System.out.println("strlen('"+s+"')");
// size_t strlen(const char *str);
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
MemorySegment hello = CLinker.toCString(s, allocator);
long len = (long) STRLEN$MH.invokeExact(hello.address()); // 5
System.out.println(" => "+len);
}
}
static class Qsort {
static int qsortCompare(MemoryAddress addr1, MemoryAddress addr2) {
int v1 = MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr1.toRawLongValue());
int v2 = MemoryAccess.getIntAtOffset(MemorySegment.globalNativeSegment(), addr2.toRawLongValue());
return v1 - v2;
}
}
private static final MethodHandle QSORT$MH = CLINKER.downcallHandle(lookup("qsort"),
MethodType.methodType(void.class, MemoryAddress.class, long.class, long.class, MemoryAddress.class),
FunctionDescriptor.ofVoid(C_POINTER, C_LONG_LONG, C_LONG_LONG, C_POINTER)
);
/**
* THIS SHOWS DOWNCALL AND UPCALL - uses qsortCompare FROM C code!
* void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
* @param toSort
*/
public static int[] qsort(int ... toSort) throws Throwable {
System.out.println("qsort() "+Arrays.toString(toSort));
MethodHandle comparHandle = MethodHandles.lookup()
.findStatic(Qsort.class, "qsortCompare",
MethodType.methodType(int.class, MemoryAddress.class, MemoryAddress.class));
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
MemoryAddress comparFunc = CLINKER.upcallStub(
comparHandle,FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER),scope
);
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
// comparFunc = allocator.register(comparFunc);
MemorySegment array = allocator.allocateArray(CLinker.C_INT, toSort);
QSORT$MH.invokeExact(array.address(), (long)toSort.length, 4L, comparFunc.address());
int[] sorted = array.toIntArray();
System.out.println(" => "+Arrays.toString(sorted));
return sorted;
}
}
private static final MethodHandle PRINTF$MH = CLINKER.downcallHandle(lookup("printf"),
MethodType.methodType(int.class, MemoryAddress.class, int.class, int.class, int.class),
FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_INT, C_INT)
);
/** This version hard-codes use of 3 int params as args to the string format */
public static void printf() throws Throwable {
System.out.println("printf()");
int a = 10;
int b = 7;
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
MemorySegment s = CLinker.toCString("%d times %d equals %d\n", allocator);
int rc = (int)PRINTF$MH.invokeExact(s.address(), a, b, a * b);
System.out.println(" => rc="+rc);
}
}
private static final MethodHandle vprintf = CLINKER.downcallHandle(lookup("vprintf"),
MethodType.methodType(int.class, MemoryAddress.class, CLinker.VaList.class),
FunctionDescriptor.of(C_INT, C_POINTER, C_VA_LIST));
/**
* vprintf takes a pointer to arg list rather than arg list as for printf
*/
public static void vprintf(String format, int ... args) throws Throwable {
System.out.println("vprintf(\""+format.replaceAll("[\r\n]{1,2}", "\\n")+"\") "+Arrays.toString(args));
// Weird Builder callback mechanism to fill the varargs values
Consumer<Builder> actions = builder -> {
for (int v : args) builder.vargFromInt(CLinker.C_INT, v);
};
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
CLinker.VaList vlist = CLinker.VaList.make(actions, scope);
MemorySegment s = CLinker.toCString(format, allocator);
int rc = (int)vprintf.invokeExact(s.address(), vlist /* ????? .address() */);
System.out.println(" => rc="+rc);
}
} }