前言
老規矩,先說下為什么會有這篇文章。近期對接了一個項目,應接口提供方要求,必須通過動態庫調用,一個是為了安全可控,調用方不用知道內部實現,加密、解密、具體的邏輯不需要考慮,只需要調用即可;另一個是封裝了統一的GUI界面。總之就是非用動態庫不可,然后我查了很多資料,請教了幾個大佬,最后在運氣的加持下,終於調通了,但整個過程特別坎坷,所以我覺有必要記錄下。需要說明的是我們這里采用的是JNA的方式
什么是動態庫
說實話,一般我們不會有調用動態庫的需求,因為這不是web開發的范疇,出發你涉及到嵌入式的開發,或者客戶端開發。動態庫也叫動態鏈接庫,英文簡寫DLL,簡單來講,就是Windows下開發的程序模塊,類似於java下的jar(不知道可不可以這樣 理解)。它是實現Windows應用程序共享資源、節省內存空間、提高使用效率的一個重要技術手段。windows下它是以dll結尾的文件,比如:msctf.dll
百度百科給的解釋是這樣的:
動態鏈接庫英文為DLL*,是Dynamic Link Library*的縮寫。DLL是一個包含可由多個程序,同時使用的代碼和數據的庫。在Windows中,這種文件被稱為應用程序拓展。例如,在 Windows 操作系統中,Comdlg32.dll 執行與對話框有關的常見函數。因此,每個程序都可以使用該 DLL 中包含的功能來實現“打開”對話框。這有助於避免代碼重用和促進內存的有效使用。 通過使用 DLL,程序可以實現模塊化,由相對獨立的組件組成。例如,一個計賬程序可以按模塊來銷售。可以在運行時將各個模塊加載到主程序中(如果安裝了相應模塊)。因為模塊是彼此獨立的,所以程序的加載速度更快,而且模塊只在相應的功能被請求時才加載。
咱也不是特別知道,咱也不敢問,你現在只有保證知道動態庫這樣的東西就行了。
開整
Talk is cheap. Show me the code。先上代碼,然后再解釋
public class DllTest {
static {
String filePath = "D:\\dll\\"; // 這里是你的動態庫所在文件夾的絕對路徑
// 這里引用動態庫和他的依賴
System.load(filePath + "mfc100.dll");
System.load(filePath + "mydll.dll");
}
public static void main(String[] args) {
String strUrl = "http://127.0.0.1/test";
String InData = "{\"data\":{\"operatorId\":\"test001\",\"operatorName\":\"超級管理員\",\"orgId\":\"123\"},\"orgId\":\"1232\"}";
byte[] OutData = new byte[1024];
String msg = CLibrary.INSTANCE.test(strUrl.getBytes(), InData.getBytes(), OutData);
System.out.println(msg);
try {
System.out.println(new String(OutData, "GBK"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
// 這里是最關鍵的地方
public interface CLibrary extends Library {
// FS_CheckCode是動態庫名稱,前面的d://test//是路徑
CLibrary INSTANCE = (CLibrary) Native.loadLibrary("mydll", CLibrary.class);
// 我們要調用的動態庫里面的方法。
String test(byte[] strUrl, byte[] InData, byte[] OutData);
}
}
動態庫里面的方法是這么定義的:
char* __stdcall test(char* strUrl,char* InData,char* OutData)
解釋
小朋友,你是否有很多的問號❓😂沒事,接下來我們就詳細說明下。首先要關注的是java定義動態庫接口方法,對應代碼:
public interface CLibrary extends Library {
// FS_CheckCode是動態庫名稱,前面的d://test//是路徑
CLibrary INSTANCE = (CLibrary) Native.loadLibrary("mydll", CLibrary.class);
// 我們要調用的動態庫里面的方法。
String test(byte[] strUrl, byte[] InData, byte[] OutData);
}
其中,loadLibrary方法是創建動態庫對象實例,第一入參是你要調用的動態庫的名字,test方法對應動態庫中的方法,這里需要注意的是jNA和動態庫直接數據類型的對應關系,具體的對應看后面的附表。
這里還有一個需要注意的問題是是,動態庫加載的問題:
// 這里引用動態庫和他的依賴
System.load(filePath + "mfc100.dll");
System.load(filePath + "mydll.dll");
如果你沒有把動態庫放到classpath下,而且沒有上面加載的代碼,會報如下錯誤:
Exception in thread "main" java.lang.UnsatisfiedLinkError: Unable to load library 'NationECCode':
找不到指定的模塊。
遇到的問題
JDK版本
如果完成了相應改造工作,你就可以直接運行了。如果你的JDK是64位,但你的動態庫是32位(X86),它肯定會報如下錯誤:
java.lang.UnsatisfiedLinkError: D:\workspace\learning\github\httputil-demo\dll\mydll.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
at java.lang.ClassLoader$NativeLibrary.load(Native Method)
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1941)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
at java.lang.Runtime.load0(Runtime.java:809)
at java.lang.System.load(System.java:1086)
或者這樣:
Exception in thread "main" java.lang.UnsatisfiedLinkError: %1 不是有效的 Win32 應用程序。
at com.sun.jna.Native.open(Native Method)
at com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:278)
at com.sun.jna.NativeLibrary.getInstance(NativeLibrary.java:455)
at com.sun.jna.Library$Handler.<init>(Library.java:179)
at com.sun.jna.Native.loadLibrary(Native.java:646)
at com.sun.jna.Native.loadLibrary(Native.java:630)
當然如果你的動態庫是64位,JDK是32位(X86),同樣也會報錯(應該會,沒試過)
解決方法很簡單:
- 更換JDK版本
- 聯系動態庫封裝的人,重新封裝對應的版本
找不到模塊
這個問題上面已經說過了,就是因為沒有加載動態庫文件,而且也沒有把它和它的依賴文件放到classpath下,就會報這個錯。
數據類型錯誤
這個問題本質上就是沒有搞清楚JNA和動態庫數據對應關系,我之前也沒搞清楚,反復試了好多次才成功。然后在今天寫這篇文章的時候,發現了char*
作為出參和入參對應的類型是不一樣的,才恍然大悟。希望小伙伴在自己搞的時候一定看清楚。
出參未分配空間
和java不一樣,動態庫方法是把入參傳給方法的,而且需要給出參分配空間,如果不分配內存空間,會報錯:
java.lang.Error: Invalid memory access
這個問題也很好解決,就是給出參分配足夠的空間:
byte[] OutData = new byte[1024];
到這里,所有問題都解決了,動態庫也完美運行起來了。
總結
收獲就一句話:對於自己沒有做過的事,要積極嘗試,積極思考,積極請教,然后問題解決后要積極分享。好了,祝大家周末愉快!
JNA和動態庫類型之間的映射關系:
Native Type | Size | Java Type | Common Windows Types |
---|---|---|---|
char | 8-bit integer | byte | BYTE, TCHAR |
short | 16-bit integer | short | WORD |
wchar_t | 16/32-bit character | char | TCHAR |
int | 32-bit integer | int | DWORD |
int | boolean value | boolean | BOOL |
long | 32/64-bit integer | NativeLong | LONG |
long long | 64-bit integer | long | __int64 |
float | 32-bit FP | float | |
double | 64-bit FP | double | |
char* | C string | String | LPCSTR |
void* | pointer | Pointer | LPVOID, HANDLE, LPXXX |
補充表:
Native Type | Java Type | Native Representation |
---|---|---|
char | byte | 8-bit integer |
wchar_t | char | 16/32-bit character |
short | short | 16-bit integer |
int | int | 32-bit integer |
int | boolean | 32-bit integer (customizable) |
long, __int64 | long | 64-bit integer |
long long | long | 64-bit integer |
float | float | 32-bit FP |
double | double | 64-bit FP |
pointer | Buffer/Pointer | |
pointer array | [] (array of primitive type) | |
char* | String | |
wchar_t* | WString | |
char** | String[] | |
wchar_t** | WString[] | |
void* | Pointer | |
void ** | PointerByReference | |
int& | IntByReference | |
int* | IntByReference | |
struct | Structure | |
(*fp)() | Callback | |
varies | NativeMapped | |
long | NativeLong | |
pointer | PointerType |
然后又找到一些補充資料:
C語言 | Java |
---|---|
char* | String (作為入口參數)/ byte[] (作為出口參數) |
unsigned char* | String (作為入口參數)(不確定,沒具體使用過)/ Pointer (作為出口參數) |
int* | IntByReference |