본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Java] JNI Tutorial - Exceptions

by 돼지왕 왕돼지 2012. 3. 30.
반응형



안녕하세요 돼지왕 왕돼지입니다.

오늘은 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 보다 좋습니다.
 



도움이 되셨다면 손가락 꾸욱~




 
반응형

댓글