當一個程式無法完全使用Java編寫時,開發人員可以通過JNI來編寫本地方法,比如標準Java類庫並不支援的依賴於平台的特色或者程式庫。JNI還可以用於修改現有的使用其它語言編寫的程式,使它們可以通過Java編寫的程式來訪問。
很多基本類庫都依賴JNI來為開發人員和使用者提供服務,比如檔案的輸入/輸出和音頻功能。在基本類庫中包含的對於效能和平台敏感的API可以允許所有的Java程式以安全和平台無關的方式來使用這些功能,在採用JNI之前,開發人員需要明確這些功能並不是已經包含在Java標準類庫中的,在這篇文章中,我將會講解JNI是如何工作的以及本地類型是如何映射到Java的類型和類庫的。
JNI工作原理
在JNI中,本地函數是通過一個獨立的.c或.cpp檔案來實現的(C++為JNI提供的介面會更簡潔一些)。當JVM調用該函數時,它傳遞了一個JNIEnv指標、一個jobject指標和通過Java方法定義的Java參數,JNI函數的形式如下:
JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobjectobj)
{
//Method native implemenation
}
env指標是一個包含了JVM介面的結構,它包含了與JVM進行互動以及與Java對象協同工作所必需的函數,樣本中的JNI函數可以在本地數組和Java數群組類型之間、本地字串和Java字串類型之間進行轉換,其功能還包括對象的執行個體化、拋出異常等。基本上您可以使用JNIEnv來實現所有Java能做到的事情,雖然要簡單很多。
更加正式的解釋是這樣的,本地代碼通過調用JNI的函數來訪問JVM,這是通過一個介面指標實現的(介面指標實際上是指向指標的指標),該指標指向一個指標數組,數組中的每個指標都指向了一個介面函數,而每個介面函數都是在數組中預先定義過的。
本地方法將JNI介面指標當作一個參數,如果在同一個Java線程中,出現對該本地方法的多重調用,JVM則保證傳遞相同的介面指標到本地方法。不過,一個本地方法可以被不同的Java線程調用,因而也可能會收到不同的JNI介面指標。
本地方法是通過System.loadLibrary方法載入的,在以下的例子中,類的初始化方法載入了一個指定平台的本地類庫,該類庫定義了本地方法:
packagepkg; class Cls {
native double f(inti, String s);
static {
system.loadLibrary(pkg_Cls");
}
}
system.loadLibrary方法的參數是一個類庫的名稱,它可以由程式員任意選取,系統則遵循一個標準的本地化平台的方式來轉換類庫的名稱到一個本地類庫的名稱。例如,在Solaris作業系統中會將pkg_Cls轉換為libpkg_Cls.so,而Win32系統則會將同樣的pkg_Cls轉換為pkg_Cls.dll。
動態指標會根據它們的名字來進行解析,一個本地方法的名稱是按照組件進行串連的,它包含了:首碼“Java_”、一個分離的合法的類名稱和一個分離的方法名稱。
注意:微軟的JVM有相同的機制從Java調用本地Windows代碼,該機制被稱為原始本地介面(Raw Native Interface (RNI))。