관리 메뉴

드럼치는 프로그래머

[JNI/NDK] JNI Tutorial - Exceptions 본문

★─Programing/☆─JNI | NDK

[JNI/NDK] JNI Tutorial - Exceptions

드럼치는한동이 2013. 5. 31. 14:53
오늘은 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


1. 일반적인 JNI function call 은 NULL 과 같은 return 으로 예외상황을 알립니다.

/* 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


Native code 는 pending exception 을 다음과 같이 2가지 방법으로 처리합니다.

- exception 발생시 바로 return 을 해서 caller 가 exception을 handle 할 수 있도록 합니다.
- ExceptionClear 를 호출하여 exception 을 clear 한 후에 error handling 을 해당 코드에서 수행.

다른 JNI function 을 호출하기 전에 pending exception 을 check 하고 handle 한 다음에 clear 해주는것은 "엄청나게" 중요합니다. pending exception 이 있는 상태로 다른 JNI 함수를 호출하는 것은 엄청나게 위험합니다. 어떤 결과를 초래할 지 모릅니다. Exception 이 발생한 상황에서는 exception 을 handle 하거나, resource 를 해제하는 등의 방어적인 느낌의 JNI function 들만 안전하게 call 할 수 있습니다.

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.


프로그래머는 native method 에서 exception 발생 시, 어떤 형태로 exception 을 caller 에게 알려줄 지 잘 결정해야 합니다.가능하면 special return value 로 exception 을 알리는 것이, client 가 처리할 것도 적게 되어 좋습니다. 참고로 utility function 은 local reference 를 잘 관리해야 합니다.

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;

}


ExceptionCheck 함수는 JDK 1.2 부터 등장합니다. ExceptionOccurred 와 비슷합니다. 다른 점은 ExceptionCheck 는 exception object 에 대한 reference 를 던지지 않고, JNI_TRUE 나 JNI_FALSE 만을 던집니다. 그래서 그냥 exception 여부만 알려면 ExceptionCheck 가 ExceptionOccurred 보다 좋습니다.


 

[출처] http://aroundck.tistory.com/623

Comments