androidjava-native-interface

JNI crashes when calling CallVoidMethod


I'm trying to call a java method from native C code in an Android Application. This sounds quite simple with the usage of JNI, but my code always crashes when finally calling the method itself. Here's my code: Native C Code:

JNIEXPORT void JNICALL
Java_com_path_to_my_package_renderStuff(JNIEnv* env,  jobject jobj){
//...
jclass clazz = env->FindClass("com/path/to/the/class");
jmethodID showCar = env->GetMethodID(clazz,"showCar","()V" );
env->CallVoidMethod(jobj,showCar); //If I comment this out, it won't crash
//...
}

Java Code:

public void showCar(){      
    doSomething()
}

doSomething() isn't even reached, I can set a breakpoint there, which will never be hit. And as said above, as soon as I comment out the CallVoidMethod call, it won't crash but obviously not call showCar() either. Any hints?


Solution

  • 4 ideas to provide you:

    ...

    jclass clazz = env->FindClass("com/path/to/the/class");

    Can you confirm the name is not "com/path/to/the/MyClass" where the classname is uppercase 1st character and obviously the name "class" is a reserved word. There is a slight discrepency between the use of the JNI C symbol name "Java_com_path_to_my_package_renderStuff" and the FindClass() lookup on "com/path/to/the/class"in your example. But since your stackoverflow is not a about UnsatisfiedLinkageError I can only guess your example provided is not consistent with itself.

    Using my example I would expect the JNI C symbol name to be "Java_com_path_to_the_MyClass_renderStuff" and the FindClass() lookup on "com/path/to/the/MyClass". The use of uppercase 1st letter of class and lowercase 1st letter of method name might be important for linkage purposes.

    ...

    Are you sure the "jobj" being passed is the same type as the "com/path/to/the/class" you are looking up ? Maybe in your Java code you can wrap your native with:

    public void renderStuff() {
        if((this instanceof com.path.to.the.MyClass) == false)
            throw new RuntimeException("Unexpected class expected: com.path.to.the.MyClass");
         renderStuff_internal();
    }
    private native void renderStuff_internal();
    

    Which will ensure that matter in Java code without causing a JVM crash. You would also need to adjust your C symbol name to append the "_1internal" onto the end making "Java_com_path_to_the_MyClass_renderStuff_1internal" (the extra "1" character is intended)

    ...

    Maybe try belt and braces exception checking in between each statement you list about:

    if(env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
    

    This will pickup things like security violations when trying to do reflection when it might not be allowed.

    ...

     jclass cls = env->GetObjectClass(jobj);  // instead of FindClass
     jmethodID mid = env->GetMethodID(cls, "showCar", "()V");
     if(!mid) return;  // whoops method does not exist
     env->CallVoidMethod(jobj, mid);
    

    Another idea to remove the FindClass() call. This would work with any class that GetMethodID worked on, kind of like dyhamic typing / late-binding.