JNI (Java Native Interface) 讓 Java 調用 C/C++ 代碼成為可能,但在跨平台動態庫命名、載入路徑、函數簽名生成等細節上藏著諸多坑。本文系統梳理 Windows (.dll) 與 Linux (.so) 的完整流程,附帶實戰避坑清單。
native 方法 → javac 編譯 → javah 生成 .h 檔System.loadLibrary 載入 → 調用 native 方法| 項目 | Windows | Linux / Unix |
|---|---|---|
| 動態庫副檔名 | .dll | .so |
| 庫檔名慣例 | libname.dll | libname.so |
| loadLibrary 參數 | "libname" (全名,可省略副檔名) | "name" (去掉 lib 前綴與 .so 後綴) |
| 搜尋路徑環境變數 | PATH | LD_LIBRARY_PATH |
| 替代放置位置 | PATH 包含的目錄 | $JAVA_HOME/jre/lib/<arch>/ 或 /usr/lib |
Java 類若在套件 cn.common.testlib 中,javah 生成的 C 函數前綴為 Java_cn_common_testlib_;無套件則無前綴。建議:C 代碼中直接包含生成的 .h 檔,勿手寫簽名。
// Java 代碼
static {
// 必須包含 lib 前綴,副檔名可省略
System.loadLibrary("libmylib");
// 或使用絕對路徑載入(不受命名慣例限制)
// System.load("C:/path/to/libmylib.dll");
}
確保 DLL 所在目錄已加入系統 PATH,或啟動參數 -Djava.library.path=... 指定。
// Java 代碼
static {
// 必須去掉 lib 前綴與 .so 後綴
System.loadLibrary("mylib");
}
配置方式二選一:
/etc/profile:export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATHjava -Djava.library.path=/path/to/lib ...$JAVA_HOME/jre/lib/amd64/(不推薦,污染 JRE 目錄)Windows (MSVC):
cl /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" /LD MyImpl.c /Femylib.dll
Linux (GCC):
gcc -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" MyImpl.c -o libmylib.so
若不想編寫 C/C++ 胶水代碼,推薦:
// JNA 範例
public interface MyLib extends Library {
MyLib INSTANCE = Native.load("mylib", MyLib.class);
int myFunction(int a, double b);
}
// 直接調用
int result = MyLib.INSTANCE.myFunction(1, 2.5);
JNI 雖強大但易出錯,核心在於:命名慣例跨平台一致性、載入路徑正確配置、函數簽名與 .h 檔嚴格對應。新專案優先評估 JNA,僅在極致效能或需複雜回調時使用 JNI。