💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
在编写工具函数时候应当注意把工具函数中可能发生的异常通过某种方式暴露给调用者,使他们能够处理这些异常。特别是在以下两种场景下: * 工具函数最好提供一个特殊的返回值来标识函数中发生的异常,可以让调用者方便的检查异常。 * 另外,工具函数在发生异常的时候,应当按照5.3节描述的规则来管理局部引用。 让我们通过一个例子来更清楚的了解这两种状况,以下是一个工具函数,通过一个对象示例方法描述符和名字属性调用一个Java函数: ~~~ 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)->CallVoidMethod(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, "illegal descriptor"); } va_end(args); } (*env)->DeleteLocalRef(env, clazz); } if(hasException){ *hasException = (*env)->ExceptionCheck(env); } return result; } ~~~ JNU_CallMethodByName使用了一个额外的jboolean类型的参数hasException用于保存函数执行过程是否出现异常的标志位,如果所有操作都成功,则被设置为JNI_FALSE, 否则被设置为JNI_TRUE. JNU_CallMethodByName首先检查了是能够创建两个局部引用:一个为了保存类引用,另一个则为了保存函数调用的返回值。然后,获取对象对应的类并查找函数对应的函数ID。接下来在switch处理流程中根据返回值类型调用不同的JNI函数。最后,如果hasException不为空,则调用ExceptionCheck检查异常并将值赋值给hasException. ExceptionCheck函数是在Java 2 SDK release 1.2中加入的,它与ExceptionOccurred函数的作用类似,不同之处在于ExceptionCheck函数不会返回异常对象的引用,而是返回一个异常是否发生的jboolean标识.所以,当调用者不关心异常的类型,仅仅关心是否发生了异常的时候,使用它会很方便而且高效。上边异常检查的代码如果用ExceptionOccurred重写如下: ~~~ if(hasException) { jthrowable exc = (*env)->ExceptionOccurred(env); *hasException = exc != NULL; (*env)->DeleteLocalRef(env, exc); } ~~~ 如上代码,需要额外的调用DeleteLocalRef来删除指向异常对象的引用。 使用JNU_CallMethodByName函数,我们可以重写4.2节的InstanceMethodCall.nativeMethod如下: ~~~ JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj){ printf("In C\n"); JUN_CallMethodByName(env, NULL, obj, "callback", "()V"); } ~~~ 我们不需要再JNU_CallMethodByName调用之后检查异常因为这个函数会在调用JNU_CallMethodByName之后立刻返回。