JNI官方文档翻译6-异常

来源:互联网 发布:手机换网络类型软件 编辑:程序博客网 时间:2024/04/29 11:42

      我们之前遇到了很多调用JNI函数抛异常的情形,我们通过检查返回值NULL的情况。这一节我们介绍怎么发现异常并且从这些错误情况中恢复。我们主要关注于JNI调用产生的异常,如果JNI系统调用发生了异常,我们可以检查返回值,但是,如果我们调用java层回调函数callback,那么接下来的内容你就需要注意了,我们需要遵循以下几个步骤。

      我们通过几个例子,介绍JNI处理异常的函数。


    捕获异常,抛出异常:看下面的例子

class CatchThrow {    private native void doit() throws IllegalArgumentException;//声明本地方法,这个方法会通过JNI方式抛出异常    private void callback() throws NullPointerException { //本地方法会回调这个方法,这个方法手动抛出一个异常        throw new NullPointerException("CatchThrow.callback");    }    public static void main(String args[]) {        CatchThrow c = new CatchThrow();        try {            c.doit();        } catch (Exception e) {            System.out.println("In Java:\n\t" + e);        }    }    static {        System.loadLibrary("CatchThrow");    }}

下面是doit() 的native实现代码:

JNIEXPORT void JNICALL Java_CatchThrow_doit(JNIEnv *env, jobject obj){    jthrowable exc;    jclass cls = (*env)->GetObjectClass(env, obj);    jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V");    if (mid == NULL) {        return;    }    (*env)->CallVoidMethod(env, obj, mid);    exc = (*env)->ExceptionOccurred(env);//检查异常是否发生    if (exc) {        /* We don't do much with the exception, except that we print a debug message for it, clear it, and             throw a new exception. */        jclass newExcCls;        (*env)->ExceptionDescribe(env);        (*env)->ExceptionClear(env);        newExcCls = (*env)->FindClass(env,"java/lang/IllegalArgumentException");        if (newExcCls == NULL) {        /* Unable to find the exception class, give up. */            return;        }        (*env)->ThrowNew(env, newExcCls, "thrown from C code");//从本地方法抛出IllegalArgumentException    }}

这是程序的输出:

java.lang.NullPointerException:
at CatchThrow.callback(CatchThrow.java)
at CatchThrow.doit(Native Method)
at CatchThrow.main(CatchThrow.java)
In Java:
java.lang.IllegalArgumentException: thrown from C code

    程序执行的流程是这样的,首先调用doit() ,doit()回调java层callback,callback执行会抛出一个空指针异常,接着流程回到native层进行异常检查,结果检查到了异常的发生,打印出log并清除这个异常后,这时native层又手动抛出了一个IllegalArgumentException。

    强调一下,native层的ThrowNew函数调用,或者是检测到异常的发生,并不会改变程序的执行流程,这和java层的执行方式是不一样的,java层在发生异常的地方会停止执行,控制流转到catch语句,或是抛到别的地方由上层控制。对于native方法抛出的异常,我们只能通过代码的方式来改变控制流程,而不要指望虚拟机。

    我们给出一个用来抛出异常的utils函数:throwByName

void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg){    jclass cls = (*env)->FindClass(env, name);    /* if cls is NULL, an exception has already been thrown */    if (cls != NULL) {         (*env)->ThrowNew(env, cls, msg);    }    /* free the local ref */    (*env)->DeleteLocalRef(env, cls);}

这个函数DeleteLocalRef第二个参数为NULL并无大碍,为NULL表明什么都不做。这个例子如果FindClass失败,cls为NULL。JNU前缀代表JNI utils,这个一个命名惯例。

JNI的程序员必须能够预料到会发生的异常,并且正确处理,这很乏味,但是对于能够写出健壮的应用是必不可少的。

检查异常的2种方式:

1.某些JNI函数通过返回值来告知执行的过程中发生了异常。下面的例子你可能觉得我啰嗦了,但是还是先看一下吧:

/* a class in the Java programming language */public class Window {    long handle;    int length;    int width;    static native void initIDs();//本地方法,用于cache fieldID    static {        initIDs();    }}

/* C code that implements Window.initIDs */jfieldID FID_Window_handle;jfieldID FID_Window_length;jfieldID FID_Window_width;JNIEXPORT void JNICALL Java_Window_initIDs(JNIEnv *env, jclass classWindow){    FID_Window_handle = (*env)->GetFieldID(env, classWindow, "handle", "J");    if (FID_Window_handle == NULL) { /* important check. 必须检查*/       return; /* error occurred. */    }    FID_Window_length = (*env)->GetFieldID(env, classWindow, "length", "I");    if (FID_Window_length == NULL) { /* important check. 必须检查*/        return; /* error occurred. */    }    FID_Window_width = (*env)->GetFieldID(env, classWindow, "width", "I");    /* no checks necessary; we are about to return anyway 可以不做检查,因为程序马上返回了*/}

这里需要强调一点,尽管我们知道handle length width一定在我们的java类的属性里,并且我们没写错,但是我们仍然要做一个NULL的检查


2. 有些JNI函数的返回值就不能告知我们有异常发生,即使返回值是NULL 或-1,这时我们需要使用JNI函数ExceptionOccurred来检查当前线程是否发生了异常。

看下面的代码片段:

public class Fraction {    // details such as constructors omitted    int over, under;    public int floor() { //将要被下面的native方法调用        return Math.floor((double)over/under);//我们    }}

/*  假定 method ID MID_Fraction_floor 已经初始化了 */void f(JNIEnv *env, jobject fraction){    jint floor = (*env)->CallIntMethod(env, fraction, MID_Fraction_floor);//调用java层的floor(),这个方法的返回值就说明不了有异常发生    /* important: check if an exception was raised */    if ((*env)->ExceptionCheck(env)) {        return;    }    ... /* use floor */}

ExceptionCheck 用来检查异常, 有时候,JNI调用返回NULL 或者-1等异常的值,这个时候如果你来检查异常的话,大多数都能检查到异常的发生。



处理异常:native处理异常也有两种方式,一种是交给调用者,另一种是调用ExceptionClear,然后自己处理异常。这是文档上用斜体排版的一句话:

It is extremely important to check, handle, and clear a pending exception
before calling any subsequent JNI functions.

意思是告诉我们,很重要,极其重要:在下一个JNI函数调用前,检查处理并清除异常是多么多么重要。如果你不这么做,发生什么谁也不知道。只有一小部分JNI函数是你可以调用的,这些函数包括异常检查,释放资源等。 发生了异常,然后清理资源是必要的,看一下下面的例子。


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

 

Utils函数中发生的异常:

    对于utils函数,你需要特殊对待,你需要保证异常传到调用者手里。我们在这里强调两项:

1.这类函数最好用返回值来告知调用者发生了哪些异常,这省去了调用者的异常检查工作。(推荐)

2.对于local ref ,我们最好清理

看下面的例子,根据名字执行方法: 这个方法通过 hasException将异常的情况传给调用者,

jvalue JNU_CallMethodByName(JNIEnv *env,jboolean *hasException,jobject obj,const char *name,const char *descriptor, ...){    va_list args;    jclass clazz;//local ref    jmethodID mid;    jvalue result;//local ref    if ((*env)->EnsureLocalCapacity(env, 2) == JNI_OK) {//保证可以创建2个local ref        clazz = (*env)->GetObjectClass(env, obj);//拿到obj的class        mid = (*env)->GetMethodID(env, clazz, name,descriptor);//查methodID       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, "illegal descriptor");        }        va_end(args);     }     (*env)->DeleteLocalRef(env, clazz);   }   if (hasException) {       *hasException = (*env)->ExceptionCheck(env);//将是否异常的flag传递给调用者,通过参数传回   }   return result;}

你或许注意到了ExceptionCheck ExceptionOccurred 都可以用来检查异常,ExceptionCheck是Java 2 SDK release 1.2.的方法。他们的区别是ExceptionCheck 返回JNI_TRUE,JNI_FALSE。ExceptionOccurred返回异常对象。 ExceptionCheck 简化了异常的检查,如果是在1.1的版本,那么你应该:

if (hasException) {    jthrowable exc = (*env)->ExceptionOccurred(env);    *hasException = exc != NULL;//如果异常发生,exec一定不为NULL    (*env)->DeleteLocalRef(env, exc);//释放资源是必要的}


    

上一篇:JNI官方文档翻译5-局部和全局引用

0 0
原创粉丝点击