JNI学习笔记(七)——异常处理

我们已经碰到过在一个JNI函数调用后,native代码进行错误检查的情形。本节解释native代码如何从这些错误条件中检查和恢复。


我们将关注发生错误的JNI函数调用上(而不是native代码上的二进制错误)。如果一个native方法有调用了一个系统调用,只需要简单地按照系统文件表明的方法来检查系统调用可能的失败。另一方面,native方法,呼叫了一个回调函数——java API方法,这时必须按照本节描述的步骤来从可能的异常中,检查和恢复。



概述


下面将以几个例子来介绍JNI的异常处理函数。


在native代码中捕获和抛出异常

以下程序展示了如何声明一个能够抛出异常的native方法。CatchThrow类声明了doit native方法并且指定它抛出一个IllegalArgumentException异常:
class CatchThrow {
    private native void doit()
        throws IllegalArgumentException;
    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");
    }
}

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

以下是运行的输出:
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

回调方法抛出了一个NullPointerException。当CallVoidMethod返回控制给native方法时,native代码会通过调用JNI函数的ExceptionOccurred来检查这个异常。在这个代码中,当一个异常发生了,native代码通过调用ExceptionDescribe函数来输出描述信息。然后调用ExceptionClear来清除该异常,然后向外抛出一个IlleagalArgumentException来代替。

一个有JNI导致的异常,不会马上终止native方法的执行。这一点和java语言的不同,当java编程语言抛出了一个异常,VM会在自动转换控制流到最近的满足异常类型的try/catch语句,然后清除附加的异常,并且执行这个异常处理。与之对比,JNI程序员,在一个异常发生时必须显示地实现控制流。


适当的异常处理

异常处理有时和乏味,但是对程序的健壮性却很有必要。


检查异常

有两种方式可以检查异常:
1)多数JNI函数以返回一个非正常的值来表示有一个错误发生了。该错误的返回值表明当前进程中有一个附加的异常。例如以下例子:
/* a class in the Java programming language */
public class Window {
    long handle;
    int length;
    int width;
    static native void initIDs();
    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 */
}

2)当使用一个不能由返回值断定发生了错误的JNI函数时,native代码必须依赖提出的异常,来进行错误检查。JNI函数在当前线程中执行异常检查的是ExceptionOccurred,在java2 sdk1.2时加入了ExceptionCheck。例如:
public class Fraction {
    // details such as constructors omitted
    int over, under;
    public int floor() {
        return Math.floor((double)over/under);
    }
}

/* Native code that calls Fraction.floor. Assume method ID
   MID_Fraction_floor has been initialized elsewhere. */
void f(JNIEnv *env, jobject fraction)
{
    jint floor = (*env)->CallIntMethod(env, fraction,
                                       MID_Fraction_floor);
    /* important: check if an exception was raised */
    if ((*env)->ExceptionCheck(env)) {
        return;
    }
    ... /* use floor */
}


处理异常


native代码可以有两种方式处理异常:
1)当异常发生时,native方法可以选择立即返回。
2)native方法可以清除异常(通过方法ExceptionClear),然后执行异常处理代码。

当异常发生时,在推出native代码执行流程时,有必要清除占用的资源,例如:
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);
}



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值