使用 JNI 時容易出錯的地方相關總結

把 jclass 和 jobject 弄混
一開始使用 JNI 時,很容易把對象引用(jobject 類型的值)和類引用(jclass 類型的值)弄混。
對象引用對應的是數組或者 java.lang.Object 及其子類的對象實例,而類引用對應的是java.lang.Class 的實例。
像 GetFieldID 這樣須要傳入 jclass 做爲參數的方法作的是一個類操做,由於它是從一個類中獲取字段的描述。
而 GetIntField 這樣須要傳入 jobject 做爲參數的方法作的是一個對象操做,由於它從一個對象實例中獲取字段的值。java

 

混淆 ID 和引用
本地代碼中使用引用來訪問 JAVA 對象,使用 ID 來訪問方法和字段。
引用指向的是能夠由本地代碼來管理的 JVM 中的資源。好比 DeleteLocalRef 這個本地函數,容許本地代碼刪除一個局部引用。
而字段和方法的ID由JVM來管理,只有它所屬的類被 unload時,纔會失效。
本地代碼不能顯式在刪掉一個字段或者方法的 ID。
本地代碼能夠建立多個引用並讓它們指向相同的對象。好比,一個全局引用和一個局部引用可能指向相同的對象。
而字段 ID 和方法 ID 是惟一的。
好比類 A定義了一個方法 f,而類 B從類 A中繼承了方法 f,那麼下面的調用結果是相同的:
jmethodID MID_A_f = (*env)->GetMethodID(env, A, "f", "()V");
jmethodID MID_B_f = (*env)->GetMethodID(env, B, "f", "()V");數組

 

訪問權限失效
在本地代碼中,訪問方法和變量時不受 JAVA 語言規定的限制。好比,能夠修改 private 和 final修飾的字段。
而且,JNI 中能夠訪問和修改 heap 中任意位置的內存。這些都會形成意想不到的結果。
好比,本地代碼中不該該修改 java.lang.String 和 java.lang.Integer 這樣的不可變對象的內容。不然,會破壞 JAVA 規範。安全

 

忽視國際化
JVM 中的字符串是 Unicode 字符序列,而本地字符串採用的是本地化的編碼。實際編碼的時候,
咱們常常須要使用像 JNU_NewStringNative 和 JNU_GetStringNativeChars 這樣的工具函數來
把 Unicode 編碼的 jstring 轉化成本地字符串,要對消息和文件名尤爲關注,它們常常是須要國際化的,可能包含各類字符。
若是一個本地方法獲得了一個文件名,必須把它轉化成本地字符串以後才能傳遞給 C 庫函數使用:函數

複製代碼

JNIEXPORT jint JNICALL ava_MyFile_open(JNIEnv *env, jobject self, jstring name, jint mode) 
 { 
     jint result; 
     char *cname = JNU_GetStringNativeChars(env, name); 
     if (cname == NULL) { 
         return 0; 
     } 
     result = open(cname, mode); 
     free(cname); 
     return result; 
 }

複製代碼

上例中,咱們使用 JNU_GetStringNativeChars 把Unicode字符串轉化成本地字符串。工具

 

jboolean 會面臨數據截取的問題 
Jboolean是一個8-bit unsigned的C類型,能夠存儲0~255的值。其中,0對應常量JNI_FALSE,
而 1~255 對應常量 JNI_TRUE。可是,32 或者 16 位的值,若是最低的 8 位是 0 的話,就會引
起問題。
假設你定義了一個函數 print,須要傳入一個 jboolean 類型的 condition 做爲參數:
void print(jboolean condition)
 {
   /* C compilers generate code that truncates condition
       to its lower 8 bits. */
     if (condition) {
         printf("true\n");
     } else {
         printf("false\n");
     }
 }
 對上面這段代碼來講,下面這樣用就會出現問題:
int n = 256; /* the value 0x100, whose lower 8 bits are all 0 */
 print(n);
咱們傳入了一個非 0 的值 256(0X100),由於這個值的低 8 位(即,0)被截出來使用,上面
的代碼會打印「false」。
根據經驗,這裏有一個經常使用的解決方案:
n = 256;
print (n ? JNI_TRUE : JNI_FALSE);優化

 

跨進程使用 JNIEnv
JNIEnv 這個指針只能在當前線程中使用,不要在其它線程中使用。編碼

 

傳遞數據
像 int、char 等這樣的基本數據類型,在本地代碼和 JVM 之間進行復制傳遞,而對象是引用傳遞的。
每個引用都包含一個指向 JVM 中相應的對象的指針,但本地代碼不能直接使用這個指針,必須經過引用來間接使用。
比起傳遞直接指針來講,傳遞引用可讓 VM 更靈活地管理對象。
好比,你在本地代碼中抓着一個引用的時候,VM那小子可能這個時候正偷偷摸摸地把這個引用間接指向的那個對象從一起內存區域給挪到另外一塊兒。
不過,有一點兒你放心,VM 是不敢動對象裏面的內容的,由於引用的有效性它要負責。
瞅一下下圖,你就會得道了。spa

 

這裏有一些經驗性的注意事項:
一、 儘可能讓 JAVA 和 C 之間的接口簡單化,C 和 JAVA 間的調用過於複雜的話,會使得 BUG 調試、代碼維護和 JVM 對代碼進行優化都會變得很難。好比虛擬機很容易對一些 JAVA 方法進行內聯,但對本地方法卻無能爲力。
二、 儘可能少寫本地代碼。由於本地代碼即不安全又是不可移植的,並且本地代碼中的錯誤檢查很麻煩。
三、 讓本地代碼儘可能獨立。也就是說,實際使用的時候,儘可能讓全部的本地方法都在同一個包甚至同一個類中。線程

相關文章
相關標籤/搜索