JNI异常处理

来源:互联网 发布:应用系统性能优化方案 编辑:程序博客网 时间:2024/05/17 17:16

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

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

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

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

3.Java中调用

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

4.输出结果:

09-29 17:16:16.291 18958-18958/com.test.git.jnidemo W/System.err: java.lang.NullPointerException: CatchThrow.callback09-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

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

抛出一个异常通常需要两步:
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);};

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        }

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

异常处理

本地代码通常有两种方式来处理一个异常:
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);}

工具函数中的异常

编写工具函数时,要把工具函数内部分发生的异常传播到调用它的方法中。
这里有两个方法:
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;};

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

为了删除指向异常对象的局部引用,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");  }

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

0 0