詳解java 本地接口 jni 使用方法
對于java程序員來說,java語言的好處和優點,我想不用我說了,大家自然會說出很多一套套的。但雖然我們作為java程序員,但我們不得不承認java語言也有一些它本身的缺點。比如在性能、和底層打交道方面都有它的缺點。所以java就提供了一些本地接口,他主要的作用就是提供一個標準的方式讓java程序通過虛擬機與原生代碼進行交互,這也就是我們平常常說的java本地接口(jni——java native interface)。它使得在 java 虛擬機 (vm) 內部運行的 java 代碼能夠與用其它編程語言(如 c、c++ 和匯編語言)編寫的應用程序和庫進行互操作。jni 最重要的好處是它沒有對底層 java 虛擬機的實現施加任何限制。因此,java虛擬機廠商可以在不影響虛擬機其它部分的情況下添加對 jni 的支持。程序員只需編寫一種版本的本地應用程序或庫,就能夠與所有支持 jni 的 java 虛擬機協同工作。我們來看一下為什么要與原生代碼進行交互:
一:提高應用程序性能。我們知道java對于c/c++、匯編語言來說,顯得比較“高級”。其實這里的高級就是簡化了程序員的工作。很多底層的東西都讓java虛擬機做了。但畢竟相對于直接訪問底層來講,java多了一步虛擬機的過程,所以在性能上比著這些原生語言稍微有點慢。
二:實現一些與底層相關的功能。java平臺提供的標準類庫,還有強大的api,雖然能完成大部分功能。但有些和底層硬件打交道的功能在java api提供的類庫中還是無法完成。
三:與已有的使用原生代碼編寫的程序進行集成。在于操作系統上由c或者c++等原生語言編寫的軟件進行集 0成的時候,可以用jni。
jni 接口函數和指針
平臺相關代碼是通過調用 jni 函數來訪問 java 虛擬機功能的。jni 函數可通過接口指針來獲得。接口指針是指針的指針,它指向一個指針數組,而指針數組中的每個元素又指向一個接口函數。每個接口函數都處在數組的某個預定偏移量中。下圖說明了接口指針的組織結構。
jni 接口的組織類似于 c++ 虛擬函數表或 com 接口。使用接口表而不使用硬性編入的函數表的好處是使 jni 名字空間與平臺相關代碼分開。虛擬機可以很容易地提供多個版本的 jni 函數表。例如,虛擬機可支持以下兩個 jni 函數表:
· 一個表對非法參數進行全面檢查,適用于調試程序;
· 另一個表只進行 jni 規范所要求的最小程度的檢查,因此效率較高。
jni 接口指針只在當前線程中有效。因此,本地方法不能將接口指針從一個線程傳遞到另一個線程中。實現 jni 的虛擬機可將本地線程的數據分配和儲存在 jni 接口指針所指向的區域中。
本地方法將jni 接口指針當作參數來接受。虛擬機在從相同的 java 線程中對本地方法進行多次調用時,保證傳遞給該本地方法的接口指針是相同的。但是,一個本地方法可被不同的 java 線程所調用,因此可以接受不同的 jni 接口指針。
(1)編寫java類代碼
其中,需要jni實現的方法應當用native關鍵字聲明。在該類中,用system.1oadlibrary()方法加載需要的動態鏈接庫。關鍵代碼如下:
1
2
3
4
5
6
7
|
//compute.java public class compute{ public native double sqrt( double params); static { //調用動態鏈接庫 system.loadlibrary(“compute”); } |
(2)編譯成字節代碼
在這個過程中,由于采用了native關鍵字聲明,java編譯器會忽視沒有代碼體的jni方法部分。
(3)生成相關jni方法的頭文件
這個過程的實現一般是通過利用jlavah-jni * class生成的(-jni可以省略),也可以手工生成該文件;但是由于 java 虛擬機是根據一定的命名規范完成對jni方法的調用,所以手工編寫頭文件需要特別小心。
上述文件產生的頭文件部分代碼如下:
1
2
3
|
//compute.h extern“c”{ jniexport jdoublejnicall java_compute_comp(jni-env *, jobject, jdoublearray); |
jni函數名稱分為三部分:首先是java關鍵字,供java虛擬機識別;然后是調用者類名稱(全限定的類名,其中用下劃線代替名稱分隔符);最后是對應的方法名稱,各段名稱之間用下劃線分割。
jni函數的參數也由三部分組成:首先是jnienv *,是一個指向jni運行環境的指針;第二個參數隨本地方法是靜態還是非靜態而有所不同一一非靜態本地方法的第二個參數是對對象的引用,而靜態本地方法的第二個參數是對其java類的引用;其余的參數對應通常java方法的參數,參數類型需要根據一定規則進行映射。
(4)編寫相應方法的實現代碼
在編碼過程中,需要注意變量的長度問題,例如java的整型變量長度為32位,而c語言為16位,所以要仔細核對變量類型映射表,防止在傳值過程中出現問題。
(5)將jni實現代碼編譯成動態鏈接庫
編譯過程是利用c/c++編譯器實現的,在windows平臺上,編譯和連接的結果是動態鏈接庫dll文件。當要使用生成的動態鏈接庫時,調用者類中需要顯式調用該鏈接庫dll文件。
經過上述處理,基本上完成了一個包含本地化方法的java類的開發。
附錄:將jav類型映射到本地 c 類型
基本類型和本地等效類型 |
||
java 類型 |
本地類型 |
說明 |
boolean |
jboolean |
無符號,8 位 |
byte |
jbyte |
無符號,8 位 |
char |
jchar |
無符號,16 位 |
short |
jshort |
有符號,16 位 |
int |
jint |
有符號,32 位 |
long |
jlong |
有符號,64 位 |
float |
jfloat |
32 位 |
double |
jdouble |
64 位 |
void |
void |
n/a |
為了使用方便,特提供以下定義。
1
2
|
#define jni_false 0 #define jni_true 1 |
jsize 整數類型用于描述主要指數和大小:
1
|
typedef jint jsize; |
故障排除
當使用 jni 從 java 程序訪問本機代碼時,您會遇到許多問題。您會遇到的三個最常見的錯誤是:
1)無法找到動態鏈接。它所產生的錯誤消息是:java.lang.unsatisfiedlinkerror。這通常指無法找到共享庫,或者無法找到共享庫內特定的本機方法。
2)無法找到共享庫文件。當用 system.loadlibrary(string libname) 方法(參數是文件名)裝入庫文件時,請確保文件名拼寫正確以及沒有指定擴展名。還有,確保庫文件的位置在類路徑中,從而確保 jvm 可以訪問該庫文件。
3)無法找到具有指定說明的方法。確保您的 c/c++ 函數實現擁有與頭文件中的函數說明相同的說明。
結束語
從 java 調用 c 或 c++ 本機代碼(雖然不簡單)是 java 平臺中一種良好集成的功能。雖然 jni 支持 c 和 c++,但 c++ 接口更清晰一些并且通常比 c 接口更可取。正如您已經看到的,調用 c 或 c++ 本機代碼需要賦予函數特殊的名稱,并創建共享庫文件。當利用現有代碼庫時,更改代碼通常是不可取的。要避免這一點,在 c++ 中,通常創建代理代碼或代理類,它們有專門的 jni 所需的命名函數。然后,這些函數可以調用底層庫函數,這些庫函數的說明和實現保持不變。
如有疑問請留言或者到本站社區交流討論,感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
原文鏈接:http://blog.csdn.net/qq_37267015/article/details/77303382