JNI异常处理

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

根据一个例子来介绍:
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.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

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

抛出一个异常通常需要两步:
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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值