未检测返回值
许多 JNI 方法都通过返回值来指示调用成功与否。与未检测异常相似,这也存在一个缺陷,即代码未检测返回值却假定调用成功而继续运行。对于大多数 JNI 方法来说,它们都设置了返回值和异常状态,这样应用程序更可以通过检测异常状态或返回值来判断方法运行正常与否。
您可以确定以下代码的问题吗?
clazz = (*env)->FindClass(env, "com/ibm/j9//HelloWorld"); method = (*env)->GetStaticMethodID(env, clazz, "main", "([Ljava/lang/String;)V"); (*env)->CallStaticVoidMethod(env, clazz, method, NULL); |
问题在于,如果未发现 HelloWorld 类,或者如果 main() 不存在,则本机将造成程序崩溃。
未正确使用数组方法
GetXXXArrayElements() 和 ReleaseXXXArrayElements() 方法允许您请求任何元素。同样,GetPrimitiveArrayCritical()、ReleasePrimitiveArrayCritical()、GetStringCritical() 和 ReleaseStringCritical() 允许您请求数组元素或字符串字节,以最大限度降低直接指向数组或字符串的可能性。这些方法的使用存在两个常见的缺陷。其一,忘记在 ReleaseXXX() 方法调用中提供更改。即便使用 Critical 版本,也无法保证您能获得对数组或字符串的直接引用。一些 JVM 始终返回一个副本,并且在这些 JVM 中,如果您在 ReleaseXXX() 调用中指定了 JNI_ABORT,或者忘记调用了 ReleaseXXX(),则对数组的更改不会被复制回去。
举例来说,考虑以下代码:
void modifyArrayWithoutRelease(JNIEnv* env, jobject obj, jarray arr1) { jboolean isCopy; jbyte* buffer = (*env)-> (*env)->GetByteArrayElements(env,arr1,&isCopy); if ((*env)->ExceptionCheck(env)) return; buffer[0] = 1; } |
在提供直接指向数组的指针的 JVM 上,该数组将被更新;但是,在返回副本的 JVM 上则不是如此。这会造成您的代码在一些 JVM 上能够正常运行,而在其他 JVM 上却会出错。您应该始终始终包括一个释放(release)调用,如清单 12 所示:
清单 12. 包括一个释放调用
void modifyArrayWithRelease(JNIEnv* env, jobject obj, jarray arr1) { jboolean isCopy; jbyte* buffer = (*env)-> (*env)->GetByteArrayElements(env,arr1,&isCopy); if ((*env)->ExceptionCheck(env)) return; buffer[0] = 1; (*env)->ReleaseByteArrayElements(env, arr1, buffer, JNI_COMMIT); if ((*env)->ExceptionCheck(env)) return; } |
第二个缺陷是不注重规范对在 GetXXXCritical() 和 ReleaseXXXCritical() 之间执行的代码施加的限制。本机可能不会在这些方法之间发起任何调用,并且可能不会由于任何原因而阻塞。未重视这些限制会造成应用程序或 JVM 中出现间断性死锁。
举例来说,以下代码看上去可能没有问题:
void workOnPrimitiveArray(JNIEnv* env, jobject obj, jarray arr1) { jboolean isCopy; jbyte* buffer = (*env)->GetPrimitiveArrayCritical(env, arr1, &isCopy); if ((*env)->ExceptionCheck(env)) return; processBufferHelper(buffer); (*env)->ReleasePrimitiveArrayCritical(env, arr1, buffer, 0); if ((*env)->ExceptionCheck(env)) return; } |