1. 程式人生 > >JNI學習筆記(七)——異常處理

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