`

Chapter 6 Exceptions(JAVA EXCEPTION IN NATIVE CODE)

阅读更多
Contents | Prev | Next | Index The Java Native Interface
Programmer's Guide and Specification 

--------------------------------------------------------------------------------


Chapter 6
Exceptions

--------------------------------------------------------------------------------


We have encountered numerous situations in which native code checks for possible errors after making JNI function calls. This chapter examines how native code can detect and recover from these error conditions.

We will focus on errors that occur as the result of issuing JNI function calls, not arbitrary errors that happen in native code. If a native method makes an operating systems call, it simply follows the documented way of checking for possible failures in the system call. If, on the other hand, the native method issues a callback to a Java API method, then it must follow the steps described in this chapter to properly check for and recover from possible exceptions that have occurred in the method execution.

6.1 Overview
We introduce JNI exception handling functions through a series of examples.

6.1.1 Caching and Throwing Exceptions in Native Code
The program below shows how to declare a native method that throws an exception. The CatchThrow class declares the doit native method and specifies that it throws an 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");
     }
}

The CatchThrow.main method calls the native method doit, implemented as follows:

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");
     }
}



Running the program with the native library produces the following output:

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

The callback method throws a NullPointerException. When the CallVoidMethod returns control to the native method, the native code will detect this exception by calling the JNI function ExceptionOccurred. In our example, when an exception is detected, the native code outputs a descriptive message about the exception by calling ExceptionDescribe, clears the exception using ExceptionClear, and throws an IllegalArgumentException instead.

A pending exception raised through the JNI (by calling ThrowNew, for example) does not immediately disrupt the native method execution. This is different from how exceptions behave in the Java programming language. When an exception is thrown in the Java programming language, the virtual machine automatically transfers the control flow to the nearest enclosing try/catch statement that matches the exception type. The virtual machine then clears the pending exception and executes the exception handler. In contrast, JNI programmers must explicitly implement the control flow after an exception has occurred.

6.1.2 A Utility Function
Throwing an exception involves first finding the exception class and then issuing a call to the ThrowNew function. To simplify the task, we can write a utility function that throws a named exception:

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);
}

In this book, the JNU prefix stands for JNI Ut>tilities. JNU_ThrowByName first finds the exception class using the FindClass function. If FindClass fails (returns NULL), the virtual machine must have thrown an exception (such as NoClassDefFoundError). In this case JNU_ThrowByName does not attempt to throw another exception. If FindClass succeeds, we throw the named exception by calling ThrowNew. When JNU_ThrowByName returns, it guarantees that there is a pending exception, although the pending exception was not necessarily what is specified by the name argument. We make sure to delete the local reference to the exception class created in this function. Passing NULL to DeleteLocalRef is a no-op, which is an appropriate action if FindClass fails and returns NULL.

6.2 Proper Exception Handling
JNI programmers must foresee possible exception conditions and write code that checks for and handles these cases. Proper exception handling is sometimes tedious but is necessary in order to produce robust applications.

6.2.1 Checking for Exceptions
There are two ways to check whether an error has occurred.

Most JNI functions use a distinct return value (such as NULL) to indicate that an error has occurred. The error return value also implies that there is a pending exception in the current thread. (Encoding error conditions in the return value is common practice in C.) The following example illustrates using the NULL value returned by Get-Field-ID in checking for errors. The example consists of two parts: a class Window that defines a number of instance fields (handle, length, and width) and a native method that caches the field IDs of these fields. Even though these fields exist in the Window class, we still need to check for possible errors returned from GetFieldID because the virtual machine may not be able to allocate the memory needed to represent a field ID.
/* 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 */
}


When using a JNI function whose return value cannot flag that an error has occurred, native code must rely on the raised exception to do error checks. The JNI function that performs checks for a pending exception in the current thread is ExceptionOccurred. (ExceptionCheck was also added in Java 2 SDK release 1.2.) For example, the JNI function CallIntMethod cannot encode an error condition in the return value. Typical choices of error condition return values, such as NULL and -1, do not work because they could be legal values returned by the method that was called. Consider a Fraction class whose floor method returns the integral part of the value of the fraction, and some native code that calls this method.
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 */
}

When the JNI function returns a distinct error code, the native code may still check for exceptions explicitly by calling, for example, ExceptionCheck. However, it is more efficient to check for the distinct error return value instead. If a JNI function returns its error value, a subsequent ExceptionCheck call in the current thread is guaranteed to return JNI_TRUE.

6.2.2 Handling Exceptions
Native code may handle a pending exception in two ways:

The native method implementation can choose to return immediately, causing the exception to be handled in the caller.
The native code can clear the exception by calling ExceptionClear and then execute its own exception handling code.
It is extremely important to check, handle, and clear a pending exception before calling any subsequent JNI functions. Calling most JNI functions with a pending exception--with an exception that you have not explicitly cleared--may lead to unexpected results. You can call only a small number of JNI functions safely when there is a pending exception in the current thread. Section 11.8.2 specifies the complete list of these JNI functions. Generally speaking, when there is a pending exception you can call the JNI functions that are designed to handle exceptions and the JNI functions that release various virtual machine resources exposed through the JNI.

It is often necessary to be able to free resources when exceptions occur. In the following example, the native method first obtains the contents of a string by issuing a GetStringChars call. It calls ReleaseStringChars if a subsequent operation fails:



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);
}

The first call to ReleaseStringChars is issued when there is a pending exception. The native method implementation releases the string resource and returns immediately afterwards without first clearing the exception.

6.2.3 Exceptions in Utility Functions
Programmers writing utility functions should pay special attention to ensure that exceptions are propagated to the caller native method. In particular, we emphasize the following two issues:

Preferably, utility functions should provide a special return value to indicate that an exception has occurred. This simplifies the caller's task of checking for pending exceptions.
In addition, utility functions should follow the rules (§5.3) for managing local references in exception handling code.

To illustrate, let us introduce a utility function that performs a callback based on the name and descriptor of an instance method:

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;
}

JNU_CallMethodByName takes, among other arguments, a pointer to a jboolean. The jboolean will be set to JNI_FALSE if everything succeeds and to JNI_TRUE if an exception occurs at any point during the execution of this function. This gives the caller of JNU_CallMethodByName an obvious way to check for possible exceptions.

JNU_CallMethodByName first makes sure that it can create two local references: one for the class reference and the other for the result returned from the method call. Next, it obtains the class reference from the object and looks up the method ID. Depending on the return type, the switch statement dispatches to the corresponding JNI method call function. After the callback returns, if hasException is not NULL, we call ExceptionCheck to check for pending exceptions.

The ExceptionCheck function is new in Java 2 SDK release 1.2. It is similar to the ExceptionOccurred function. The difference is that ExceptionCheck does not return a reference to the exception object, but returns JNI_TRUE when there is a pending exception and returns JNI_FALSE when there is no pending exception. ExceptionCheck simplifies local reference management when the native code only needs to know whether an exception has occurred but needs not obtain a reference to the exception object. The previous code would have to be rewritten as follows in JDK release 1.1:

   if (hasException) {
         jthrowable exc = (*env)->ExceptionOccurred(env);
         *hasException = exc != NULL;
         (*env)->DeleteLocalRef(env, exc);
   }

The additional DeleteLocalRef call is necessary in order to delete the local reference to the exception object.

Using the JNU_CallMethodByName function we can rewrite the implementation of Instance-MethodCall.nativeMethod in Section 4.2 as follows:

JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
{
     printf("In C\n");
     JNU_CallMethodByName(env, NULL, obj, "callback", "()V");
}

We need not check for exceptions after the JNU_CallMethodByName call because the native method returns immediately afterwards.



--------------------------------------------------------------------------------

Contents | Prev | Next | Index The Java Native Interface
Programmer's Guide and Specification 


Copyright © 2002 Sun Microsystems, Inc. All rights reserved
Please send any comments or corrections to jni@java.sun.com
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics