안녕하세요 돼지왕 왕돼지입니다.
오늘은 JNI 의 Exception 에 대해 알아보겠습니다.
이 글은 http://java.sun.com/docs/books/jni/html/exceptions.html#11202 을 요약 정리한 글입니다.
Exceptions Introduction.
우리는 지금까지 JNI call 후에 여러가지 error 상황들을 체크해왔습니다. 예제 코드에서 말이죠. 여기서는 어떻게 그런 error 를 detect 하고 recover 하는지를 다룹니다. JNI 의 system call 에서 발생하는 에러는 그저 문서에 기술된 처리만 해주면 됩니다. 하지만, Java API method callback 을 호출한 경우에는 여기서 소개하는 방법에 따라 exception 처리를 해야 합니다.
JNI Exceptions Overview
Caching and Throwing Exceptions in Native Code
<Java Code>
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");
}
}
<JNI Code>
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");
}
}
<Result>
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
Exception 발생 여부는 ExceptionOccurred 함수로 체크합니다. Exception 발생시 ExceptionDescribe 함수로 exception 에 관한 메세지를 출력할 수 있고,ExceptionClear 를 통해, 해당 exception 을 제거할 수 있습니다.
ThrowNew 와 같은 함수를 호출했다고 해서, native method 의 실행이 바로 끝나지 않습니다. Java 와 다른 처리입니다.
( 참고로 exception 은 jthrowable 로 받습니다. )
A Utility Function.
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);
}
ThrowNew 를 호출했다고 해서 바로 exception이 던져지지 않습니다. return 을 한 순간에 pending exception 이 날아갑니다.
Proper Exception Handling
JNI 프로그램 역시 exception 을 예측하고 handle 할 수 있어야 합니다. 물론 귀찮은 일일 수 있지만, 좋은 어플을 만들기 위해서는 꼭 필요합니다.
Checking for Exceptions
/* 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. return value를 통해 error 인지 알 수 없을 경우에는 exception check 를 해주어야 합니다. 현재 thread 에서 일어난 pending exception 의 체크는 ExceptionOccurred 를 통해 check 할 수 있습니다. ( JDK 1.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 */
}
Handling Exceptions
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);
}
Exceptions in Utility Functions.
jvalue
JNU_CallMethodByName(JNIEnv *env,
jboolean *hasException,
jobject obj,
const char *name,
const char *descriptor, ...) {
va_list args;
jclass clazz;
jmethodID mid;
jvalue result;
if ((*env)->EnsureLocalCapacity(env, 2) == JNI_OK) {
clazz = (*env)->GetObjectClass(env, obj);
mid = (*env)->GetMethodID(env, clazz, name, descriptor);
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);
}
return result;
}
댓글