Java 本地接入(JNA)主要是在Java世界和傳統代碼之間起到橋接作用為什麼JNA的存在如此重要呢?JNA可以有效避免重寫傳統代碼同樣JNA還意味著不再需要支付昂貴的橋接方案後者包括代理安排硬編碼專有協議等所有這些解決方案都是有難度且容易出錯的另一個關鍵的JNA要素是它還能有效取代Java本地接口(JNI)
例一中展示了一組筆者將要在本文中尋找的代碼筆者從Windows Kernel DLL中引用了GetTickCount()程序GetTickCount()返回了系統啟動後所產生的毫秒數量
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
NativeloadLibrary((PlatformisWindows() ? kernel : c)
CLibraryclass);
int GetTickCount();
}
public static void main(String[] args) {
Systemoutprintln(TickCount + CLibraryINSTANCEGetTickCount());
}
例
簡單的JNA示例
例一中有趣的一件事情是不再需要JNI代碼相反你只需從Java代碼中調用一個DLL符號不需要映射和自動生成JNI標頭文件使用JNA你只需簡單加載所需庫映射感興趣的符合然後引用這些符號就可以了
總之毫無疑問JNA解決方案可以節約成本它能直接調用傳統代碼從而避免一切使用JNI的請求或是重寫傳統代碼的需要或許JNA最重要的一方面是它具備統一的代碼環境但是其他與JNA相關的事物會干涉本地代碼區域其中之一就是要決定Java是否是所謂的系統語言
Java不是系統語言?
初期外界針對Java提出主要質疑是它並非是一個系統語言與C或C++不同Java存在於JVM之中不能訪問低級別的機器指定型細節信息只有通過高級別的API才能對其進行訪問Java這樣的相對獨立性帶來的一大優點是其安全性也就是說JVM死機的時候整個系統不一定會死機
JNA的出現改變了這一狀況因為現在Java代碼可以訪問C類型機制例二展示了另一個通過Windows kernel DLL函數訪問數據的Java代碼示例
Kernel lib = KernelINSTANCE;
SYSTEMTIME time = new SYSTEMTIME();
libGetSystemTime(time);
Systemoutprintln(Todays integer value is + timewDay);
例二Kerneldll的系統時間
注意在例二中Java代碼對低級別平台的數據進行了訪問因此JNA意味著Java可以進行系統級別的存取但是另一個使用JNA的重要性是對傳統代碼的訪問因為主要的商業價值存在於傳統代碼中例如用C/C++編寫的復雜數學函數也就是說JNA可以起到技術性橋接作用
JNA橋接技術
從上述兩個例子中你可以看出JNA是一項能有效實現Java本地Java 的橋接技術這使得JNA有別於JNI因為不再需要自動生成標頭文件或執行特殊的C代碼相反使用JNA你可以簡單映射需要的庫符號然後再引用它們
現在讓我們再看一個創建DLL的復雜示例然後再用JNA代碼對其進行調用
使用JNA的實例
與單一使用JNA技術然後簡單調用已有DLL不同之處在於你你好要將JNA調用映射到自己的DLL中因此筆者想創建一個真正簡單的DLL然後通過JNA代碼對其進行調用筆者用微軟VisualC++ Express Edition創建了一個DLL當然你也可以使用更新的版本再使用相同的方法例三截取了DLL代碼的重要部分其中大部分是自動生成(下圖為例三 DLL代碼)
BOOL APIENTRY DllMain( HMODULE hModule
DWORD ul_reason_for_call
LPVOID lpReserved)
{
return TRUE;
}
extern C __declspec(dllexport) DWORD helloWorld (DWORD divider)
{
return /divider;
}
不要擔心例三中的細節
其中大部分都是自動生成
其中重要的部分是名為helloWorld()的函數
該函數通過整數參數來傳遞並將其劃分為固定值
顯然
這還不是標准結果
稍後
筆者會使用例三中的代碼安排來模擬其分離過程以便看清楚JNA中發生的一切
讓我們迅速了解helloWorld()函數的要點首先外部C用來避免C++名稱裝飾這意味著函數可以作為helloWorld()從外部引用而不需要對名稱添加特別性能其次_declspec(dllexport)標簽用來從DLL中輸出函數函數定義的剩余部分就是返回值函數名稱和參數
最後要注意的一件事是調用慣例要確保它被設定為_cdecl在Visual C++ Express Edition中你要將調用慣例設定在C++高級部分的項目配置級別
當上述所有步驟都完成後你就可以創建制造DLL的項目了在本文中DLL被稱為nativecodedll下面讓我們通過JNA執行該DLL代碼
從Java中引用DLL代碼
例四展示了引用DLL函數的代碼
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
NativeloadLibrary((PlatformisWindows() ? nativecode : c)
CLibraryclass);
int helloWorld(int divider);
}
public static void main(String[] args) {
CLibraryINSTANCEhelloWorld());
}
在例四中
創建了一個Clibrary實例
該對象允許加載指定的DLL
參考下面的庫加載過程
所需的庫符號被映射了
在例四中
只有一個名為helloWorld()的符號
例五展示了從例四代碼中輸出的程序
C:\jnacode>java HelloWorld
Value is
在例五中沒有什麼令人驚訝的地方——數值被發送到函數中參數()隨後被函數中的分割以生成答案
當筆者嘗試找出與調用慣例相關的DLL問題時發現可以用Dependency Walker工具看到DLL你只需下載免費的Dependency Walker副本打開並往裡面加載DLL與下圖類似
注意上圖中的函數名稱與DLL符號helloWorld()名稱匹配當你創建DLL時如果你使用標准的調用慣例函數名稱看起來就和下圖中的一樣
注意函數名稱改變的方式現在如果你想嘗試運行Java程序你會得到下面的錯誤陳述
C:\jnacode>java HelloWorld
Exception in thread main javalangUnsatisfiedLinkError: Error looking up function helloWorld: The specified procedure could not be found
at comsunjnaFunction(Functionjava:)
at comsunjnaNativeLibrarygetFunction(NativeLibraryjava:)
at comsunjnaLibrary$Handlerinvoke(Libraryjava:)
at $ProxyhelloWorld(Unknown Source)
at HelloWorldmain(HelloWorldjava:)
筆者之所以將這一情況列出來是因為自己也是突然遇到這一情況
因此這完全是一個證明JNA可行的例子
這與JNI有什麼關系呢?
告別JNI
在使用JNI來連接大型Java和C++代碼庫時有時Java和C++代碼會一起被傳送但是每次都造成死機在這樣的情況下Java和C++程序員往往開始互相指責
解決這一紛爭的一項方案是為兩種代碼運行一套清單目錄例如在C代碼中
·數組越界了嗎?
·空的指示針是否被廢棄?
·動態內存是否被正確分配了?
JNA可以結束這一切疑問
庫許可證問題
JNA中有趣的一面在於它為Java訪問DLL打通了一條捷徑這也同樣可以應用於其他庫技術如共享Unix庫這對於授權的軟件組件意味著什麼呢?JNA的使用或許能有效訪問授權庫代碼該問題的另一個方面是JNA代碼可能允許對安全限制庫代碼的訪問
除了上述情況的考慮訪問傳統代碼的能力也為Java和傳統代碼之間的接口例外開辟了其他可能性請看下列代碼
重新調用例三中的代碼如果鍵入並引發 dividebyzero異常會發生什麼?
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
NativeloadLibrary((PlatformisWindows() ? nativecode : c)
CLibraryclass);
int helloWorld(int divider);
}
public static void main(String[] args) {
CLibraryINSTANCEhelloWorld());
Systemoutprintln(Value: + CLibraryINSTANCEhelloWorld());
}
當筆者指定上述代碼時系統給出了下列回復
C:\jna_article\jnacode>java HelloWorld
Value is
#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
# EXCEPTION_INT_DIVIDE_BY_ZERO (xc) at pc=xb pid= tid=
#
# Java VM: Java HotSpot(TM) Client VM (_b mixed mode)
# Problematic frame:
# C [nativecodedll+x]
#
# An error report file with more information is saved as hs_err_pidlog
#
# If you would like to submit a bug report please visit:
#
或許這些並不好看
但是我們確實會獲取一些追蹤輸出
但是將其擴展到真實世界的實例
想象一下如果繼續在大型代碼庫中進行追蹤會怎樣?總之
JNA的功能是很強大的
結語
如果庫代碼的建立方式正確你就不會遇到很多困難JNA對於那些擁有龐大的復雜的商業遺留代碼的公司來說可以提供相當大的幫助記住任何發送到本地庫的數據都要經過仔細驗證
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26050.html