在 JNI 編程中常需要從一個普通的 C/C++ 函數(shù)中調(diào)用 JNI 方法,比如:
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)該是這樣的:
但萬一 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ù)保存,而是:
這樣,無論 ProcessKeyboardEventInJava 是在 init 返回前還是返回后,調(diào)用它都是安全的,可行的了。
若要在某個 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í) |
|