有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何构建从另一个DLL调用函数的JNI DLL?杰尼,格雷德尔

我已经在这个repo中实现了IDEA网站的JNI指南中给出的示例(你可以在那里看到fork),现在它甚至可以在我的AARC64 Mac上运行现代JDK和JUnit测试(希望它可以在其他任何地方运行)。但我真的不明白Gradle子项目文件夹中的build.gradle发生了什么。我可以识别那里的编译器参数,但include选项是在不同的方法中设置的,这让我感到困惑

我的目标是让包装器C-file执行dlopen(或其Windows的替代方案),并从动态库运行一些函数

例如

<ProjectRoot>/hello/src/main/c/ wrapper-with-JNI-call-convensions.c 
                                libsomelib.dylib
                                libsomelib.so
                                somelib.dll

Calls looks like this: Java -[JNI]-> wrapper.dll -> somelib.dll
                                     ^    ^    ^
                                     |    |    |
                         building wrapper.c using Gradle to this

因此,如果我只使用两个C源文件(使用JNI和我库的C源代码的包装器)运行build,我已经成功地使JNI工作(同时不改变样本中的build.gradle)。然后,我使用macOS内置的clang编译器构建了dylib,并编写了正确的dlopendlsym代码。但当我构建项目时,dlopen调用返回空值

我曾尝试向build.gradle添加一些参数,该参数用于编译使用动态库的二进制文件,但没有帮助

我的测试运行在这里退出:

// my wrapper
void* dlHandle = dlopen("libsomelib.dylib", RTLD_LAZY);
    if (dlHandle == NULL) {
        printf("DLL NOT OPENED !!!\n");
        exit(0);
    }

我知道我必须在包装器中编写适当的依赖于平台的调用,这对我来说没什么大不了的

那么你能告诉我如何使用Gradle让它正常工作吗?记住,在我的例子中,目标是只有JNI就绪*.c包装器和动态链接库*.so & *.dylib & *.dll

更新:我决定不使用Gradle,我使用VSCode tasks.json,它的直观性要高出20倍。我会写下我的方法作为自我回答


共 (2) 个答案

  1. # 1 楼答案

    要使其运行,您可以尝试以下操作:

    > cc -g -shared -fpic wrapper.c -L. -lsome -o libwrapper.dylib
    

    我假设当前目录中有libsome.dylib。然后可以在JNI内加载libwrapper.dylib,它引用另一个库:libsome.dylib

    更新

    假设你有两个文件:

    #include <stdio.h>
    
    void anotherFunction () {
      // we are printing message from another C file
      printf ("Hello from another function!\n");
    }
    

    和相应的头文件

    #ifndef another_h__
    #define another_h__
    
    void anotherFunction (void)
    #endif                          // another_h__
    

    你可以用它建立一个库,方法如下:

    cc -shared -fpic recipeNo023_AnotherFunction.c -o libanother.dylib
    

    这将相当于您的:somelib

    然后,我们可以有这样的东西:

    #include "recipeNo023_redux_HelloWorld.h"
    
    void anotherFunction ();
    
    JNIEXPORT void JNICALL Java_recipeNo023_redux_HelloWorld_displayMessage
      (JNIEnv * env, jclass obj) {
    
      printf ("Hello world!\n");
      /* We are calling function from another source */
    
      anotherFunction ();
    }
    

    我们声明函数并使用它。名称将在链接阶段稍后解析

    现在,您可以编译wrapper库了。在这个示例中,它是HelloWorld

    cc -shared -fpic recipeNo023_redux_HelloWorld.c -L. -lanother
    

    现在,在JNI内,您所做的就是:

    System.loadLibrary("HelloWorld");
    

    由于HelloWorld与共享库another链接,它将可以访问其符号。所以,您不需要使用dlopen

    试试这个示例(甚至还有基于Docker的文件,所以您不必费劲地设置环境)

  2. # 2 楼答案

    作为@Oo。oO提到的this菜谱正是我需要的。这是一种动态加载方法,适用于Mac和Linux。此外,正如他所提到的,静态链接方法在他的回答中得到了演示

    我还想在其中添加一些编译器选项:

    -O3                     \   # for optimisation of speed
    -x c                    \   # to explicitly set language to C, in my case
    -fvisibility=default    \   # to export only that I need (read below)
    -arch x86_64            \   # option ONLY for macOS built-in clang in case you want build Intel binary on Silicon Mac
    

    在lib的源代码中,还应该定义

    #define EXPORT __attribute__((visibility("default")))
    

    然后在每个函数声明之前的头文件中,或者在每个定义之前的源文件中,为您希望从外部或库中使用的函数编写它

    例如

    // somelib.h
    EXPORT void somefunc(int a, int b);
    
    // somelib.c
    #include "somelib.h" // somelib.c and somelib.h in same directory
    void somefunc(int a, int b) { ... }
    

    或者

    // in case you don't want to use header (I recommend to use)
    // somelib.c
    EXPORT void somefunc(int a, int b) { ... }
    

    这个答案将在以后的Windows案例中给出

    摘要

    macOS+clang&;Linux+gcc