如何构建从另一个 DLL 调用函数的 JNI DLL? - JNI,Gradle

How to build JNI DLL that calls function from another DLL? - JNI, Gradle

我已经在这个 repo 中实现了 IDEA 网站关于 JNI 的指南中给出的示例(你可以在那里看到分支)现在它 运行s 与现代 JDK 和 JUnit 测试即使在我的 aarch64 Mac 上(希望它在其他任何地方 运行)。但是我真的不明白 build.gradlehello Gradle 子项目文件夹中发生了什么。我可以在那里识别编译器参数,但是包含选项是在单独的方法中设置的,这让我很困惑。

我的目标是让包装器 C 文件执行 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 的包装器和我的图书馆)。然后我使用 macOS 内置的 clang 编译器构建了 dylib,并编写了正确的(我猜)dlopendlsym 代码。但是当我构建项目时,我从 dlopen 调用中返回 NULL。

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

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

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

我知道我必须在我的包装器中编写适当的平台相关调用,这对我来说不是什么大问题。

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

更新:我决定不用Gradle,我用VSCodetasks.json,好像直观了20倍.我会把我的方法写成自我回答。

要获取它 运行 您可以尝试以下操作:

> 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 的文件,因此您不必为设置环境而苦恼)。

正如@Oo.oO 提到的 this recipe is exactly what I need. It's dynamic load approach and it works on Mac and Linux. Also, as he mentioned there is static linking approach which is demonstrated here 和他的回答。

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

-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