68

I'd like a consistent and simple way to throw exceptions in JNI code; something that handles chained exceptions (implicitly from the env->ExceptionOccurred method, or explicitly by parameters, either way is good) and saves me looking up constructors every time I want to do this. All of the above is preferably in C, although I could translate it from C++ at need.

Does anyone on SO have something like this that they can share?

Chris R
  • 17,546
  • 23
  • 105
  • 172
  • By 'handles chained exceptions' do you mean that your code would notice an catch a Java-level exception on return from Java to C++, wrap it in some other exception, and throw that new exception back up from C++ to Java? – android.weasel Aug 31 '12 at 11:49

4 Answers4

56

We just code utility methods for each of the types of exceptions we want to throw. Here are some examples:

jint throwNoClassDefError( JNIEnv *env, char *message )
{
    jclass exClass;
    char *className = "java/lang/NoClassDefFoundError";

    exClass = (*env)->FindClass( env, className);
    if (exClass == NULL) {
        return throwNoClassDefError( env, className );
    }

    return (*env)->ThrowNew( env, exClass, message );
}

jint throwNoSuchMethodError(
        JNIEnv *env, char *className, char *methodName, char *signature )
{

    jclass exClass;
    char *exClassName = "java/lang/NoSuchMethodError" ;
    LPTSTR msgBuf;
    jint retCode;
    size_t nMallocSize;

    exClass = (*env)->FindClass( env, exClassName );
    if ( exClass == NULL ) {
        return throwNoClassDefError( env, exClassName );
    }

    nMallocSize = strlen(className) 
            + strlen(methodName)
            + strlen(signature) + 8;

    msgBuf = malloc( nMallocSize );
    if ( msgBuf == NULL ) {
        return throwOutOfMemoryError
                ( env, "throwNoSuchMethodError: allocating msgBuf" );
    }
    memset( msgBuf, 0, nMallocSize );

    strcpy( msgBuf, className );
    strcat( msgBuf, "." );
    strcat( msgBuf, methodName );
    strcat( msgBuf, "." );
    strcat( msgBuf, signature );

    retCode = (*env)->ThrowNew( env, exClass, msgBuf );
    free ( msgBuf );
    return retCode;
}

jint throwNoSuchFieldError( JNIEnv *env, char *message )
{
    jclass exClass;
    char *className = "java/lang/NoSuchFieldError" ;

    exClass = (*env)->FindClass( env, className );
    if ( exClass == NULL ) {
        return throwNoClassDefError( env, className );
    }

    return (*env)->ThrowNew( env, exClass, message );
}

jint throwOutOfMemoryError( JNIEnv *env, char *message )
{
    jclass exClass;
    char *className = "java/lang/OutOfMemoryError" ;

    exClass = (*env)->FindClass( env, className );
    if ( exClass == NULL ) {
        return throwNoClassDefError( env, className );
    }

    return (*env)->ThrowNew( env, exClass, message );
}

That way, it's easy to find them, your code-completion editor will help you to type them in, and you can pass simple parameters.

I'm sure you could expand this to handle chained exceptions, or other more complicated approaches. This was enough to meet our needs.

morgano
  • 17,210
  • 10
  • 45
  • 56
Steven M. Cherry
  • 1,355
  • 1
  • 11
  • 14
  • 34
    Just found this, thanks. However, won't the error condition in `throwNoClassDefError` result in infinite recursion and an inevitable stack overflow? It should really never happen, I admit, but that doesn't seem like the appropriate way to handle it. Perhaps fall back on `java.lang.error`, and `abort()` or something if that doesn't work. – Tim Sylvester Sep 19 '10 at 00:42
  • Yeah, I saw that too. Agreed. I can't get my ThrowNew() calls to do _anything_, even though they return NULL (success, that is). Nothin's ever easy... – Stevens Miller Feb 14 '12 at 15:25
  • 2
    Sorry, but can anyone explain me why the throwNoClassDefError function will not fall in infinite recursion in case when the "java/lang/NoClassDefFoundError" class will not be found? – amigo Dec 13 '12 at 20:53
  • It probably would if the class was ever not found. However I suspect the chance of it not being found is practically 0. It looks like that is a copy/past of other functions and in reality should be handled differently. – shbi Dec 17 '14 at 13:51
  • 1
    I'm curious how you handle the result value from these methods? If you try and throw an exception and that fails what should we do then? – steinybot May 07 '15 at 04:20
  • 1
    I think that throwNoClassDefError() function is not necessary, as FindClass() function would throw NoClassDefFoundError itself if the class wouldn't be found. – Kurovsky Jun 30 '15 at 10:16
  • what is LPTSTR msgBuf;? I cannot use "LPTSTR" in Android Studio cpp – Raii Jun 12 '21 at 00:35
  • Doesn't ThrowNew actually throw the exception? If so, then why have code to return the the result of ThrowNew? In fact, why does ThrowNew have a result code? – steve May 24 '23 at 16:02
30

I simply use 2 lines:

 sprintf(exBuffer, "NE%4.4X: Caller can %s %s print", marker, "log", "or");
 (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/Exception"), exBuffer);

Produces:

 Exception in thread "main" java.lang.Exception: NE0042: Caller can log or print.
Java42
  • 7,628
  • 1
  • 32
  • 50
  • 5
    I'm given to understand that catching java.lang.Exception is considered poor practice: I'm throwing com.mycompany.JniException where I want a general JNI failure case. – android.weasel Aug 31 '12 at 11:44
  • 33
    @android.weasel: Dude, it's sample code on StackOverflow to illustrate the ThrowNew API. It's not intended to be production code in a mission-critical server. Give the guy a break... – deltamind106 Sep 09 '15 at 15:30
  • Exactly what one needs, from the insight, that acceptance is not driven by the self-purpose of a complex exception concept :) – Sam Ginrich Jan 06 '22 at 19:06
7

My code starts in Java, invokes C++, which then invokes Java back again for things like finding, getting, and setting field values.

In case someone looking for a C++ approach finds this page, I'll plough on with this:

What I'm now doing is wrapping my JNI method bodies up with a C++ try/catch block,

JNIEXPORT void JNICALL Java_com_pany_jni_JNIClass_something(JNIEnv* env, jobject self)
{
    try
    {
        ... do JNI stuff
        // return something; if not void.
    }
    catch (PendingException e) // (Should be &e perhaps?)
    {
        /* any necessary clean-up */
    }
}

where PendingException is declared trivially:

class PendingException {};

and I'm invoking the following method after invoking any JNI from C++, so if the Java exception status indicates an error, I'll bail immediately and let the normal Java exception handling add the (Native method) line to the stack trace, while giving the C++ the opportunity to clean up while unwinding:

PendingException PENDING_JNI_EXCEPTION;
void throwIfPendingException(JNIEnv* env)
{
    if (env->ExceptionCheck()) {
        throw PENDING_JNI_EXCEPTION;
    }
}

My Java stack trace looks like this for a failed env->GetFieldId() call:

java.lang.NoSuchFieldError: no field with name='opaque' signature='J' in class Lcom/pany/jni/JniClass;
  at com.pany.jni.JniClass.construct(Native Method)
  at com.pany.jni.JniClass.doThing(JniClass.java:169)
  at com.pany.jni.JniClass.access$1(JniClass.java:151)
  at com.pany.jni.JniClass$2.onClick(JniClass.java:129)
  at android.view.View.performClick(View.java:4084)

and pretty similar if I call up to a Java method that throws:

 java.lang.RuntimeException: YouSuck
  at com.pany.jni.JniClass.fail(JniClass.java:35)
  at com.pany.jni.JniClass.getVersion(Native Method)
  at com.pany.jni.JniClass.doThing(JniClass.java:172)

I can't talk to wrapping the Java exception within another Java exception from within C++, which I think is part of your question - I've not found the need to do that - but if I did, I'd either do it with a Java-level wrapper around the native methods, or just extend my exception-throwing methods to take a jthrowable and replace the env->ThrowNew() call with something ugly: it's unfortunate Sun didn't provide a version of ThrowNew that took a jthrowable.

void impendNewJniException(JNIEnv* env, const char *classNameNotSignature, const char *message)
{
    jclass jClass = env->FindClass(classNameNotSignature);
    throwIfPendingException(env);
    env->ThrowNew(jClass, message);
}

void throwNewJniException(JNIEnv* env, const char* classNameNotSignature, const char* message)
{
    impendNewJniException(env, classNameNotSignature, message);
    throwIfPendingException(env);
}

I wouldn't consider caching (exception) class constructor references because exceptions aren't supposed to be a usual control flow mechanism, so it shouldn't matter if they're slow. I imagine look-up isn't terribly slow anyway, since Java presumably does its own caching for that sort of thing.

android.weasel
  • 3,343
  • 1
  • 30
  • 41
3

I will put a more complete and general answer for who need a little bit more explanations like I need before.

First is nice to set your method with a Throw Exception so the IDE will ask for try/catch.

public native int func(Param1, Param2, Param3) throws IOException;

I decide for IOException over Exception because of this.

JNIEXPORT int JNICALL Java_YourClass_func
(int Param1, int Param2, int Param3) {
    if (Param3 == 0) { //something wrong
        jclass Exception = env->FindClass("java/lang/Exception");
        env->ThrowNew(Exception, "Can't divide by zero."); // Error Message
    }
    return (Param1+Param2)/Param3;
}
Canato
  • 3,598
  • 5
  • 33
  • 57