Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 871
Lennart Avatar asked Sep 23 '11 14:09

Lennart


2 Answers

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.

like image 120
Darryl Miles Avatar answered Sep 21 '22 16:09

Darryl Miles


In my case I was calling a Kotlin function. And to call a kotlin function you need to write @JvmStatic before function name in kotlin.

Kotlin code

@JvmStatic
fun ReceiveDataFromCpp(data: ShortArray)
{
    Log.d("Kotlin array Return -----> ", "arr: " + data
        .contentToString()
    );
}

Cpp code

JNIEnv * g_env;
g_env = getEnv();


 jmethodID jmethodId = g_env->GetStaticMethodID(clientClass, "ReceiveDataFromCpp",
                                        "([S)V");
 if (jmethodId == NULL)
 {
     return;
 }

 jshortArray dataArray = nullptr;
 dataArray = g_env->NewShortArray(480);
 g_env->SetShortArrayRegion(dataArray, 0, 480, mData);
 g_env->CallStaticVoidMethod(clientRecorderClass, jmethodId, dataArray);
like image 38
Slahuddin Chaudhary Avatar answered Sep 20 '22 16:09

Slahuddin Chaudhary