如何使用Gradle构建可调用其他动态库函数的JNI动态库?
我在代码仓库(可查看fork来源)中更新了IDEA官网JNI指南给出的示例,现在它可在现代JDK和JUnit测试环境下运行,甚至在我的aarch64架构Mac上也能正常工作(希望其他设备也可兼容)。但我不太理解hello Gradle子项目目录下的build.gradle配置逻辑,我能识别出其中的编译器参数,但头文件引入选项是在单独的方法中设置的,这让我十分困惑。
我的目标是仅编写一个C语言封装文件,通过dlopen(或Windows对应的替代接口)调用动态库中的指定函数。
示例结构如下:
<ProjectRoot>/hello/src/main/c/ wrapper-with-JNI-call-convensions.c libsomelib.dylib libsomelib.so somelib.dll
调用链路如下:
Java -[JNI]-> wrapper.dll -> somelib.dll ^ ^ ^ | | | 通过Gradle编译wrapper.c生成该文件
目前我已经在不修改示例原有build.gradle的前提下,将JNI封装文件和我的库的C源码一起编译,成功实现了JNI功能。之后我用macOS自带的clang编译器编译生成了dylib文件,也编写了我认为逻辑正确的dlopen和dlsym代码,但构建项目后dlopen调用返回了NULL。
我尝试给编译依赖动态库的二进制的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倍左右,我后续会把我的实现方案作为自问自答发布。
若仍需使用Gradle实现
dlopen返回NULL的核心是动态库搜索路径问题,对应解决方法如下:
- 路径处理:
- 调用
dlopen时直接传入libsomelib.dylib的绝对路径,可在Java层先获取库文件的绝对路径,通过JNI参数传递给C层后再传入dlopen即可 - 编译wrapper库时添加rpath参数,让加载器优先从wrapper库所在目录搜索依赖:macOS加
-Wl,-rpath,@loader_path/,Linux加-Wl,-rpath,$ORIGIN/,Windows无需额外配置。对应Gradle只需在编译器配置块中添加linker.args [对应参数]。
- 调用
- 编译配置:
把预编译的libsomelib系列动态库放到指定目录,在build.gradle中配置规则,确保打包时wrapper库和依赖的动态库输出到同一路径即可。
若采用VSCode tasks.json实现
核心配置逻辑参考:
- 分平台配置编译任务,用变量区分不同平台的输出后缀、编译器参数
- 编译JNI wrapper时主动引入JDK目录下的
jni.h和对应平台的jni_md.h头文件路径 - 编译完成后自动将生成的wrapper库和预编译的依赖动态库拷贝到Java项目的库搜索路径即可
内容的提问来源于stack exchange,提问作者Vadim

