JNI官方文档翻译5-局部和全局引用

      我们之前遇到了很多调用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-局部和全局引用

猜你喜欢

转载自blog.csdn.net/mtaxot/article/details/51433417