廣州輕工職業學校 劉魁元
廣州市機電高級技工學校 余楷鑫
JAVA跨平臺的特性深受JAVA程序員的喜愛,這是JAVA的優越性,但是正是為了實現跨平臺的目的,JAVA和本地系統的各種內在聯系變得很少,這大大約束了它的功能,比如與一些硬件設備的通信,往往要花很大的精力去編寫動態函數庫來管理設備端口,JDK從1.1版本開始提供了解決這個問題的技術標準:JNI標準;和許多解釋執行的語言一樣,JAVA提供了調用原生函數的機制,以加強JAVA平臺的能力,JavaTMNative Interface(JNI)就是JAVA調用原生函數的機制。
事實上,很多JAVA核心代碼內部就是使用JNI實現的,這些JAVA功能實際上是通過原生函數提供的。但是,使用JNI對于JAVA開發者來說簡直是一場惡夢;如果你已經有了原生代碼,使用JNI,你必須用C語言重新編寫一個動態庫,這個動態庫的唯一功能就是使用JAVA能理解的C代碼來調用目標原生函數。一般情況下,設備廠商提供的硬件接口都已經經過一定的封裝和處理,不能直接使用JAVA程序通過端口和設備進行通信,JAVA若想與設備進行通信,就必須使用JNI的方式重新編寫動態函數庫來調用硬件設備,而這種方法的繁冗程度也可想而知,開發效率也不高,因此,人們一直都視JNI為禁地,輕易不愿涉足。
JNA(Java Native Access)是一個開源的JAVA框架,由SUN公司主導開發的,建立在經典的JNI基礎之上的一個框架,它提供一組JAVA工具類用于在運行期動態訪問系統本地庫(native library:如Window的dll)而不需要編寫任何Native/JNI代碼。開發人員只要在一個JAVA接口中描述目標native library的函數與結構,JNA將自動實現JAVA接口到native function的映射。[1]JNA的項目地址:https://jna.dev.java.net/,JNA使JAVA調用原生函數就像.NET上的P/Invoke一樣方便快捷,極大的提高程序員編寫代碼的效率。JNA使JAVA平臺可以方便的調用原生函數,這大大擴展了JAVA平臺的整合能力,簡化了開發難度,又增強了JAVA與硬件設備通信的功能。
JNA是建立在JNI技術基礎之上的一個JAVA類庫,它使編程人員可以方便地使用JAVA直接訪問動態鏈接庫中的函數,從而實現對.dll/.so文件的訪問。原來使用JNI,你必須手工用C寫一個動態鏈接庫,在C語言中映射JAVA的數據類型,而編寫動態鏈接庫的唯一用途就是使用JAVA能夠理解的C代碼來調用目標原生函數。同時編寫JAVA和C代碼的過程使開發的難度大大增加,而這個沒其他用途的動態鏈接庫的編寫過程顯得相當枯燥。JNI調用設備方法如圖1所示。
JNA中,它提供了一個動態的C語言編寫的轉發器,可以自動實現JAVA和C的數據類型映射。作為程序員,不再需要編寫C動態鏈接庫,極大地簡化了JAVA調用原生函數的過程。當然,這也意味著,使用JNA技術比使用JNI技術調用動態鏈接庫會對性能略有影響,如可能在速度上會降低幾倍,但影響并不大。從總體上來看,使用JNA是利遠遠大于弊的。JNA打破了JAVA和原生代碼原本涇渭分明的界限,充分發揮各自擅長領域的分工合作,提高程序員開發的效率。從某種意義上講,JNA從JNI中來,但卻青出于藍而勝于藍,逐漸獲得了廣大開發人員的喜愛。其調用設備方法如圖2所示。

表1 JAVA與C語言數據結構的對應關系

圖1 JNI調用設備方法

圖2 JNA調用設備方法
(1)當前路徑是在項目下,而不是bin輸出目錄下。JNA在搜索dll路徑的時候首先是從項目的根路徑開始查找,然后再搜索當前操作系統的全局路徑,其次搜索path指定的路徑。
(2)JNA所使用的數據類型屬于JAVA的數據類型,而原生函數中的數據類型是由使用的編程語言決定的,有可能是C、Delphi等語言的數據類型。JAVA與C語言數據結構的對應關系如表1所示。
Dll是C函數的集合、容器,這正和接口的概念吻合。JNA把一個dll/.so文件看做是一個JAVA接口,JNA通過調用接口來實現與第三方dll的通信。下面我們將以一個例子來說明如何調用dll中的函數。
(1)首先我們定義這樣一個接口


(2)分析過程如下所示
如果dll是以stdcall方式輸出函數,那么就繼承StdCallLibrary。否則就繼承默認的Library接口。接口內部需要一個公共靜態常量:sdtapi。

通過這個常量,就可以獲得這個接口的實例,從而使用接口的方法。也就是調用外部dll的函數!注意:1) Native.loadLibrary()函數有2個參數:第一個參數是dll或者.so文件的名字,但不帶后綴名。這符合JNI的規范,因為帶了后綴名就不可以跨操作系統平臺了。第二個參數是本接口的Class類型。JNA通過這個Class類型,根據指定的dll/.so文件,動態創建接口的實例。2)接口中你只需要定義你需要的函數或者公共變量,不需要的可以不定義。
boolean USB_DevInit(int port);
參數和返回值的類型,應該和dll中的C函數的類型一致。這是JNA,甚至所有跨平臺調用的難點。這里,C語言的函數參數是:int port;JNA中對應的JAVA類型也是int,所以我們在做跨平臺的時候,在數據類型上的選擇應該盡量做到簡單,這有利于跨平臺的實現。
我們已經見識了JNA的強大。但是,有些需求還是必須求助于JNI。JNA是建立在JNI技術基礎之上的一個框架。使用JNI技術,不僅可以實現JAVA訪問C函數,也可以實現C語言調用JAVA代碼。而JNA只能實現JAVA訪問C函數,作為一個JAVA框架,自然不能實現C語言調用JAVA代碼。此時,你還是需要使用JNI技術。JNI是JNA的基礎,是JAVA和C互操作的技術基礎。
目前市場上的大多硬件廠商提供的開發包是原生函數,比如讀寫設備就是這個情況,一般設備廠商會提供兩種類型的類庫文件,windows系統的會包含.dll/.h/.lib文件,而linux會包含.so/.a文件,這里只討論windows系統下的c/c++編譯的dll文件調用方法。
現在來討論這樣一個問題,我們現要為JAVA項目添加IC卡讀寫器功能,設備廠商提供了一個fkc60.dll動態庫,下面以其中的二個函數為例:
1)bool USB_DevInit(int port);
2)用途及說明:調用其它函數前先打開串口,成功返回true,失敗返回false;
3)參數:port表示串行口,1為端口1,2為端口2,以此類推。
1)bool USB_BeepEx(int port,int ptype);
2)用途及說明:控制讀寫器發聲;成功返回true,失敗返回false;
3)參數:port表示串口號,1為端口1,2為端口2,以此類推,ptype表示發聲類型0發短聲,1發長聲。
首先,你需要下載一個jna.jar包,就可以方便地調用動態鏈接庫中的C函數了,在JAVA項目中引入jna.jar包,本例是把fkc60放在項目的lib目錄下引入的。



最后執行可以看到控制臺中打印串行口打開成功信息,并聽到讀寫器發出了短聲。
JNA技術相對于JNI技術確實提高了開發的效率,并且擴展了JAVA的功能,但它仍存在著一個缺陷,即破壞了JAVA程序的最重要優點:平臺無關性,所以除非必須(不得不)使用JNA技術,一般還是提倡寫100%純JAVA程序,根據自己的經驗和查閱的一些資料,把可以使用JNA的情況羅列如下:
1.需要直接操作物理設備,而沒有相關的驅動程序;
2.用JAVA會產生系統難以支付的開銷,如需要大量網絡鏈接的場合;
3.存在大量可重用的C/C++代碼,通過JNA可以減少開發工作量,避免重復開發。
[1]沈東良.深入淺出JNA—快速調用原生函數[J].程序員,2009,3.
[2]匿名.JNA—JNI終結者.