對於java程序員來講,java語言的好處和優勢,我想不用我說了,你們天然會說出不少一套套的。但雖然咱們做爲java程序員,但咱們不得不認可java語言也有一些它自己的缺點。好比在性能、和底層打交道方面都有它的缺點。因此java就提供了一些本地接口,他主要的做用就是提供一個標準的方式讓java程序經過虛擬機與原生代碼進行交互,這也就是咱們日常常說的java本地接口(JNI——java native Interface)。它使得在 Java 虛擬機(VM) 內部運行的Java 代碼可以與用其它編程語言(如 C、C++ 和彙編語言)編寫的應用程序和庫進行互操做。JNI 最重要的好處是它沒有對底層Java 虛擬機的實現施加任何限制。所以,Java虛擬機廠商能夠在不影響虛擬機其它部分的狀況下添加對JNI 的支持。程序員只需編寫一種版本的本地應用程序或庫,就可以與全部支持JNI 的Java 虛擬機協同工做。咱們來看一下爲何要與原生代碼進行交互:java
一:提升應用程序性能。咱們知道java對於c/c++、彙編語言來講,顯得比較「高級」。其實這裏的高級就是簡化了程序員的工做。不少底層的東西都讓java虛擬機作了。但畢竟相對於直接訪問底層來說,java多了一步虛擬機的過程,因此在性能上比着這些原生語言稍微有點慢。c++
二:實現一些與底層相關的功能。Java平臺提供的標準類庫,還有強大的API,雖然能完成大部分功能。但有些和底層硬件打交道的功能在java API提供的類庫中仍是沒法完成。程序員
三:與已有的使用原生代碼編寫的程序進行集成。在於操做系統上由c或者c++等原生語言編寫的軟件進行集成的時候,能夠用JNI。編程
JNI 接口函數和指針windows
平臺相關代碼是經過調用 JNI 函數來訪問Java 虛擬機功能的。JNI 函數可經過接口指針來得到。接口指針是指針的指針,它指向一個指針數組,而指針數組中的每一個元素又指向一個接口函數。每一個接口函數都處在數組的某個預約偏移量中。下圖說明了接口指針的組織結構。數組
JNI 接口的組織相似於C++ 虛擬函數表或COM 接口。使用接口表而不使用硬性編入的函數表的好處是使JNI 名字空間與平臺相關代碼分開。虛擬機能夠很容易地提供多個版本的JNI 函數表。例如,虛擬機可支持如下兩個JNI 函數表:編程語言
1)一個表對非法參數進行全面檢查,適用於調試程序;函數
2)另外一個表只進行 JNI 規範所要求的最小程度的檢查,所以效率較高。性能
JNI 接口指針只在當前線程中有效。所以,本地方法不能將接口指針從一個線程傳遞到另外一個線程中。實現JNI 的虛擬機可將本地線程的數據分配和儲存在JNI 接口指針所指向的區域中。編碼
本地方法將JNI 接口指針看成參數來接受。虛擬機在從相同的 Java 線程中對本地方法進行屢次調用時,保證傳遞給該本地方法的接口指針是相同的。可是,一個本地方法可被不一樣的Java 線程所調用,所以能夠接受不一樣的JNI 接口指針。
1)編寫Java類代碼
其中,須要JNI實現的方法應當用native關鍵字聲明,在該類中,用System.loadLibrary()方法加載須要的動態連接庫,關鍵代碼以下:
//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方法的調用,因此手工編寫頭文件須要特別當心。
上述文件產生的頭文件部分代碼以下:
//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 類型
爲了使用方便,特提供如下定義。
#define JNI_FALSE 0
#define JNI_TRUE 1
jsize 整數類型用於描述主要指數和大小:
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 所需的命名函數。而後,這些函數能夠調用底層庫函數,這些庫函數的說明和實現保持不變。