内容发布更新时间 : 2024/12/22 19:51:41星期一 下面是文章的全部内容请认真阅读。
20. FID_Window_handle =
21. (*env)->GetFieldID(env, classWindow, \22. if (FID_Window_handle == NULL) { /* important check. */ 23. return; /* error occurred. */ 24. }
25. FID_Window_length =
26. (*env)->GetFieldID(env, classWindow, \27. if (FID_Window_length == NULL) { /* important check. */ 28. return; /* error occurred. */ 29. }
30. FID_Window_width =
31. (*env)->GetFieldID(env, classWindow, \32. /* no checks necessary; we are about to return anyway */ 33. } 第二种方式:
public class Fraction {
// details such as constructors omitted int over, under; public int floor() {
return Math.floor((double)over/under); } }
/* Native code that calls Fraction.floor. Assume method ID MID_Fraction_floor has been initialized elsewhere. */ void f(JNIEnv *env, jobject fraction) {
jint floor = (*env)->CallIntMethod(env, fraction,
MID_Fraction_floor); /* important: check if an exception was raised */ if ((*env)->ExceptionCheck(env)) { return; }
... /* use floor */ }
当一个JNI函数返回一个明确的错误码时,你仍然可以用ExceptionCheck来检查是否有异常发生。但是,用返回的错误码来判断比较高效。一旦JNI函数的返回值是一个错误码,那么接下来调用ExceptionCheck肯定会返回JNI_TRUE。
6.2.2 异常处理
本地代码通常有两种方式来处理一个异常:
1、 一旦发生异常,立即返回,让调用者处理这个异常。
2、 通过ExceptionClear清除异常,然后执行自己的异常处理代
码。
当一个异常发生后,必须先检查、处理、清除异常后再做其它JNI函数调用,否则的话,结果未知。当前线程中有异常的时候,你可以调用的JNI函数非常少,11.8.2节列出了这些JNI函数的详细列表。通常来说,当有一个未处理的异常时,你只可以调用两种JNI函数:异常处理函数和清除VM资源的函数。 当异常发生时,释放资源是一件很重要的事,下面的例子中,调用GetStringChars函数后,如果后面的代码发生异常,不要忘了调用ReleaseStringChars释放资源。 JNIEXPORT void JNICALL
Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr) {
const jchar *cstr = (*env)->GetStringChars(env, jstr); if (c_str == NULL) { return; } ...
if (...) { /* exception occurred */
(*env)->ReleaseStringChars(env, jstr, cstr); return; } ...
/* normal return */
(*env)->ReleaseStringChars(env, jstr, cstr); }
6.2.3 工具函数中的异常
程序员编写工具函数时,一定要把工具函数内部分发生的异常传播到调用它的方法中去。这里有两个需要注意的地方:
1、 对调用者来说,工具函数提供一个错误返回码比简单地把异常
传播过去更方便一些。
2、 工具函数在发生异常时尤其需要注意管理局部引用的方式。
为了说明这两点,我们写了一个工具函数,这个工具函数根据对象实例方法的名字和描述符做一些方法回调。 ? jvalue ? JNU_CallMethodByName(JNIEnv *env, ? jboolean *hasException, ? jobject obj, ? const char *name, ? const char *descriptor, ...) ? { ? va_list args; ? jclass clazz; ? jmethodID mid; ? jvalue result;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
if ((*env)->EnsureLocalCapacity(env, 2) == JNI_OK) { clazz = (*env)->GetObjectClass(env, obj); mid = (*env)->GetMethodID(env, clazz, name, descriptor); if (mid) {
const char *p = descriptor;
/* skip over argument types to find out the return type */
while (*p != ')') p++; /* skip ')' */ p++;
va_start(args, descriptor); switch (*p) { case 'V':
(*env)->CallVoidMethodV(env, obj, mid, args); break; case '[': case 'L':
result.l = (*env)->CallObjectMethodV(
env, obj, mid, args); break; case 'Z':
result.z = (*env)->CallBooleanMethodV(
env, obj, mid, args); break; case 'B':
result.b = (*env)->CallByteMethodV(
env, obj, mid, args); break; case 'C':
result.c = (*env)->CallCharMethodV(
env, obj, mid, args); break; case 'S':
result.s = (*env)->CallShortMethodV(
env, obj, mid, args); break; case 'I':
result.i = (*env)->CallIntMethodV(
env, obj, mid, args); break; case 'J':
result.j = (*env)->CallLongMethodV(
env, obj, mid, args);
break; ? case 'F': ? result.f = (*env)->CallFloatMethodV( ? env, obj, mid, args); ? break; ? case 'D': ? result.d = (*env)->CallDoubleMethodV( ? env, obj, mid, args); ? break; ? default: ? (*env)->FatalError(env, \descriptor\? } ? va_end(args); ? } ? (*env)->DeleteLocalRef(env, clazz); ? } ? if (hasException) { ? *hasException = (*env)->ExceptionCheck(env); ? } ? return result; ? }
JNU_CallMethodByName的参数当中有一个jboolean指针,如果函数执行成功的话,指针指向的值会被设置为JNI_TRUE,如果有异常发生的话,会被设置成JNI_FALSE。这就可以让调用者方便地检查异常。
JNU_CallMethodByName首先通过EnsureLocalCapacity来确保可以创建两个局部引用,一个类引用,一个返回值。接下来,它从对象中获取类引用并查找方法ID。根据返回类型,switch语句调用相应的JNI方法调用函数。回调过程完成后,如果hasException不是NULL,我们调用ExceptionCheck检查异常。 函数ExceptionCheck和ExceptionOccurred非常相似,不同的地方是,当有异常发生时,ExceptionCheck不会返回一个指向异常对象的引用,而是返回
JNI_TRUE,没有异常时,返回JNI_FALSE。而ExceptionCheck这个函数不会返回一个指向异常对象的引用,它只简单地告诉本地代码是否有异常发生。上面的代码如果使用ExceptionOccurred的话,应该这么写: ? if (hasException) { ? jthrowable exc = (*env)->ExceptionOccurred(env); ? *hasException = exc != NULL; ? (*env)->DeleteLocalRef(env, exc); }
为了删除指向异常对象的局部引用,DeleteLocalRef方法必须被调用。 使用JNU_CallMethodByName这个工具函数,我们可以重写Instance-MethodCall.nativeMethod方法的实现: ? JNIEXPORT void JNICALL ? Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
?
{ ? printf(\? JNU_CallMethodByName(env, NULL, obj, \? }
调用JNU_CallMethodByName函数后,我们不需要检查异常,因为本地方法后面会立即返回。
第七章 调用接口(invocation interface)
本章讲述如何把一个JVM嵌入到你的本地程序当中去。一个JVM可以看作就是一个本地库。本地程序可以链接这个库,然后通过“调用接口”(invocation interface)来加载JVM。实际上,JDK中标准的启动器也就是一段简单的链接了JVM的C代码。启动器解析命令、加载JVM、并通过“调用接口”(invocation interface)运行JAVA程序。
7.1 创建JVM
我们用下面这段C代码来加载一个JVM并调用Prog.main方法来演示如何使用调用接口。
public class Prog {
public static void main(String[] args) {
System.out.println(\ } }
下面是启动器: #include
#define PATH_SEPARATOR ';' /* define it to be ':' on Solaris */ #define USER_CLASSPATH \
main() {
JNIEnv *env; JavaVM *jvm; jint res; jclass cls; jmethodID mid; jstring jstr;
jclass stringClass; jobjectArray args;
#ifdef JNI_VERSION_1_2
JavaVMInitArgs vm_args; JavaVMOption options[1]; options[0].optionString =
?