第一種方式,在加載動態連接庫的時候,JVM會調用JNI_OnLoad(JavaVM* jvm, void* reserved)(若是定義了該函數)。第一個參數會傳入JavaVM指針。c++
第二種方式,在native code中調用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)能夠獲得JavaVM指針。jvm
兩種狀況下,均可以用全局變量,好比JavaVM* g_jvm來保存得到的指針以便在任意上下文中使用。函數
Android系統是利用第二種方式Invocation interface來建立JVM的。this
須要強調的是JNIEnv是跟線程相關的。spa
在native method中,JNIEnv做爲第一個參數傳入。那麼在JNIEnv不做爲參數傳入的時候,該如何得到它?線程
JNI提供了兩個函數:設計
(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)指針
(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)code
兩個函數都利用JavaVM接口得到JNIEnv接口,上面已經講到如何得到JavaVM接口。orm
JNI規範也說明,能夠將得到JNIEnv封裝成一個函數。
1
2
3
4
5
6
|
JNIEnv* JNU_GetEnv()
{
JNIEnv* env;
(*g_jvm)->GetEnv(g_jvm, (
void
**)&env, JNI_VERSION_1_2);
return
env;
}
|
Java經過JNI機制調用c/c++寫的native程序。c/c++開發的native程序須要遵循必定的JNI規範,下面的例子就是一個JNI函數聲明:
JNIEXPORT jint JNICALL Java_jnitest_MyTest_test
(JNIEnv * env, jobject obj, jint arg0);
JVM負責從Java Stack轉入C/C++ Native Stack。當Java進入JNI調用,除了函數自己的參數(arg0),會多出兩個參數:JNIEnv指針和jobject指針。
JNIEnv指針是JVM建立的,用於Native的c/c++方法操縱Java執行棧中的數據,好比Java Class, Java Method等。
首先,JNI對於JNIEnv的使用, 提供了兩種語法: c語法以及c++語法,以下:
c語法:
jsize len = (*env)->GetArrayLength(env,array);
c++語法:
jsize len =env->GetArrayLength(array);
(注:因爲C語言並不支持對象的概念,因此C語法中須要把env做爲第一個參數傳入,相似於C++的隱式參數this指針).
對於JNIEnv *env來講,在C中調用:
(*env)->NewStringUTF(env, "Hello from JNI!");
而在C++中若是按照上述調用則會發生'base operand of '->' has non-pointer type '_JNIEnv''錯誤,須要以下調用:
env->NewStringUTF("Hello from JNI!");
緣由:參見jni.h中對於JNIEnv的定義:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
#else
typedef const struct JNINativeInterface* JNIEnv;
#endif
另外: JNIEnv有幾個設計的原則:
第1、JNIEnv指針被設計成了Thread Local Storage(TLS)變量,也就是說每個Thread, JNIEnv變量都有獨立的Copy。你不能把Thead#1使用的JNIEnv傳給Thread#2使用。
第 2、JNIEnv中定義了一組函數指針,c/c++ Native程序是經過這些函數指針操縱Java數據。這樣設計的好處是:你的c/c++ 程序不須要依賴任何函數庫,或者DLL。因爲JVM可能由不一樣的廠商實現,不一樣廠商有本身不一樣的JNI實現,若是要求這些廠商暴露約定好的一些頭文件和 庫,這不是靈活的設計。
並且使用函數指針表的另一個好處是: JVM能夠根據啓動參數動態替換JNI實現。