簡(jiǎn)述
- 寫(xiě) JNI 代碼時(shí),需要時(shí)刻考慮每一個(gè) JNI函數(shù) 可能拋出的異常。
- 在原生代碼中,一旦發(fā)生異常,需要馬上處理。
簡(jiǎn)單示例
public class CatchThrow {
static {
System.loadLibrary("CatchThrow");
}
public static void main(String args[]) {
CatchThrow c = new CatchThrow();
try {
c.doit();
} catch (Exception e) {
System.out.println("In Java:\n\t" + e);
}
}
private native void doit() throws IllegalArgumentException;
private void callback() throws NullPointerException {
throw new NullPointerException("CatchThrow.callback");
}
}
JNIEXPORT void JNICALL Java_CatchThrow_doit(JNIEnv * env, jobject obj) {
jclass cls = (* env)->GetObjectClass(env, obj);
jmethodID mid = (* env)->GetMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return;
}
// 注意此調(diào)用會(huì)拋出異常
(* env)->CallVoidMethod(env, obj, mid);
// 嘗試獲取已拋出的異常
jthrowable exc = (* env)->ExceptionOccurred(env);
// 通過(guò)此處的判斷,檢查是否真的拋出了異常
if (exc) {
// 輸出異常描述
(* env)->ExceptionDescribe(env);
// 清除異常
(* env)->ExceptionClear(env);
// 創(chuàng)建新的異常
jclass newExcCls = (* env)->FindClass(env, "java/lang/IllegalArgumentException");
if (newExcCls == NULL) {
return;
}
// 拋出新的異常取代舊的異常
(* env)->ThrowNew(env, newExcCls, "thrown from C code");
}
}
示例解說(shuō)
- 示例演示了如何聲明一個(gè)可能拋出異常的 原生方法 。
- 在原生代碼中,通過(guò)
ThrowNew()拋出異常時(shí),不會(huì)馬上中斷執(zhí)行流程。若在 Java 代碼中拋出異常則會(huì)馬上中斷執(zhí)行流程,轉(zhuǎn)到最近的try/catch塊執(zhí)行代碼。
函數(shù)解說(shuō)
-
ExceptionOccurred():若有異常,則返回一個(gè)jthrowable對(duì)象,否則返回NULL。
-
ExceptionCheck():若有異常,則返回JNI_TRUE,否則返回JNI_FALSE。
-
ExceptionDescribe():若有異常,輸出異常描述,相當(dāng)于 Java 代碼中的Exception.printStackTrace()。
-
ExceptionClear():清除隊(duì)列中的異常,后續(xù)代碼不會(huì)在獲取到。
-
ThrowNew():拋出異常。
異常檢查
第一種
簡(jiǎn)介
- 大多數(shù) JNI函數(shù) 會(huì)直接通過(guò)返回值(如返回
NULL)來(lái)說(shuō)明是否遇到了錯(cuò)誤,同時(shí)也暗示了此線程的隊(duì)列中有待處理的異常。
簡(jiǎn)單示例
public class Window {
static native void initIDs();
static {
initIDs();
}
long handle;
int length;
int width;
}
jfieldID FID_Window_handle;
jfieldID FID_Window_length;
jfieldID FID_Window_width;
JNIEXPORT void JNICALL Java_Window_initIDs(JNIEnv * env, jclass classWindow) {
// 即便可以確保Window類中有這些字段,也必須要檢查,因?yàn)槿匀豢赡軙?huì)因?yàn)閮?nèi)存不足而失敗
FID_Window_handle = (* env)->GetFieldID(env, classWindow, "handle", "J");
if (FID_Window_handle == NULL) {
return;
}
FID_Window_length = (* env)->GetFieldID(env, classWindow, "length", "I");
if (FID_Window_length == NULL) {
return;
}
FID_Window_width = (* env)->GetFieldID(env, classWindow, "width", "I");
// 此處沒(méi)有檢測(cè)的必要,無(wú)論成功與否都是直接return
}
第二種
簡(jiǎn)介
- 有些 JNI函數(shù) 的返回值無(wú)法表明錯(cuò)誤是否發(fā)生,因?yàn)檫@類 JNI函數(shù) 的返回值可能另作他用,如
CallIntMethod(),此時(shí)可以通過(guò)ExceptionOccurred()或ExceptionCheck()來(lái)檢測(cè)。
簡(jiǎn)單示例
public class Fraction {
public int floor() {
return (int) Math.floor((double) over / under);
}
int over, under;
}
void Java_Fraction_floor(JNIEnv * env, jobject fraction) {
// ...
jint floor = (* env)->CallIntMethod(env, fraction, MID_Fraction_floor);
// 檢查是否有異常
if ((* env)->ExceptionCheck(env)) {
// 處理異常
return;
}
// ...
}
異常處理
- 有兩種處理異常的方法:
- 立即返回,讓異常傳遞至 Java 代碼。
- 使用
ExceptionClear()清除異常,然后處理異常
- 在調(diào)用一些 JNI函數(shù) 前,檢查、處理、清理異常很重要。
- 大部分 JNI函數(shù) 在隊(duì)列中有待處理異常時(shí),調(diào)用會(huì)導(dǎo)致一些無(wú)法預(yù)料的錯(cuò)誤,另一小部分 JNI函數(shù) 之所以能在這種情況下調(diào)用是因?yàn)檫@些函數(shù)為了處理異常而設(shè)計(jì)的。
- 通常很有必要在發(fā)生異常的時(shí)候清理資源。
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 (/* ... */) {
// 發(fā)生異常,釋放資源
(* env)->ReleaseStringChars(env, jstr, cstr);
return;
}
// 函數(shù)結(jié)束時(shí),釋放資源
(* env)->ReleaseStringChars(env, jstr, cstr);
}
效用函數(shù)里的異常
- 對(duì)于 效用函數(shù) 里的異常,應(yīng)該要確保異常能夠傳播到 原生函數(shù) 調(diào)用者中。
- 通常情況下, 效用函數(shù) 應(yīng)該提供一個(gè)特殊的返回值來(lái)表明異常在 效用函數(shù) 里發(fā)生了。
- 應(yīng)該在 效用函數(shù) 的異常處理代碼中,處理好 局部引用 。
相關(guān)工具函數(shù)
用于便捷拋出異常
// name是異常的類型描述符,msg是拋出異常時(shí)需要輸出的信息
void JNU_ThrowByName(JNIEnv * env, const char * name, const char * msg) {
// 此處若FindClass失敗,會(huì)拋出NoClassDefFoundError異常
jclass cls = (* env)->FindClass(env, name);
if (cls != NULL) {
(* env)->ThrowNew(env, cls, msg);
}
// 若cls是NULL,DeleteLocalRef不會(huì)執(zhí)行任何操作
(* env)->DeleteLocalRef(env, cls);
}
通用調(diào)用函數(shù)
代碼
// 參數(shù)說(shuō)明:
// hasException:用來(lái)接收異常是否拋出的信息,若非NULL,則代表著調(diào)用者關(guān)心是否有異常拋出
// obj:方法所屬的對(duì)象
// name:方法名字
// descriptor:方法描述符
jvalue
JNU_CallMethodByName(JNIEnv * env, jboolean * hasException, jobject obj, const char * name, const char * descriptor,
...) {
va_list args;
// 聯(lián)合體
jvalue result = NULL;
if ((* env)->EnsureLocalCapacity(env, 2) == JNI_OK) {
jclass clazz = (* env)->GetObjectClass(env, obj);
jmethodID mid = (* env)->GetMethodID(env, clazz, name, descriptor);
if (mid) {
const char * p = descriptor;
while (* p != ')') p++;
p++;
va_start(args, descriptor);
switch (* p) {
case 'V':
(* env)->CallVoidMethodV(env, obj, mid, args);
break;
case '[':
case 'L':
// result任意一個(gè)字段被初始化,都代表著有錯(cuò)誤發(fā)生
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);
}
// 若調(diào)用者關(guān)心是否有異常拋出,則檢查是否有異常拋出
if (hasException) {
* hasException = (* env)->ExceptionCheck(env);
}
// // JDK release 1.1版本
// if (hasException) {
// jthrowable exc = (* env)->ExceptionOccurred(env);
// * hasException = (jboolean) (exc != NULL);
// (* env)->DeleteLocalRef(env, exc);
// }
return result;
}
使用例子
JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv * env, jobject obj) {
printf("In C\n");
JNU_CallMethodByName(env, NULL, obj, "callback", "()V");
// 這里之所以不檢查錯(cuò)誤是因?yàn)闊o(wú)論是否發(fā)生錯(cuò)誤,都會(huì)直接return
}