JNI异常处理

xiaoxiao2021-02-28  87

本地代码中如何缓存和抛出异常

根据一个例子来介绍:  1.新建一个CatchThrow.Java

public class CatchThrow { public native void doit() throws IllegalArgumentException; private void callback() throws NullPointerException{ throw new NullPointerException("CatchThrow.callback"); } } 1234567 1234567

2.JNI实现

JNIEXPORT void JNICALL Java_com_test_git_jnidemo_JniUtil_CatchThrow_doit (JNIEnv *env, jobject jobj){ jthrowable exc; //获取jclass jclass cls = (*env).GetObjectClass(jobj); //获取callback 方法id jmethodID mid = (*env).GetMethodID(cls, "callback", "()V"); if(mid == NULL){ return; } //调用callback方法 (*env).CallVoidMethod(jobj, mid); //异常检查 exc = (*env).ExceptionOccurred(); if(exc){ //异常处理 jclass newExcCls; //输出异常 (*env).ExceptionDescribe(); //清除异常 (*env).ExceptionClear(); newExcCls = (*env).FindClass("java/lang/IllegalArgumentException"); if(newExcCls == NULL){ //没有发现异常 return; } (*env).ThrowNew(newExcCls, "thrown from C code"); } }; 123456789101112131415161718192021222324252627282930 123456789101112131415161718192021222324252627282930

3.Java中调用

CatchThrow catchThrow = new CatchThrow(); try { catchThrow.doit(); }catch (Exception e){ Log.i(TAG, "In Java catchThrow: " + e); } 123456 123456

4.输出结果:

09-29 17:16:16.291 18958-18958/com.test.git.jnidemo W/System.err: java.lang.NullPointerException: CatchThrow.callback 09-29 17:16:16.291 18958-18958/com.test.git.jnidemo W/System.err: at com.test.git.jnidemo.JniUtil.CatchThrow.callback(CatchThrow.java:11) 09-29 17:16:16.291 18958-18958/com.test.git.jnidemo W/System.err: at com.test.git.jnidemo.JniUtil.CatchThrow.doit(Native Method) 09-29 17:16:16.291 18958-18958/com.test.git.jnidemo W/System.err: at com.test.git.jnidemo.Activity.MainActivity.catchThrow(MainActivity.java:26) 09-29 17:16:16.291 18958-18958/com.test.git.jnidemo W/System.err: at com.test.git.jnidemo.Activity.MainActivity.onCreate(MainActivity.java:19) ...... 09-29 17:16:16.301 18958-18958/com.test.git.jnidemo I/MainActivity-: In Java catchThrow: java.lang.IllegalArgumentException: thrown from C code 1234567 1234567

制作一个抛出异常的工具函数

抛出一个异常通常需要两步:  1.通过FindClass找到异常类  2.调用ThrowNew函数生成异常。

void JUN_ThrowByName(JNIEnv *env, const char *name, const char *msg){ //生成jclass jclass cls = (*env).FindClass(name); //如果cls为NULL,一个异常已经发生 if(cls != NULL){ (*env).ThrowNew(cls, msg); } //释放本地引用 (*env).DeleteLocalRef(cls); }; 12345678910 12345678910

JNU_ThrowByName 这个工具函数首先使用 FindClass 函数来找到异常类,如果 FindClass 执行失败(返回 NULL),VM 会抛出一个异常(比如 NowClassDefFoundError),这种情况下 JNI_ThrowByName 不会再抛出另外一个  异常。如果 FindClass 执行成功的话,我们就通过 ThrowNew 来抛出一个指定名 字的异常。当函数 JNU_ThrowByName 返回时,它会保证有一个异常需要处理,但 这个异常不一定是 name 参数指定的异常。当函数返回时,记得要删除指向异常 类的局部引用。向 DeleteLocalRef 传递 NULL 不会产生作用。

异常处理

异常检查

检查异常有两种方式:  1.大部分JNI函数会通过特定的返回值(NULL)来表示已经发生了一个错误,并且当前线程中又一个异常需要处理。在C语言中,用返回值来标识错误信息是一个很常见的方式。

jclass localRefCls = (*env).FindClass("java/lang/String"); if(localRefCls == NULL){ return; //exeption } 1234 1234

2.当一个JNI函数返回一个明确的错误码时,可以用ExceptionCheck来检查是否有异常发生。但是,用返回的错误码来判断比较高效。一旦JNI函数的返回值是一个错误码,那么接下来调用ExceptionCheck肯定会返回JNI_TRUE。

//异常检查 exc = (*env).ExceptionOccurred(); jboolean error = (*env).ExceptionCheck(); if(error){ //异常处理 jclass newExcCls; //输出异常 (*env).ExceptionDescribe(); //清除异常 (*env).ExceptionClear(); newExcCls = (*env).FindClass("java/lang/IllegalArgumentException"); if(newExcCls == NULL){ //没有发现异常 return; } (*env).ThrowNew(newExcCls, "thrown from C code"); } 1234567891011121314151617 1234567891011121314151617

异常处理

本地代码通常有两种方式来处理一个异常:  1.一旦发生异常,立即返回,让调用者处理这个异常。  2.通过ExceptionClear清除异常,然后执行自己的异常处理代码。

当一个异常发生后,必须先检查、处理、清除异常后再做其他JNI函数调用,否则的话,结果未知。  通常来说,当有一个未处理的异常时,你只可以调用两种JNI函数:异常处理和清除VM资源的函数。

当异常发生时,释放资源是一件很重要的事。比如:

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); } 12345678910111213141516 12345678910111213141516

工具函数中的异常

编写工具函数时,要把工具函数内部分发生的异常传播到调用它的方法中。  这里有两个方法:  1.对调用者来说,工具函数提供一个错误返回码比简单地把异常传播过去更方便一些。  2.工具函数在发生异常时尤其需要注意管理局部引用的方式。

jvalue JUN_CallMethodByName(JNIEnv *env, jboolean *hasException, jobject jobj, const char* name, const char* descriptor, ...){ va_list args; jclass clazz; jmethodID mid; jvalue result; if((*env).EnsureLocalCapacity(2) == JNI_OK){ clazz = (*env).GetObjectClass(jobj); mid = (*env).GetMethodID(clazz, name, descriptor); if(mid){ const char *p = descriptor; while (*p != ')') p++; p++; va_start(args, descriptor); switch (*p){ case 'V': (*env).CallVoidMethodV(jobj, mid, args); break; case '[': case 'L': result.l = (*env).CallObjectMethodV(jobj, mid, args); break; case 'Z': result.z = (*env).CallBooleanMethodV(jobj, mid, args); break; default: (*env).FatalError("illegal descriptor"); break; } va_end(args); } (*env).DeleteLocalRef(clazz); } if(hasException){ *hasException = (*env).ExceptionCheck(); } return result; }; 12345678910111213141516171819202122232425262728293031323334353637383940414243 12345678910111213141516171819202122232425262728293031323334353637383940414243

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){ // *hasException = (*env).ExceptionCheck(); jthrowable exc = (*env).ExceptionOccurred(); *hasException = exc != NULL; (*env).DeleteLocalRef(exc); } 123456 123456

为了删除指向异常对象的局部引用,DeleteLocalRef 方法必须被调用。 使用 JNU_CallMethodByName 这个工具函数,我们可以重写 Instance-MethodCall.nativeMethod 方法的实现:

JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) { printf("In C\n"); JNU_CallMethodByName(env, NULL, obj, "callback", "()V"); } 1234 1234

调用 JNU_CallMethodByName 函数后,我们不需要检查异常,因为 地方法后面 会立即返回。

转载请注明原文地址: https://www.6miu.com/read-79414.html

最新回复(0)