JNI學習筆記(七)——異常處理
阿新 • • 發佈:2019-01-24
我們已經碰到過在一個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);
}