Java 調用 Windows DLL 主流方案:JNI (標準、繁瑣)、JNA (推薦、純 Java)、JNative (輕量、開源但停維)。本文記錄基於 **JNative** 在 MyEclipse 10 環境下,從項目創建、JAR 引入、DLL 放置、參數類型映射、指針處理到中文亂碼排查的完整實戰過程。
一、方案選型對比
| 方案 | 特點 | 適用場景 |
| JNI | 官方標準,需寫 C/C++ 胶水代碼編譯成動態庫,調試難,跨平台需多編譯 | 高性能、系統級、需精細控制內存 |
| JNA | 純 Java,基於 libffi 運行時動態綁定,**無需編譯本地代碼**,API 豐富,社區活躍 | 現代首選,大多數 DLL/so 調用場景 |
| JNative | 輕量封裝 JNI,直接映射導出函數,**項目已停止維護**,文檔極少,64 位支持弱 | 老項目維護、極簡調用、學習原理 |
註:新項目**強烈建議用 JNA**,本文僅作 JNative 歷史經驗記錄。
二、環境準備與項目結構
- MyEclipse 10 / Eclipse + JDK 32 位 (JNative 多為 32 位.native 庫)。
- 新建 Java Project → 新建 Package
com.ehfscliax → 新建 Class Helper (含 main)。
- 項目根目錄創建
lib/ 放入 JNative.jar,右鍵 Build Path → Add to Classpath。
- **關鍵**:待調用 DLL (
EhfscliaxDll.dll) 放在 **項目根目錄** (與 src 同級),而非 lib/ 或 bin/。運行時工作目錄為項目根,System.loadLibrary 會在此查找。
三、DLL 接口定義 (C++ 端)
// EhfscliaxDll.h
extern "C" __declspec(dllexport) const char* getUrl(const char* ip, int port, int id);
extern "C" __declspec(dllexport) const wchar_t* getPlayUrl(const wchar_t* ip, int port, int id);
getUrl:返回 const char* (ANSI/UTF-8 窄字符)。
getPlayUrl:返回 const wchar_t* (Unicode 寬字符)。
四、JNative 核心 API 與類型映射
| Java 類型 | JNative Type 常量 | 對應 C 類型 |
| int | Type.INT | int / long |
| long | Type.LONG | long long / int64_t |
| double | Type.DOUBLE | double |
| String (char*) | Type.STRING | const char* |
| Pointer | Type.POINTER | void* / wchar_t* / 結構體指針 |
關鍵方法:
new JNative(dllName, funcName):綁定導出函數。
setRetVal(Type):聲明返回類型。
setParameter(index, value):按順序設置參數 (索引從 0 開始)。
invoke():執行調用。
getRetVal():獲取返回值 (String/Number)。
getRetValAsInt():獲取返回指針地址 (int/long)。
JNative.getUnicodeMemoryAsString(ptrAddress):從內存地址讀取 Unicode 字符串。
五、完整調用代碼
package com.ehfscliax;
import org.xvolks.jnative.JNative;
import org.xvolks.jnative.Type;
import org.xvolks.jnative.exceptions.NativeException;
import org.xvolks.jnative.pointers.Pointer;
public class Helper {
public static void main(String[] args) throws NativeException {
JNative.setLoggingEnabled(true); // 開啟調試日志
// 1. 調用返回 const char* 的函數
try {
JNative getUrl = new JNative("EhfscliaxDll.dll", "getUrl");
getUrl.setRetVal(Type.STRING);
getUrl.setParameter(0, "127.0.0.1"); // const char*
getUrl.setParameter(1, 10087); // int
getUrl.setParameter(2, 123); // int
getUrl.invoke();
System.out.println("getUrl 返回: " + getUrl.getRetVal());
} catch (IllegalAccessException e) { e.printStackTrace(); }
// 2. 調用返回 const wchar_t* 的函數 (難點:指針+寬字符)
try {
JNative getPlayUrl = new JNative("EhfscliaxDll.dll", "getPlayUrl");
getPlayUrl.setRetVal(Type.INT); // 先按指針地址 (int) 取回
String mgrIp = "127.0.0.1";
Pointer ptr = Pointer.createPointerFromString(mgrIp); // Java String → wchar_t* 內存塊
getPlayUrl.setParameter(0, ptr);
getPlayUrl.setParameter(1, 10087);
getPlayUrl.setParameter(2, 123);
getPlayUrl.invoke();
// 從返回的指針地址讀取 Unicode 字符串
int retPtr = getPlayUrl.getRetValAsInt();
String url = JNative.getUnicodeMemoryAsString(retPtr);
System.out.println("getPlayUrl 返回: " + url);
} catch (IllegalAccessException e) { e.printStackTrace(); }
}
}
六、運行結果與中文亂碼分析
2014-5-25 22:48:46 [DEBUG] [loadLibrary]: Successfully loaded library 'EhfscliaxDll.dll', functionName = getUrl
getUrl 返回: https://中文127.0.0.1:10087 <-- 正常 (ANSI/UTF-8 兼容)
2014-5-25 22:48:46 [DEBUG] [loadLibrary]: Successfully loaded library 'EhfscliaxDll.dll', functionName = getPlayUrl
getPlayUrl 返回: https://-17001:10087 <-- 亂碼!預期應包含中文
亂碼根因
- DLL 返回
const wchar_t* (UTF-16LE)。
- JNative
getUnicodeMemoryAsString 內部實現可能假設平台默認編碼或固定 UTF-16,但內存佈局/終止符處理有差異。
- JNative 年代久遠,對 Unicode 內存讀取支持不完善。
嘗試修復方向 (未在文中完全解決)
- 確認 DLL 導出確實是 UTF-16LE (用 Dependency Walker / VS 調試)。
- 手動讀取指針內存:
Pointer.getMemory() 獲取字節數組,自行 new String(bytes, "UTF-16LE")。
- **終極建議**:遷移到 **JNA**,
WideString / WString 類型原生支持寬字符,無此類坑。
七、總結與遺產代碼維護建議
- JNative 實現了「零 C 代碼調用 DLL」,但**停維、文檔缺、Unicode 支持弱、64 位難**。
- 核心難點在於 **指針類型映射** (Parameter/Pointer) 與 **字符串編碼協議** (ANSI vs Unicode) 的雙重匹配。
- 若維護老項目必須用 JNative:編寫測試用例覆蓋所有 DLL 接口,鎖定當前 JDK/架構/OS 環境,避免升級導致不可用。
- **新項目/重構請直接上 JNA**,
Native.loadLibrary + 接口映射,幾行代碼搞定,支持回調、結構體、Unicode、跨平台。
- 上一篇文章: 編程學習避坑指南:從入門選語言到職業發展的粗淺思考
- 下一篇文章: Java 將 IPv4 地址轉換為長整型 (long) 的高效實現與邊界處理
Copyright ©
程式碼之家 All Rights Reserved