廣州輕工職業(yè)學(xué)校 劉魁元
廣州市機(jī)電高級(jí)技工學(xué)校 余楷鑫
JAVA跨平臺(tái)的特性深受JAVA程序員的喜愛,這是JAVA的優(yōu)越性,但是正是為了實(shí)現(xiàn)跨平臺(tái)的目的,JAVA和本地系統(tǒng)的各種內(nèi)在聯(lián)系變得很少,這大大約束了它的功能,比如與一些硬件設(shè)備的通信,往往要花很大的精力去編寫動(dòng)態(tài)函數(shù)庫(kù)來管理設(shè)備端口,JDK從1.1版本開始提供了解決這個(gè)問題的技術(shù)標(biāo)準(zhǔn):JNI標(biāo)準(zhǔn);和許多解釋執(zhí)行的語(yǔ)言一樣,JAVA提供了調(diào)用原生函數(shù)的機(jī)制,以加強(qiáng)JAVA平臺(tái)的能力,JavaTMNative Interface(JNI)就是JAVA調(diào)用原生函數(shù)的機(jī)制。
事實(shí)上,很多JAVA核心代碼內(nèi)部就是使用JNI實(shí)現(xiàn)的,這些JAVA功能實(shí)際上是通過原生函數(shù)提供的。但是,使用JNI對(duì)于JAVA開發(fā)者來說簡(jiǎn)直是一場(chǎng)惡夢(mèng);如果你已經(jīng)有了原生代碼,使用JNI,你必須用C語(yǔ)言重新編寫一個(gè)動(dòng)態(tài)庫(kù),這個(gè)動(dòng)態(tài)庫(kù)的唯一功能就是使用JAVA能理解的C代碼來調(diào)用目標(biāo)原生函數(shù)。一般情況下,設(shè)備廠商提供的硬件接口都已經(jīng)經(jīng)過一定的封裝和處理,不能直接使用JAVA程序通過端口和設(shè)備進(jìn)行通信,JAVA若想與設(shè)備進(jìn)行通信,就必須使用JNI的方式重新編寫動(dòng)態(tài)函數(shù)庫(kù)來調(diào)用硬件設(shè)備,而這種方法的繁冗程度也可想而知,開發(fā)效率也不高,因此,人們一直都視JNI為禁地,輕易不愿涉足。
JNA(Java Native Access)是一個(gè)開源的JAVA框架,由SUN公司主導(dǎo)開發(fā)的,建立在經(jīng)典的JNI基礎(chǔ)之上的一個(gè)框架,它提供一組JAVA工具類用于在運(yùn)行期動(dòng)態(tài)訪問系統(tǒng)本地庫(kù)(native library:如Window的dll)而不需要編寫任何Native/JNI代碼。開發(fā)人員只要在一個(gè)JAVA接口中描述目標(biāo)native library的函數(shù)與結(jié)構(gòu),JNA將自動(dòng)實(shí)現(xiàn)JAVA接口到native function的映射。[1]JNA的項(xiàng)目地址:https://jna.dev.java.net/,JNA使JAVA調(diào)用原生函數(shù)就像.NET上的P/Invoke一樣方便快捷,極大的提高程序員編寫代碼的效率。JNA使JAVA平臺(tái)可以方便的調(diào)用原生函數(shù),這大大擴(kuò)展了JAVA平臺(tái)的整合能力,簡(jiǎn)化了開發(fā)難度,又增強(qiáng)了JAVA與硬件設(shè)備通信的功能。
JNA是建立在JNI技術(shù)基礎(chǔ)之上的一個(gè)JAVA類庫(kù),它使編程人員可以方便地使用JAVA直接訪問動(dòng)態(tài)鏈接庫(kù)中的函數(shù),從而實(shí)現(xiàn)對(duì).dll/.so文件的訪問。原來使用JNI,你必須手工用C寫一個(gè)動(dòng)態(tài)鏈接庫(kù),在C語(yǔ)言中映射JAVA的數(shù)據(jù)類型,而編寫動(dòng)態(tài)鏈接庫(kù)的唯一用途就是使用JAVA能夠理解的C代碼來調(diào)用目標(biāo)原生函數(shù)。同時(shí)編寫JAVA和C代碼的過程使開發(fā)的難度大大增加,而這個(gè)沒其他用途的動(dòng)態(tài)鏈接庫(kù)的編寫過程顯得相當(dāng)枯燥。JNI調(diào)用設(shè)備方法如圖1所示。
JNA中,它提供了一個(gè)動(dòng)態(tài)的C語(yǔ)言編寫的轉(zhuǎn)發(fā)器,可以自動(dòng)實(shí)現(xiàn)JAVA和C的數(shù)據(jù)類型映射。作為程序員,不再需要編寫C動(dòng)態(tài)鏈接庫(kù),極大地簡(jiǎn)化了JAVA調(diào)用原生函數(shù)的過程。當(dāng)然,這也意味著,使用JNA技術(shù)比使用JNI技術(shù)調(diào)用動(dòng)態(tài)鏈接庫(kù)會(huì)對(duì)性能略有影響,如可能在速度上會(huì)降低幾倍,但影響并不大。從總體上來看,使用JNA是利遠(yuǎn)遠(yuǎn)大于弊的。JNA打破了JAVA和原生代碼原本涇渭分明的界限,充分發(fā)揮各自擅長(zhǎng)領(lǐng)域的分工合作,提高程序員開發(fā)的效率。從某種意義上講,JNA從JNI中來,但卻青出于藍(lán)而勝于藍(lán),逐漸獲得了廣大開發(fā)人員的喜愛。其調(diào)用設(shè)備方法如圖2所示。
表1 JAVA與C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)的對(duì)應(yīng)關(guān)系
圖1 JNI調(diào)用設(shè)備方法
圖2 JNA調(diào)用設(shè)備方法
(1)當(dāng)前路徑是在項(xiàng)目下,而不是bin輸出目錄下。JNA在搜索dll路徑的時(shí)候首先是從項(xiàng)目的根路徑開始查找,然后再搜索當(dāng)前操作系統(tǒng)的全局路徑,其次搜索path指定的路徑。
(2)JNA所使用的數(shù)據(jù)類型屬于JAVA的數(shù)據(jù)類型,而原生函數(shù)中的數(shù)據(jù)類型是由使用的編程語(yǔ)言決定的,有可能是C、Delphi等語(yǔ)言的數(shù)據(jù)類型。JAVA與C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)的對(duì)應(yīng)關(guān)系如表1所示。
Dll是C函數(shù)的集合、容器,這正和接口的概念吻合。JNA把一個(gè)dll/.so文件看做是一個(gè)JAVA接口,JNA通過調(diào)用接口來實(shí)現(xiàn)與第三方dll的通信。下面我們將以一個(gè)例子來說明如何調(diào)用dll中的函數(shù)。
(1)首先我們定義這樣一個(gè)接口
(2)分析過程如下所示
如果dll是以stdcall方式輸出函數(shù),那么就繼承StdCallLibrary。否則就繼承默認(rèn)的Library接口。接口內(nèi)部需要一個(gè)公共靜態(tài)常量:sdtapi。
通過這個(gè)常量,就可以獲得這個(gè)接口的實(shí)例,從而使用接口的方法。也就是調(diào)用外部dll的函數(shù)!注意:1) Native.loadLibrary()函數(shù)有2個(gè)參數(shù):第一個(gè)參數(shù)是dll或者.so文件的名字,但不帶后綴名。這符合JNI的規(guī)范,因?yàn)閹Я撕缶Y名就不可以跨操作系統(tǒng)平臺(tái)了。第二個(gè)參數(shù)是本接口的Class類型。JNA通過這個(gè)Class類型,根據(jù)指定的dll/.so文件,動(dòng)態(tài)創(chuàng)建接口的實(shí)例。2)接口中你只需要定義你需要的函數(shù)或者公共變量,不需要的可以不定義。
boolean USB_DevInit(int port);
參數(shù)和返回值的類型,應(yīng)該和dll中的C函數(shù)的類型一致。這是JNA,甚至所有跨平臺(tái)調(diào)用的難點(diǎn)。這里,C語(yǔ)言的函數(shù)參數(shù)是:int port;JNA中對(duì)應(yīng)的JAVA類型也是int,所以我們?cè)谧隹缙脚_(tái)的時(shí)候,在數(shù)據(jù)類型上的選擇應(yīng)該盡量做到簡(jiǎn)單,這有利于跨平臺(tái)的實(shí)現(xiàn)。
我們已經(jīng)見識(shí)了JNA的強(qiáng)大。但是,有些需求還是必須求助于JNI。JNA是建立在JNI技術(shù)基礎(chǔ)之上的一個(gè)框架。使用JNI技術(shù),不僅可以實(shí)現(xiàn)JAVA訪問C函數(shù),也可以實(shí)現(xiàn)C語(yǔ)言調(diào)用JAVA代碼。而JNA只能實(shí)現(xiàn)JAVA訪問C函數(shù),作為一個(gè)JAVA框架,自然不能實(shí)現(xiàn)C語(yǔ)言調(diào)用JAVA代碼。此時(shí),你還是需要使用JNI技術(shù)。JNI是JNA的基礎(chǔ),是JAVA和C互操作的技術(shù)基礎(chǔ)。
目前市場(chǎng)上的大多硬件廠商提供的開發(fā)包是原生函數(shù),比如讀寫設(shè)備就是這個(gè)情況,一般設(shè)備廠商會(huì)提供兩種類型的類庫(kù)文件,windows系統(tǒng)的會(huì)包含.dll/.h/.lib文件,而linux會(huì)包含.so/.a文件,這里只討論windows系統(tǒng)下的c/c++編譯的dll文件調(diào)用方法。
現(xiàn)在來討論這樣一個(gè)問題,我們現(xiàn)要為JAVA項(xiàng)目添加IC卡讀寫器功能,設(shè)備廠商提供了一個(gè)fkc60.dll動(dòng)態(tài)庫(kù),下面以其中的二個(gè)函數(shù)為例:
1)bool USB_DevInit(int port);
2)用途及說明:調(diào)用其它函數(shù)前先打開串口,成功返回true,失敗返回false;
3)參數(shù):port表示串行口,1為端口1,2為端口2,以此類推。
1)bool USB_BeepEx(int port,int ptype);
2)用途及說明:控制讀寫器發(fā)聲;成功返回true,失敗返回false;
3)參數(shù):port表示串口號(hào),1為端口1,2為端口2,以此類推,ptype表示發(fā)聲類型0發(fā)短聲,1發(fā)長(zhǎng)聲。
首先,你需要下載一個(gè)jna.jar包,就可以方便地調(diào)用動(dòng)態(tài)鏈接庫(kù)中的C函數(shù)了,在JAVA項(xiàng)目中引入jna.jar包,本例是把fkc60放在項(xiàng)目的lib目錄下引入的。
最后執(zhí)行可以看到控制臺(tái)中打印串行口打開成功信息,并聽到讀寫器發(fā)出了短聲。
JNA技術(shù)相對(duì)于JNI技術(shù)確實(shí)提高了開發(fā)的效率,并且擴(kuò)展了JAVA的功能,但它仍存在著一個(gè)缺陷,即破壞了JAVA程序的最重要優(yōu)點(diǎn):平臺(tái)無關(guān)性,所以除非必須(不得不)使用JNA技術(shù),一般還是提倡寫100%純JAVA程序,根據(jù)自己的經(jīng)驗(yàn)和查閱的一些資料,把可以使用JNA的情況羅列如下:
1.需要直接操作物理設(shè)備,而沒有相關(guān)的驅(qū)動(dòng)程序;
2.用JAVA會(huì)產(chǎn)生系統(tǒng)難以支付的開銷,如需要大量網(wǎng)絡(luò)鏈接的場(chǎng)合;
3.存在大量可重用的C/C++代碼,通過JNA可以減少開發(fā)工作量,避免重復(fù)開發(fā)。
[1]沈東良.深入淺出JNA—快速調(diào)用原生函數(shù)[J].程序員,2009,3.
[2]匿名.JNA—JNI終結(jié)者.