【问题标题】:How to call a C function from Java 17 using JEP 412: Foreign Function & Memory API如何使用 JEP 412 从 Java 17 调用 C 函数:外部函数和内存 API
【发布时间】:2021-11-18 02:22:41
【问题描述】:

有没有人有一个简单的例子来说明如何从 Java 17 调用 C 函数,包括创建 C 库以及如何设置 MethodHandle?

https://openjdk.java.net/jeps/412 的 JEP 描述确实有一个示例,但我一直在努力理解它。

我认为可以使用 Project Panama (https://jdk.java.net/panama/) jextract 来实现,但由于 JDK 中包含此功能,因此我不想使用 Panama。

【问题讨论】:

  • 你的问题太笼统了。分别调查 Java + C 问题,并添加有关您所做工作的详细信息。我将发布一个可以帮助您入门的答案。

标签: java c project-panama


【解决方案1】:

我将我从 JEP412 / Panama 中读取的各种代码 sn-ps 整理到这个简单的示例类中,它演示了如何在 Java 17 中设置方法句柄以调用 C 运行时库并展示 Java->C 和 C 的使用->Java 调用。

它没有以任何方式优化。长期使用jextract 比手动编码从 Java->C 和 C->Java 进行向上/向下调用的各种方法句柄要好得多,并且它将大大减少为您自己或其他本机构建 Java API 映射的工作量图书馆。

该示例将在 JDK17 Windows 10 和 GNU/Linux(我使用 OpenJDK 17)上运行,但对于 GNU/Linux,您可能需要调整库负载的设置,例如 -Djava.library.path=/lib/x86_64-linux-gnu 以及 libc.so 的名称您系统上的库。

使用命令运行示例:

java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign Example17.java

请注意,Foreign Memory API 处于孵化阶段,因此 API 经常更改。只需稍加努力,这个示例就可以在 JDK16 或最新版本的 JDK Panama Foreign 上运行。

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();
    private static final SymbolLookup LOADER = SymbolLookup.loaderLookup();

    private static final String GETPIDNAME;
    static {
        final boolean isWindows = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows");
        // Windows: Some calls are in msvcrt, not the universal C library ucrtbase
        // Linux  : "C" - did not work for me so found path to libc-X.YZ.co using:
        //          ls -al $(ldd `which ls` | grep libc | awk '{print $3}')
        GETPIDNAME  = isWindows ? "_getpid":"getpid";
        String libc = isWindows ? "msvcrt" : /* "c" */ "c-2.31";
        System.loadLibrary(libc);
    }

    static MemoryAddress lookup(String name) {
        return Objects.requireNonNull(LOADER.lookup(name).orElse(null), () -> "Not found native method: "+name);
    }

    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();
        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(GETPIDNAME),
                    MethodType.methodType(int.class),
                    FunctionDescriptor.of(CLinker.C_INT));
    private static void getpid() throws Throwable {
        long jpid = ProcessHandle.current().pid();
        System.out.println("getpid() JAVA => "+jpid);

        // invoke it!
        int npid = (int)GETPID$MH.invokeExact();
        System.out.println("getpid() 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()");
        // 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("strlen('"+s+"' => "+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()");

        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(); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
            System.out.println("INPUT :"+Arrays.toString(toSort));
            System.out.println("OUTPUT:"+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("printf() 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("vprintf(\""+format.replaceAll("[\r\n]{1,2}", "\\\\n")+"\") "+Arrays.toString(args)+ " rc="+rc);
        }
    }
}

【讨论】:

  • 感谢@DuncG 的回答。我可以使用它从我自己的 DLL 中调用函数。
  • 谢谢。它看起来非常冗长,即使对于 Java 也是如此。尽管如此,如果它是稳定的,并且如果它允许在不修改相应源的情况下调用本机代码,那么它就是一个胜利。从描述(openjdk.java.net/jeps/412)来看,我不明白它是否已经支持 C++/Fortran,或者只是“随着时间的推移”。
  • @Eric Duminil jextract 工具非常容易使用,它可以为 C 头文件生成绑定,它大大缩短和简化了本地库调用工作所需的工作。我现在已经让我的 Java 应用程序直接在 100% Java 中使用 Windows API/OLE。但是 jextract 不在 JDK17 中,因此对于上面的示例,您会看到所有用于外部 API 调用的手工制作的 MethodHandle 声明。我不相信 C++/Fortran 还受支持。
  • 谢谢。 inside.java/2020/10/06/jextract 的示例看起来不错。您是否知道新的 100% Java API 是否会在出现问题时提供更多信息性错误消息,而不是“SIGSEGV 其他地方。不在 JVM 中,不是我的问题”?
  • 如果本机代码中出现问题,您应该希望看到带有本机框架 + java 框架信息的 hs_err_pid 日志文件。
猜你喜欢
  • 2018-06-12
  • 1970-01-01
  • 2019-12-13
  • 2014-10-01
  • 1970-01-01
  • 2018-07-28
  • 1970-01-01
  • 2012-08-24
  • 1970-01-01
相关资源
最近更新 更多