JNA(Java Native Access)是建立在JNI(Java Native Interface,Java本地調(diào)用)技術(shù)之上的Java開源框架,JNA提供了一組Java工具類用于在運行期間動態(tài)訪問系統(tǒng)本地庫(Native Library,如Windows的動態(tài)鏈接庫*.dll、Linux的共享庫*.so)。
使用JNA開發(fā)后無需編寫任何Native/JNI代碼,只需在Java接口中描述目標(biāo)Native Library的函數(shù)與結(jié)構(gòu),JNA會自動實現(xiàn)Java接口到Native Library的映射,可以方便地使用Java直接訪問動態(tài)鏈接庫中的函數(shù)。
JNA提供了一個動態(tài)的C編寫的轉(zhuǎn)發(fā)器,可自動實現(xiàn)Java和C的數(shù)據(jù)類型映射。DLL和SO是C函數(shù)的集合和容器,這與Java中的接口概念吻合,JNA把DLL/SO文件看作是接口,在JNA中定義一個接口相當(dāng)于定義了一個DLL/SO文件的描述文件,Java接口代表了動態(tài)鏈接庫中發(fā)布的函數(shù)。
使用JNA一般只適用于簡單的C/C++庫,如果接口、數(shù)據(jù)結(jié)構(gòu)復(fù)雜的化就不推薦,而且JNA也只提供了C/C++對Java的接口轉(zhuǎn)化。
- Github地址 https://github.com/java-native-access/jna
- API文檔 http://java-native-access.github.io/jna/5.2.0/javadoc/
JNI
JNI全稱Java Native Interface即Java本地調(diào)用,從Java1.1開始JNI標(biāo)準(zhǔn)成為Java平臺的一部分,實現(xiàn)了Java代碼和其他語言編寫的代碼交互。JNI實現(xiàn)Java跨平臺的同時,也能與其他語言(如C、C++)的動態(tài)庫交互。
使用JNI可以實現(xiàn)Java程序中的函數(shù)調(diào)用Native語言編寫的函數(shù),Native一般指的是C/C++編寫的函數(shù)。使用JNI可以實現(xiàn)在Native程序的函數(shù)中調(diào)用Java層的函數(shù),也就是在C/C++程序中可以調(diào)用Java函數(shù)。
JVM本身就是用Native語言編寫的,JVM運行在具體平臺上其本身是無法做到與平臺無關(guān)的。通過JNI技術(shù)可以對Java層屏蔽具體JVM實現(xiàn)上的差異,進(jìn)而實現(xiàn)Java本身的平臺無關(guān)性。
使用JNI并不簡單,對一個編譯好的DLL/SO文件,使用JNI調(diào)用時首先需要使用C另外編寫一個DLL/SO共享庫,使用SUN規(guī)定的數(shù)據(jù)結(jié)構(gòu)替代C的數(shù)據(jù)結(jié)構(gòu),再調(diào)用已有的DLL/SO中公布的函數(shù)。然后在Java中載入這個DLL/SO,最后編寫Java Native函數(shù)作為鏈接庫中函數(shù)的代理。使用JNI技術(shù)調(diào)用本地代碼比較繁瑣,因此引入了JNA技術(shù)。
使用JNI調(diào)用C/C++的過程
| 時序 | 步驟 | 文件類型 |
|---|---|---|
| 1 | 編寫Java類代碼 | *.java |
| 2 | 編譯成字節(jié)碼 | *.class |
| 3 | 產(chǎn)生C/C++頭文件 | *.h |
| 4 | 編寫JNI實現(xiàn)代碼 | *.c/cpp |
| 5 | 編譯成鏈接庫文件 | *.dll/so |
使用JNA相比JNI調(diào)用動態(tài)鏈接庫會有性能損耗,速度會降低幾倍。另外,使用JNI不僅可以實現(xiàn)Java訪問C函數(shù),也可實現(xiàn)C調(diào)用Java代碼。但JNA只能實現(xiàn)Java訪問C函數(shù)。
入門
JNA定義的接口繼承自com.sun.jna.Library接口,若DLL文件中的函數(shù)以stdcall方式輸出,接口應(yīng)繼承com.sun.jna.win32.StdCallLibrary接口。
例如:在SpringBoot中是用DLL文件
- 創(chuàng)建Maven工程,將DLL文件放到
resources目錄下。 - 引入JNA相關(guān)的jar包
- 創(chuàng)建繼承自
Library類的接口 - 接口中創(chuàng)建對象用于加載DLL/SO的類庫。
- 接口中聲明DLL/SO類庫頭文件中暴露的方法
引入依賴
$ vim pom.xml
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.4.0</version>
</dependency>
JNA引入動態(tài)鏈接庫
通過JNA建立與動態(tài)鏈接庫的映射,只需創(chuàng)建一個接口來調(diào)用Native的loadLabrary()方法。
JNA建立與動態(tài)鏈接庫中函數(shù)的對應(yīng)關(guān)系,只需在加載相應(yīng)類庫接口中聲明的函數(shù)即可。
JNA調(diào)用原生函數(shù)的模式
JNI中是用Native關(guān)鍵字來聲明一個Java方法表明外部的原生函數(shù),JNA中沒有使用Native表明原生函數(shù)而是使用Java Interface來表明動態(tài)鏈接庫中全部原生函數(shù)。
使用JNI加載動態(tài)鏈接庫時必須使用System.loadLibrary()方法,將專門為JNI編寫的動態(tài)鏈接庫載入,這個動態(tài)鏈接庫實際上是真正動態(tài)鏈接庫的代理。使用JNA無需編寫作為代理的動態(tài)鏈接庫,直接使用JNA庫的Native類中的loadLibrary()方法可直接加載最終的動態(tài)鏈接庫。
類型映射
JNA使用的數(shù)據(jù)類型是Java的數(shù)據(jù)類型,而原生函數(shù)中是用的數(shù)據(jù)類型是原生函數(shù)編程語言的數(shù)據(jù)類型,大多為C/Delphi/匯編等語言的數(shù)據(jù)類型。如果數(shù)據(jù)類型映射不一致,調(diào)用時可能會發(fā)生無法預(yù)知的行為導(dǎo)致調(diào)用失敗。
JNA的難點在于編程語言之間的數(shù)據(jù)類型不一致,Java中是用的函數(shù)必須與鏈接庫中的函數(shù)的函數(shù)原型保持一致,這是JNA甚至是所有跨平臺調(diào)用的難點,因為C/C++的類型與Java的類型是不一樣的,因此必須將其裝換為Java對應(yīng)的數(shù)據(jù)類型,這就是類型映射(Type Mappings)。類型映射的難點在于結(jié)構(gòu)體、指針和函數(shù)回調(diào)。