JNA (Java Native Access) 極大簡化了 JNI 開發,無需編寫 C/C++ 胶水代碼即可調用原生庫。GitHub:java-native-access/jna
public class HelloWorld {
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary) Native.loadLibrary(
Platform.isWindows() ? "msvcrt" : "c", CLibrary.class);
void printf(String format, Object... args);
}
public static void main(String[] args) {
CLibrary.INSTANCE.printf("Hello, World\n");
for (int i = 0; i < args.length; i++) {
CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
}
}
}
核心機制:介面繼承 Library,透過 Native.loadLibrary() 載入系統庫,宣告需調用的原生函數簽名,JNA 自動完成映射。
場景:bar.dll 依賴 foo.dll,且兩者皆需在 Java 中映射。若直接分別 loadLibrary,可能因依賴解析失敗導致 UnsatisfiedLinkError。
public class Foo {
public static final boolean LOADED;
static {
Native.register("foo"); // 先載入 foo
LOADED = true;
}
public static native void call_foo();
}
public class Bar {
static {
if (Foo.LOADED) { // 確保 foo 已載入
System.loadLibrary("baz"); // 可選:載入 bar 依賴的第三方庫
Native.register("bar"); // 再載入 bar
}
}
public static native void call_bar();
}
關鍵點:System.loadLibrary 僅在庫不在系統路徑(PATH/LD_LIBRARY_PATH)或不在同目錄時才需顯式調用。
public interface Foo extends Library {
Foo INSTANCE = (Foo) Native.loadLibrary("foo", Foo.class);
}
public interface Bar extends Library {
Foo FOO = Foo.INSTANCE; // 引用 Foo.INSTANCE 強制先初始化 Foo 類
Bar INSTANCE = (Bar) Native.loadLibrary("bar", Bar.class);
}
利用類初始化順序:存取 Foo.INSTANCE 會觸發 Foo 類載入,從而先載入 foo.dll,再載入 bar.dll。
public class DDLTest {
public static void main(String[] args) {
System.out.println(System.getProperty("java.library.path"));
// 調用鏈:ZAZAPIt -> ID_FprCap -> ID_Fpr
FingerLibrary.Fingerdll.ZAZCloseDeviceEx(-1);
ID_FprCap.fprCap.LIVESCAN_Init();
ID_FprCap.fprCap.LIVESCAN_Close();
ID_FprCap.fprCap.LIVESCAN_BeginCapture(1);
ID_FprCap.fprCap.LIVESCAN_GetFPRawData(1, "aaaaaa");
ID_Fpr.INSTANCE.FP_FeatureMatch("aaaaaaaaaaa", "aaaaaaaaaaa", 0.5f);
}
// 底層驅動庫
public interface FingerLibrary extends Library {
FingerLibrary Fingerdll = (FingerLibrary) Native.loadLibrary("ZAZAPIt", FingerLibrary.class);
int ZAZOpenDeviceEx(int[] hHandle, int nDeviceType, int iCom, int iBaud, int nPackageSize, int iDevNum);
int ZAZCloseDeviceEx(int handle);
// ...其他方法...
}
// 特徵提取庫(依賴 FingerLibrary)
public interface ID_FprCap extends StdCallLibrary {
ID_Fpr fpr = ID_Fpr.INSTANCE; // 強制先載入 ID_Fpr
ID_FprCap fprCap = (ID_FprCap) Native.loadLibrary("ID_FprCap", ID_FprCap.class);
int LIVESCAN_Init();
int LIVESCAN_Close();
int LIVESCAN_BeginCapture(int nChannel);
int LIVESCAN_GetFPRawData(int nChannel, String pRawData);
// ...其他方法...
}
// 比對庫(被 ID_FprCap 依賴)
public interface ID_Fpr extends Library {
ID_Fpr INSTANCE = (ID_Fpr) Native.loadLibrary("ID_Fpr", ID_Fpr.class);
int FP_FeatureMatch(String pFeatureData1, String pFeatureData2, float pfSimilarity);
}
}
ID_Fpr fpr = ID_Fpr.INSTANCE 觸發先載入StdCallLibrary,C 標準庫用 LibraryString 對應 char*,float 對應 float*,陣列用 int[]/byte[]此模式完美解決「多 DLL 依賴鏈」的加載難題,生產環境驗證穩定可靠。