使用 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 方法进行内联,但对本地方法却无能为力。
二、 尽可能少写本地代码。由于本地代码即不安全又是不可移植的,并且本地代码中的错误检查很麻烦。
三、 让本地代码尽可能独立。也就是说,实际使用的时候,尽可能让全部的本地方法都在同一个包甚至同一个类中。线程

相关文章
相关标签/搜索