1. 程式人生 > >java中通過JNA呼叫dll JNI的替代者—使用JNA訪問Java外部功能介面

java中通過JNA呼叫dll JNI的替代者—使用JNA訪問Java外部功能介面

---恢復內容開始---

1. JNA簡單介紹

先說JNI(Java Native Interface)吧,有過不同語言間通訊經歷的一般都知道,它允許Java程式碼和其他語言(尤其C/C++)寫的程式碼進行互動,只要遵守呼叫約定即可。首先看下JNI呼叫C/C++的過程,注意寫程式時自下而上,呼叫時自上而下。

 

可見步驟非常的多,很麻煩,使用JNI呼叫.dll/.so共享庫都能體會到這個痛苦的過程。如果已有一個編譯好的.dll/.so檔案,如果使用JNI技 術呼叫,我們首先需要使用C語言另外寫一個.dll/.so共享庫,使用SUN規定的資料結構替代C語言的資料結構,呼叫已有的 dll/so中公佈的函 數。然後再在Java中載入這個庫dll/so,最後編寫Java  native函式作為連結庫中函式的代理。經過這些繁瑣的步驟才能在Java中呼叫 原生代碼。因此,很少有Java程式設計師願意編寫呼叫dll/.so庫中原生函式的java程式。這也使Java語言在客戶端上乏善可陳,可以說JNI是 Java的一大弱點!

那麼JNA是什麼呢?

JNA(Java Native Access)是一個開源的Java框架,是Sun公司推出的一種呼叫本地方法的技術,是建立在經典的JNI基礎之上的一個框架。之所以說它是JNI的替 代者,是因為JNA大大簡化了呼叫本地方法的過程,使用很方便,基本上不需要脫離Java環境就可以完成。

如果要和上圖做個比較,那麼JNA呼叫C/C++的過程大致如下:

 

可以看到步驟減少了很多,最重要的是我們不需要重寫我們的動態連結庫檔案,而是有直接呼叫的API,大大簡化了我們的工作量。

JNA只需要我們寫Java程式碼而不用寫JNI或原生代碼。功能相對於Windows的Platform/Invoke和Python的ctypes。

 

2. JNA技術原理

JNA使用一個小型的JNI庫插樁程式來動態呼叫原生代碼。開發者使用Java介面描述目標本地庫的功能和結構,這使得它很容易利用本機平臺的功能,而不會產生多平臺配置和生成JNI程式碼的高開銷。這樣的效能、準確性和易用性顯然受到很大的重視。

此外,JNA包括一個已與許多本地函式對映的平臺庫,以及一組簡化本地訪問的公用介面。

 

注意:

JNA是建立在JNI技術基礎之上的一個Java類庫,它使您可以方便地使用java直接訪問動態連結庫中的函式。

原來使用JNI,你必須手工用C寫一個動態連結庫,在C語言中對映Java的資料型別。

JNA中,它提供了一個動態的C語言編寫的轉發器,可以自動實現Java和C的資料型別對映,你不再需要編寫C動態連結庫。

也許這也意味著,使用JNA技術比使用JNI技術呼叫動態連結庫會有些微的效能損失。但總體影響不大,因為JNA也避免了JNI的一些平臺配置的開銷。

 

3. JNA簡單使用

JNA的專案已遷移至Github,目前最新版本是4.1.0,已有打包好的jar檔案可供下載。

JNA把一個.dll/.so檔案看做是一個Java介面,下面以一個簡單的例項來說明怎麼使用。

當然要從最經典的HelloWorld開始,我們呼叫C的printf函式打印出“HelloWorld”(官方的例子)

 

新建java project

然後finish

新建檔案HelloWorld.java

在專案下建立lib資料夾,將jna.jar放入其中

在專案Properties->java Build Path->Add External JARs 中新增jna.jar

然後OK

編輯HelloWorld.java,並執行,結果如下:

 

執行程式,如果沒有帶引數則只打印出“Hello, World Hello jna!”,如果帶了引數,則會打印出所有的引數。

 

下面來解釋下這個程式。

(1)需要定義一個介面,繼承自Library 或StdCallLibrary

預設的是繼承Library ,如果動態連結庫裡的函式是以stdcall方式輸出的,那麼就繼承StdCallLibrary,比如眾所周知的kernel32庫。比如上例中的介面定義:

public interface CLibrary extends Library { }

 

(2)介面內部定義

介面內部需要一個公共靜態常量:INSTANCE,通過這個常量,就可以獲得這個介面的例項,從而使用介面的方法,也就是呼叫外部dll/so的函式。

該常量通過Native.loadLibrary()這個API函式獲得,該函式有2個引數:

  • 第 一個引數是動態連結庫dll/so的名稱,但不帶.dll或.so這樣的字尾,這符合JNI的規範,因為帶了字尾名就不可以跨作業系統平臺了。搜尋動態鏈 接庫路徑的順序是:先從當前類的當前資料夾找,如果沒有找到,再在工程當前資料夾下面找win32/win64資料夾,找到後搜尋對應的dll檔案,如果 找不到再到WINDOWS下面去搜索,再找不到就會拋異常了。比如上例中printf函式在Windows平臺下所在的dll庫名稱是msvcrt,而在 其它平臺如Linux下的so庫名稱是c。
  • 第二個引數是本介面的Class型別。JNA通過這個Class型別,根據指定的.dll/.so檔案,動態建立介面的例項。該例項由JNA通過反射自動生成。

CLibrary INSTANCE = (CLibrary) Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class);

介面中只需要定義你要用到的函式或者公共變數,不需要的可以不定義,如上例只定義printf函式:

void printf(String format, Object... args);

注意引數和返回值的型別,應該和連結庫中的函式型別保持一致。

 

(3)呼叫連結庫中的函式

定義好介面後,就可以使用介面中的函式即相應dll/so中的函數了,前面說過呼叫方法就是通過介面中的例項進行呼叫,非常簡單,如上例中:

 

CLibrary.INSTANCE.printf("Hello, World\n"); for (int i=0;i < args.length;i++) { CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]); }

這就是JNA使用的簡單例子,可能有人認為這個例子太簡單了,因為使用的是系統自帶的動態連結庫,應該還給出一個自己實現的庫函式例子。其實我覺得這個完全沒有必要,這也是JNA的方便之處,不像JNI使用使用者自定義庫時還得定義一大堆配置資訊,對於JNA來說,使用使用者自定義庫與使用系統自帶的庫是完全一樣的方法,不需要額外配置什麼資訊。比如我在Windows下建立一個動態庫程式:

 

用vs建立DLL工程:

      檔案->新建->專案->visual c++->win32->win32控制檯應用程式(win32專案也可以)

      填寫專案名稱MyDLL->確定->下一步->DLL(附加選項 對空專案打鉤)->完成。

      到這裡DLL工程就建立完畢了,下面新建兩個檔案MyDLL.cpp和MyDLL.h。

      MyDLL.cpp內容如下:

然後Bulid -->Bulid MyDLL,dll檔案就在debug資料夾下生成了編譯成一個dll檔案(比如Mydll.dll),將Mydll.dll放到工程的bin目錄下,然後編寫JNA程式呼叫即可:

然後修改程式碼並執行如下:

得到輸出。

 

4. JNA技術難點

有過跨語言、跨平臺開發的程式設計師都知道,跨平臺、語言呼叫的難點,就是不同語言之間資料型別不一致造成的問題。絕大部分跨平臺呼叫的失敗,都是這個問題造成的。關於這一點,不論何種語言,何種技術方案,都無法解決這個問題。JNA也不例外。

上面說到介面中使用的函式必須與連結庫中的函式原型保持一致,這是JNA甚至所有跨平臺呼叫的難點,因為C/C++的型別與Java的型別是不一樣的,你必須轉換型別讓它們保持一致,比如printf函式在C中的原型為:

void printf(const char *format, [argument]);

你不可能在Java中也這麼寫,Java中是沒有char *指標型別的,因此const char *轉到Java下就是String型別了。

這就是型別對映(Type Mappings),JNA官方給出的預設型別對映表如下:

還有很多其它的型別對映,需要的請到JNA官網檢視。

另外,JNA還支援型別對映定製,比如有的Java中可能找不到對應的型別(在Windows API中可能會有很多型別,在Java中找不到其對應的型別),JNA中TypeMapper類和相關的介面就提供了這樣的功能。

 

5. JNA能完全替代JNI嗎?

這可能是大家比較關心的問題,但是遺憾的是,JNA是不能完全替代JNI的,因為有些需求還是必須求助於JNI。

使用JNI技術,不僅可以實現Java訪問C函式,也可以實現C語言呼叫Java程式碼。

而JNA只能實現Java訪問C函式,作為一個Java框架,自然不能實現C語言呼叫Java程式碼。此時,你還是需要使用JNI技術。

JNI是JNA的基礎,是Java和C互操作的技術基礎。有時候,你必須迴歸到基礎上來。

 

6.  參考文獻

(1)JNI的替代者—使用JNA訪問Java外部功能介面

---恢復內容結束---