2

Suppose I have a Java class like this:

    public class Test
    {
        static { System.loadLibrary("test"); }
        public native int foo();
    }

Suppose that the foo() method is doing some JNI calls, and one of these calls fails (IE, throws an exception). I would then like to return from the JNI code and have the exception thrown in Java. For example:

    jint Java_Test_foo(JNIEnv* env, jobject thiz)
    {
        jstring foobar = (*env)->NewStringUTF(env, "Hello from JNI !");
        if(foobar == NULL) // Not enough memory. OutOfMemoryError is thrown
            return NULL; // Return immediately to get exception thrown in Java
        // Do other stuff
        ...
        return some_computed_jint;
    }

The problem is that return NULL is not a jint. In Android for example, I would get this warning when compiling: warning: return makes integer from pointer without a cast.

Now the question is: What should I return in case of an Exception being thrown inside a JNI method that returns a jint?

foens
  • 8,642
  • 2
  • 36
  • 48

2 Answers2

6

If your code (or a library) raises an Exception in Java, it doesn't matter what value you return, Java will ignore it. Obviously it needs to be a compatible type - so returning 0 in your example would seem to make sense, or whatever you're comfortable with. When your code returns, the Java runtime will notice that an Exception has been raised and continue to propagate it and ignore the return value of your function.

You will, of course, need to return a compatible type. Don't simply return NULL, as that will be cast to an int when the function is not declared to return a pointer, which may not be appropriate.

Obviously, though, when you're calling C functions, those would not raise an Exception. So you can either map an integer to an error condition (-1 for example) and then throw the Exception in Java, or you can take the time to build an Exception in JNI.

Edward Thomson
  • 74,857
  • 14
  • 158
  • 187
  • So your saying in Java (not down in JNI) the return values does not matter when an Exception is thrown. This is true since the java method will never reach the return statement when an exception is thrown. I am with you there. But are you saying that if I call a JNI method that "throws" an exception and I do not clear it using ExceptionClear, then no matter what I return from JNI the exception will be thrown when returned to Java? If yes, then I can return any jint and that jint will never be visible on the Java side, right? – foens Dec 03 '11 at 17:21
0

EDIT:   See also this elegant answer using a function instead of the bottom preprocessor macro.


I provide an example to complete the Edward Thomson's answer.

In this example the JNI non-void functions return 0;

JNIEXPORT jlong JNICALL Java_group_package_class_function1(
                           JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
    return jlong(result);
  }
  CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION
  return 0;
}

JNIEXPORT jstring JNICALL Java_group_package_class_function2(
                            JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
    jstring jstr = env->NewStringUTF("my result");
    return  jstr;
  }
  CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION
  return 0;
}

JNIEXPORT void JNICALL Java_group_package_class_function3(
                           JNIEnv *env, jobject object, jlong value)
{
  try 
  {
    /* ... my processing ... */
  }
  CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION
}  // void function => no "return 0;" statement

The C preprocessor macro CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION is present at the end of all the above JNI functions.

#define CATCH_CPP_EXCEPTION_AND_THROW_JAVA_EXCEPTION              \
                                                                  \
  catch (const package::Exception& e)                             \
  {                                                               \
    jclass jc = env->FindClass("group/package/Exception");        \
    if(jc) env->ThrowNew (jc, e.what());                          \
    /* if null => NoClassDefFoundError already thrown */          \
  }                                                               \
  catch (const std::bad_alloc& e)                                 \
  {                                                               \
    /* OOM exception */                                           \
    jclass jc = env->FindClass("java/lang/OutOfMemoryError");     \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (const std::ios_base::failure& e)                         \
  {                                                               \
    /* IO exception */                                            \
    jclass jc = env->FindClass("java/io/IOException");            \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (const std::exception& e)                                 \
  {                                                               \
    /* unknown exception */                                       \
    jclass jc = env->FindClass("java/lang/Error");                \
    if(jc) env->ThrowNew (jc, e.what());                          \
  }                                                               \
  catch (...)                                                     \
  {                                                               \
    /* Oops I missed identifying this exception! */               \
    jclass jc = env->FindClass("java/lang/Error");                \
    if(jc) env->ThrowNew (jc, "unexpected exception");            \
  }
Community
  • 1
  • 1
oHo
  • 51,447
  • 27
  • 165
  • 200