JNA(Java Native Access)是建立在JNI(Java Native Interface,Java本地調(diào)用)技術(shù)之上的Java開(kāi)源框架嗜暴,JNA提供了一組Java工具類用于在運(yùn)行期間動(dòng)態(tài)訪問(wèn)系統(tǒng)本地庫(kù)(Native Library精堕,如Windows的動(dòng)態(tài)鏈接庫(kù)*.dll
孵淘、Linux的共享庫(kù)*.so
)。
使用JNA開(kāi)發(fā)后無(wú)需編寫(xiě)任何Native/JNI代碼歹篓,只需在Java接口中描述目標(biāo)Native Library的函數(shù)與結(jié)構(gòu)瘫证,JNA會(huì)自動(dòng)實(shí)現(xiàn)Java接口到Native Library的映射,可以方便地使用Java直接訪問(wèn)動(dòng)態(tài)鏈接庫(kù)中的函數(shù)庄撮。
JNA提供了一個(gè)動(dòng)態(tài)的C編寫(xiě)的轉(zhuǎn)發(fā)器背捌,可自動(dòng)實(shí)現(xiàn)Java和C的數(shù)據(jù)類型映射。DLL和SO是C函數(shù)的集合和容器洞斯,這與Java中的接口概念吻合毡庆,JNA把DLL/SO文件看作是接口,在JNA中定義一個(gè)接口相當(dāng)于定義了一個(gè)DLL/SO文件的描述文件,Java接口代表了動(dòng)態(tài)鏈接庫(kù)中發(fā)布的函數(shù)么抗。
使用JNA一般只適用于簡(jiǎn)單的C/C++庫(kù)毅否,如果接口、數(shù)據(jù)結(jié)構(gòu)復(fù)雜的化就不推薦乖坠,而且JNA也只提供了C/C++對(duì)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開(kāi)始JNI標(biāo)準(zhǔn)成為Java平臺(tái)的一部分熊泵,實(shí)現(xiàn)了Java代碼和其他語(yǔ)言編寫(xiě)的代碼交互。JNI實(shí)現(xiàn)Java跨平臺(tái)的同時(shí)甸昏,也能與其他語(yǔ)言(如C顽分、C++)的動(dòng)態(tài)庫(kù)交互。
使用JNI可以實(shí)現(xiàn)Java程序中的函數(shù)調(diào)用Native語(yǔ)言編寫(xiě)的函數(shù)施蜜,Native一般指的是C/C++編寫(xiě)的函數(shù)卒蘸。使用JNI可以實(shí)現(xiàn)在Native程序的函數(shù)中調(diào)用Java層的函數(shù),也就是在C/C++程序中可以調(diào)用Java函數(shù)翻默。
JVM本身就是用Native語(yǔ)言編寫(xiě)的缸沃,JVM運(yùn)行在具體平臺(tái)上其本身是無(wú)法做到與平臺(tái)無(wú)關(guān)的。通過(guò)JNI技術(shù)可以對(duì)Java層屏蔽具體JVM實(shí)現(xiàn)上的差異修械,進(jìn)而實(shí)現(xiàn)Java本身的平臺(tái)無(wú)關(guān)性趾牧。
使用JNI并不簡(jiǎn)單,對(duì)一個(gè)編譯好的DLL/SO文件肯污,使用JNI調(diào)用時(shí)首先需要使用C另外編寫(xiě)一個(gè)DLL/SO共享庫(kù)翘单,使用SUN規(guī)定的數(shù)據(jù)結(jié)構(gòu)替代C的數(shù)據(jù)結(jié)構(gòu),再調(diào)用已有的DLL/SO中公布的函數(shù)蹦渣。然后在Java中載入這個(gè)DLL/SO哄芜,最后編寫(xiě)Java Native函數(shù)作為鏈接庫(kù)中函數(shù)的代理。使用JNI技術(shù)調(diào)用本地代碼比較繁瑣柬唯,因此引入了JNA技術(shù)认臊。
使用JNI調(diào)用C/C++的過(guò)程
時(shí)序 | 步驟 | 文件類型 |
---|---|---|
1 | 編寫(xiě)Java類代碼 | *.java |
2 | 編譯成字節(jié)碼 | *.class |
3 | 產(chǎn)生C/C++頭文件 | *.h |
4 | 編寫(xiě)JNI實(shí)現(xiàn)代碼 | *.c/cpp |
5 | 編譯成鏈接庫(kù)文件 | *.dll/so |
使用JNA相比JNI調(diào)用動(dòng)態(tài)鏈接庫(kù)會(huì)有性能損耗,速度會(huì)降低幾倍锄奢。另外美尸,使用JNI不僅可以實(shí)現(xiàn)Java訪問(wèn)C函數(shù),也可實(shí)現(xiàn)C調(diào)用Java代碼斟薇。但JNA只能實(shí)現(xiàn)Java訪問(wèn)C函數(shù)师坎。
入門(mén)
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)建對(duì)象用于加載DLL/SO的類庫(kù)遏乔。
- 接口中聲明DLL/SO類庫(kù)頭文件中暴露的方法
引入依賴
$ vim pom.xml
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.4.0</version>
</dependency>
JNA引入動(dòng)態(tài)鏈接庫(kù)
通過(guò)JNA建立與動(dòng)態(tài)鏈接庫(kù)的映射义矛,只需創(chuàng)建一個(gè)接口來(lái)調(diào)用Native的loadLabrary()
方法。
JNA建立與動(dòng)態(tài)鏈接庫(kù)中函數(shù)的對(duì)應(yīng)關(guān)系盟萨,只需在加載相應(yīng)類庫(kù)接口中聲明的函數(shù)即可凉翻。
JNA調(diào)用原生函數(shù)的模式
JNI中是用Native關(guān)鍵字來(lái)聲明一個(gè)Java方法表明外部的原生函數(shù),JNA中沒(méi)有使用Native表明原生函數(shù)而是使用Java Interface來(lái)表明動(dòng)態(tài)鏈接庫(kù)中全部原生函數(shù)捻激。
使用JNI加載動(dòng)態(tài)鏈接庫(kù)時(shí)必須使用System.loadLibrary()
方法制轰,將專門(mén)為JNI編寫(xiě)的動(dòng)態(tài)鏈接庫(kù)載入,這個(gè)動(dòng)態(tài)鏈接庫(kù)實(shí)際上是真正動(dòng)態(tài)鏈接庫(kù)的代理胞谭。使用JNA無(wú)需編寫(xiě)作為代理的動(dòng)態(tài)鏈接庫(kù)垃杖,直接使用JNA庫(kù)的Native類中的loadLibrary()
方法可直接加載最終的動(dòng)態(tài)鏈接庫(kù)。
類型映射
JNA使用的數(shù)據(jù)類型是Java的數(shù)據(jù)類型丈屹,而原生函數(shù)中是用的數(shù)據(jù)類型是原生函數(shù)編程語(yǔ)言的數(shù)據(jù)類型调俘,大多為C/Delphi/匯編等語(yǔ)言的數(shù)據(jù)類型。如果數(shù)據(jù)類型映射不一致旺垒,調(diào)用時(shí)可能會(huì)發(fā)生無(wú)法預(yù)知的行為導(dǎo)致調(diào)用失敗彩库。
JNA的難點(diǎn)在于編程語(yǔ)言之間的數(shù)據(jù)類型不一致,Java中是用的函數(shù)必須與鏈接庫(kù)中的函數(shù)的函數(shù)原型保持一致先蒋,這是JNA甚至是所有跨平臺(tái)調(diào)用的難點(diǎn)骇钦,因?yàn)镃/C++的類型與Java的類型是不一樣的,因此必須將其裝換為Java對(duì)應(yīng)的數(shù)據(jù)類型鞭达,這就是類型映射(Type Mappings)司忱。類型映射的難點(diǎn)在于結(jié)構(gòu)體、指針和函數(shù)回調(diào)畴蹭。