调用 CallVoidMethod 时 JNI 崩溃

发布于 2024-12-06 07:39:26 字数 642 浏览 0 评论 0原文

我正在尝试从 Android 应用程序中的本机 C 代码调用 java 方法。使用 JNI 听起来很简单,但我的代码在最终调用方法本身时总是崩溃。 这是我的代码: 本机 C 代码:

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 代码:

public void showCar(){      
    doSomething()
}

doSomething() 甚至未到达,我可以在那里设置一个断点,该断点永远不会被命中。如上所述,一旦我注释掉 CallVoidMethod 调用,它就不会崩溃,但显然也不会调用 showCar() 。有什么提示吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情仇皆在手 2024-12-13 07:39:26

为您提供 4 个想法:

...

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

您能否确认名称不是“com/path/ to/the/MyClass”,其中类名第一个字符大写,显然名称“class”是保留字。在您的示例中,JNI C 符号名称“Java_com_path_to_my_package_renderStuff”的使用与“com/path/to/the/class”上的 FindClass() 查找之间存在轻微差异。但由于您的 stackoverflow 不是关于 UnsatisfiedLinkageError 我只能猜测您提供的示例与其本身不一致。

使用我的示例,我希望 JNI C 符号名称为“Java_com_path_to_the_MyClass_renderStuff”,FindClass() 查找为“com/path/to/the/MyClass”。使用类的第一个大写字母和方法名称的第一个小写字母对于链接目的可能很重要。

...

您确定传递的“jobj”与您正在查找的“com/path/to/the/class”类型相同吗?也许在 Java 代码中,您可以使用以下内容包装本机:

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

这将确保 Java 代码中的内容不会导致 JVM 崩溃。您还需要调整您的 C 符号名称以将“_1internal”附加到末尾,从而使“Java_com_path_to_the_MyClass_renderStuff_1internal”(额外的“1”字符是有意的)

...

也许尝试在您列出的每个语句之间进行皮带和大括号异常检查:

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

当尝试进行反射(但可能不允许)时,这会拾取诸如安全违规之类的问题。

...

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

删除 FindClass() 调用的另一个想法。这适用于 GetMethodID 所处理的任何类,有点像动态类型/后期绑定。

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.

不回头走下去 2024-12-13 07:39:26

就我而言
我正在调用 Kotlin 函数。要调用 kotlin 函数,您需要在 kotlin 中的函数名称前编写 @JvmStatic

Kotlin代码

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

Cpp代码

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

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);
流殇 2024-12-13 07:39:26

有点不相关,但死灵万一有人发现自己陷入了像我刚才那样的类似困境(或者我将来会这样做)。

我确实有一个像这样的 kotlin 函数,

private fun showCar(msg: String) {      
    meow(msg)
}

它是从像这样的 C 代码调用的

jmethodID FshowCar = (*env)->GetMethodID(env, cls, "showCar", "(Ljava/lang/String;)V");
jstring Jmsg = (*env)->NewStringUTF(env, msg);
(*env)->CallVoidMethod(env, cls, FshowCar , Jmsg);

,并且这会导致出现难以理解的堆栈跟踪的模糊错误。
在发布版本的 Google Play Console 中得到类似的东西,这在调试时从未崩溃

JNI DETECTED ERROR IN APPLICATION: mid == null
    in call to CallVoidMethod

结果发现发布版本正在从类中删除未引用的 Java 方法。这可以通过添加您想要保留在 proguard-rules 中的方法来解决,

并将其添加到 proguard-rules.pro 的末尾来解决它

-keepclassmembers class org.example.myapp.MyClass {
    void showCar(java.lang.String);
}

Somewhat unrelated but necromancing this in case someone finds themselves in a similar predicament like I just did (or I will in the future).

I do have a kotlin function like

private fun showCar(msg: String) {      
    meow(msg)
}

This is called from C code like this

jmethodID FshowCar = (*env)->GetMethodID(env, cls, "showCar", "(Ljava/lang/String;)V");
jstring Jmsg = (*env)->NewStringUTF(env, msg);
(*env)->CallVoidMethod(env, cls, FshowCar , Jmsg);

And this was getting obscure errors with unreadable stacktraces.
Was getting something like this in the Google Play Console for release versions, this never crashed on Debug

JNI DETECTED ERROR IN APPLICATION: mid == null
    in call to CallVoidMethod

Turns out the release version was pruning the unreferenced Java methods from the class. This can be solved adding the methods you want to keep in the proguard-rules

added this at the end of proguard-rules.pro to solve it

-keepclassmembers class org.example.myapp.MyClass {
    void showCar(java.lang.String);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文