日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

JNI 對象在函數(shù)調(diào)用中的生命周期

 weicat 2010-01-27

問題

在 JNI 編程中常需要從一個普通的 C/C++ 函數(shù)中調(diào)用 JNI 方法,比如:

long  ProcessKeyboardEventInJava(int keyboardEvent) {	     long    error = 0;     JNIEnv*    env = g_env;     jclass    that = g_class;     jmethodID    mid = g_mid;     …	…	     error  = env->CallStaticIntMethod(that, mid, keyboardEvent);      return error; } 

ProcessKeyboardEventInJava 只是個普通的 C 函數(shù),目的是把參數(shù)中的鍵盤事件交給 Java 代碼處理。它并沒有 JNI 參數(shù),卻需要調(diào)用一個 JNI 方法。那么調(diào)用所必需的參數(shù) JNIEnv,jclass 和 jmethodID,是如何獲得呢?從上面的代碼中可以看出,它們來自保存的全局變量 g_env,g_class 和 g_mid。這樣做對么?安全么?也許這段代碼在某種情況下,實現(xiàn)了軟件的需求,成功地執(zhí)行了 Java 的函數(shù),處理了這個鍵盤事件。在這種情況下,設(shè)計者很清楚,也深信 ProcessKeyboardEventInJava 只會在某個 Native 函數(shù)返回前被調(diào)用。對應(yīng)的場景代碼應(yīng)該是這樣的:

JNIEnv * g_env; jclass g_class; jmethodID g_mid;  JNIEXPORT jint JNICALL Java_SampleTest_init (JNIEnv *env, jclass cls) {	 	     g_class = cls;     g_env = env;	     g_mid = env->GetStaticMethodID(cls, "ProcessKeyboardEventInJava", "(III)I");     while(true)     {         ….         long error;         error = ProcessKeyboardEventInJava(theEvent)         ….     }  } 			

但萬一 ProcessKeyboardEventInJava 在 Java_SampleTest_init(以下簡稱 init)返回后的其他時機(jī)被調(diào)用了,會怎樣呢?那時可就沒有這么幸運(yùn)了,初衷是肯定達(dá)不到了,而發(fā)生 crash 或者 segmentation fault 這類錯誤則幾乎是一定的。事實上,如上設(shè)計,ProcessKeyboardEventInJava 也只允許在 init 函數(shù)中被調(diào)用。在其返回后被調(diào)用,就不合法了。所以,如果你是這個設(shè)計師,之前的設(shè)計中,是不是這個假設(shè)也有些太樂觀了呢?





回頁首


分析

調(diào)用者本來以為這個函數(shù)能幫他的大忙,現(xiàn)在卻疑惑了,到底發(fā)生了什么?做錯了什么呢?

原因其實很簡單,上面三個全局變量中的 g_class 已經(jīng)是非法的了,生命周期在退出 init 之后就結(jié)束了。

這里要澄清的是 g_mid,由于它不是一個 jobject,所以只要它對應(yīng)的 class 沒有被卸載,在退出 init 之后仍可以使用,沒問題;而 g_env 對于同一個 thread 來說,它是唯一的,所以只要是 init 和 ProcessKeyboardEventInJava 處于一個 thread,初始化后它的值在這個 thread 沒有中止之前,都一直是合法的。

那如何才能解決 g_class 的非法引用帶來的問題呢?

這首先涉及到 Java 和 Native 代碼之間函數(shù)調(diào)用時,參數(shù)如何傳遞的問題。簡單類型,也就是內(nèi)置類型,比如 int, char 等是值傳遞(pass by value),而其它 Java 對象都是引用傳遞(pass by reference),這些對象引用由 JVM 傳給 Native 代碼,每個都有其生命周期。

其次,Java 對象的生命周期是由它的引用類型決定的,這里的引用分兩種:local reference 和 global reference。Native 函數(shù)參數(shù)中 jobject 或者它的子類,其參數(shù)都是 local reference。Local reference 只在這個 Native 函數(shù)中有效,Native 函數(shù)返回后,引用的對象就被釋放,它的生命周期就結(jié)束了。若要留著日后使用,則需根據(jù)這個 local reference 創(chuàng)建 global reference。Global reference 不會被系統(tǒng)自動釋放,它僅當(dāng)被程序明確調(diào)用 DeleteGlobalReference 時才被回收。





回頁首


解決

于是解決的辦法就出來了:

在 init 函數(shù)中,不是簡單地把 jcalss 參數(shù)保存,而是:

JNIEXPORT jint JNICALL Java_SampleTest_init (JNIEnv *env, jclass cls) {      ….     g_class= (jclass)(env->NewGlobalRef(cls));     …. } 			

這樣,無論 ProcessKeyboardEventInJava 是在 init 返回前還是返回后,調(diào)用它都是安全的,可行的了。





回頁首


總結(jié)

若要在某個 Native 代碼返回后,還希望能繼續(xù)使用 JVM 提供的參數(shù)(比如 init 函數(shù)中的 jclass), 或者是過程中調(diào)用 JNI 函數(shù)的返回值(比如 g_mid),只要它是一個 jobject, 則需要為它創(chuàng)建一個 global reference,以后只能使用這個 global reference;若不是一個 jobject,則無需這么做。

jclass 是由 jobject public 繼承而來的子類,所以它當(dāng)然是一個 jobject,需要創(chuàng)建一個 global reference 以便日后使用。而 jmethodID/jfieldID 與 jobject 沒有繼承關(guān)系,它不是一個 jobject,只是個整數(shù),所以不存在被釋放與否的問題,可保存后直接使用。JNIEnv 對于每個 thread 而言是唯一的,不能也不需要對它調(diào)用 NewGlobalReference。



參考資料

學(xué)習(xí)

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多