[英]How to build JNI DLL that calls function from another DLL? - JNI, Gradle
我已經在這個repo 中實現了 IDEA 網站關於 JNI 的指南中給出的示例(你可以在那里看到 fork),現在它甚至可以在我的 aarch64 Mac 上運行現代 JDK 和 JUnit 測試(希望它可以在其他任何地方運行)。 但我真的不明白在hello
Gradle 子項目文件夾中的build.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 和我的庫的 C 源代碼的包裝器)運行構建,我已經成功地使 JNI 工作(同時不更改示例中的build.gradle
)。 然后我使用 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-ready *.c
包裝器和動態鏈接庫*.so & *.dylib & *.dll
。
更新:我決定不使用 Gradle,我使用 VSCode tasks.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 提到的, 這個食譜正是我所需要的。 它是動態加載方法,適用於 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 案例中推進此答案。
clang
& Linux+ gcc
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.