java通過JNA調用動態庫


前言

老規矩,先說下為什么會有這篇文章。近期對接了一個項目,應接口提供方要求,必須通過動態庫調用,一個是為了安全可控,調用方不用知道內部實現,加密、解密、具體的邏輯不需要考慮,只需要調用即可;另一個是封裝了統一的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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM