JNA(Java Native Access)框架是一個開源的Java框架,是SUN公司主導開發的,建立在經典的JNI的基礎之上的一個框架。使用JNI調用共享類庫(.dll/.so文件)是非常麻煩的事情,既需要編寫java代碼,又要編寫C語言的代理方法,這其中需要很多數據類型的轉換,是讓人非常頭痛。JNA框架就是為了解決這些問題和繁瑣的事情而開發的,它提供一組Java工具類用於在運行期動態訪問系統本地共享類庫而不需要編寫任何Native/JNI代碼。開發人員只要在一個java接口中描述目標native library的函數與結構,JNA將自動實現Java接口到native function的映射,大大降低了Java調用本體共享庫的開發難度。JNA與.NET平台上的P/Invoke機制一樣簡單和方便。
你只需要下載一個jar包,就可以使用JNA的強大功能方便地調用動態鏈接庫中的C函數。下載地址是:https://github.com/twall/jna
JNA調用本地的庫函數
假設有一個動態鏈接庫: CnblogsJna.dll。里面有這樣一個函數:
void sayHello(char * name){ printf("C Code Start...\n"); printf("Hello! Mr %s.\n",name); printf("C Code End.\n"); }
此函數接收一個代表姓名的字符指針,然后在控制台上輸出幾句字符串。
為了調用這個函數,使用JNA,我們需要編寫下面的JAVA代碼:
1、接口ICnblogsJna.java
import com.sun.jna.Library; import com.sun.jna.Native; /** * @author BCH)王國成 */ public interface ICnblogsJna extends Library { // 接口實例 ICnblogsJna INSTANCE = (ICnblogsJna) Native.loadLibrary("CnblogsJna",ICnblogsJna.class); // 與C代碼映射的函數 public void sayHello(String name); }
注意:接口需要繼承制JNA的Library接口;
接口內部需要一個公共靜態常量INSTANCE, 通過這個常量,就可以獲得這個接口的實例,從而使用接口的方法。也就是調用動態鏈接庫CnblogsJna.dll中的sayHello函數了。
如果使用JNI,你需要使用System.loadLibrary方法,來加載我們專為JNI編寫的動態鏈接庫,這個動態鏈接庫實際上是我們真正需要的動態鏈接庫的代理。使用JNA是,需要用JNA類庫的Native類的loadLibrary函數,是直接把我們需要的動態鏈接庫載入進來。使用JNA,我們不需要編寫作為代理的動態鏈接庫,不需要編寫一行原生代碼。
Native類的loadLibrary方法有兩個參數:第一個參數是.dll或者.so文件的名字,但不帶后綴名。這符合JNI的規范,因為帶了后綴名就不可以跨操作系統平台了。第二個參數是本接口的Class類型,JNA通過這個Class類型,根據指定的dll/.so文件,動態創建接口的實例。
2、調用動態鏈接庫文件中函數的Java方法():
/** * @author BCH)王國成 */ public class CnblogsJna { /** * 入口函數 * @param args */ public static void main(String[] args) { // 調用動態鏈庫中的sayHello函數 ICnblogsJna.INSTANCE.sayHello("Wanggc/王國成"); } }
方法很簡單,就像調用Java自己的函數一樣。執行輸出結果如下:
和原生代碼的類型映射
跨平台,跨語言調用的難點,就是不同語言之間數據類型不一致造成的,JNA也不例外,要想跨平台調用,數據類型轉換是個無法回避的問題。JNA提供了Java和原生代碼的類型映射。
Java和C數據類型的對應表如下:
Java 類型 |
C 類型 |
原生表現 |
boolean |
int |
32位整數 (可定制) |
byte |
char |
8位整數 |
char |
wchar_t |
平台依賴 |
short |
short |
16位整數 |
int |
int |
32位整數 |
long |
long long, __int64 |
64位整數 |
float |
float |
32位浮點數 |
double |
double |
64位浮點數 |
Buffer/Pointer |
pointer |
平台依賴(32或 64位指針) |
<T>[] (基本類型的數組) |
pointer/array |
32或 64位指針(參數/返回值) 鄰接內存(結構體成員) |
String |
char* |
/0結束的數組 (native encoding or jna.encoding) |
WString |
wchar_t* |
/0結束的數組(unicode) |
String[] |
char** |
/0結束的數組的數組 |
WString[] |
wchar_t** |
/0結束的寬字符數組的數組 |
Structure |
struct*/struct |
指向結構體的指針 (參數或返回值) (或者明確指定是結構體指針) |
Union |
union |
等同於結構體 |
Structure[] |
struct[] |
結構體的數組,鄰接內存 |
Callback |
<T> (*fp)() |
Java函數指針或原生函數指針 |
NativeMapped |
varies |
依賴於定義 |
NativeLong |
long |
平台依賴(32或64位整數) |
PointerType |
pointer |
和 Pointer相同 |
由於跨平台和跨語言尤其自身無法克服的確定,所以盡量少跨平台、跨語言傳遞數據。如果必須這樣做,也盡量使用簡單的數據類型。如果有復雜的數據類型需要在Java和原生函數中傳遞,那么我們就必須在Java中模擬這種復雜的原生類型。這將大大增加實現的難度,甚至無法實現。如果在Java和原生函數間存在大量的數據傳遞,一方面,會損失程序的性能;另一方面會造成內存碎片,Java調用原生函數時,會把數據固定在內存中,這樣原生函數才可以訪問這些Java數據。這些數據,JVM的GC不能管理,就會造成內存碎片。