从c++类崩溃到Java的JNI回调

JNI callback to Java from c++ class crashing

本文关键字:JNI 回调 Java c++ 崩溃      更新时间:2023-10-16

我正试图在Android上使用JNI从C++类调用Java函数。我找了又找,但没有找到确切的箱子。我可以从Java调用c++库中的方法,但在执行相反操作时遇到问题。我已经搞砸了两天了,在浪费时间,所以能有比我更了解的人帮我吗?

完整目标:保留从Java传递给本机c++JNI EXPORT调用的JNIEnv,或者只保留JavaVM(稍后获取并附加有效的JNIEnv),以便稍后由c++类方法(而不是JNI EXPORT)使用。

因此,Java类方法调用本机c++方法,传递其JNIEnv*和jobject。将这些作为静态类成员存储在c++类中。稍后,该c++类的一个方法使用这些静态成员来回调最初传递其上下文或其他内容的同一类的Java方法。

我尝试过使用env->NewGlobalRef(someObj);但这很奇怪,因为这将使引用对象的一些未来使用成功,但有些仍然失败。

这里有一些代码:

Java代码:

//this is what I want to call from native code
public void something(String msg)
{
//do something with msg
}
public void somethingElse()
{
callNative();
}
private native void callNatve();
//access native
static
{
System.loadLibrary("someLib");
}

以上所有操作都很好,但是试图做同样操作的c++却没有。(注意:我需要将本机库中的类作为一个类,而不是独立的静态调用)

C++代码:(注意:为了简单起见,这里的一切都是公开的)

MyClass.h:

#include <string>
#include <jni.h>
class MyClass
{
//ctor
//dtor
void someCall(std::string)
static JNIEnv* envRef;
static JavaVM* jvmRef;
static jobject objRef;
};

//////////////////////////////////////////////////////////////////////////////////////MyClass.cpp

#include <MyClass.h>
//static members
MyClass:;:JNIEnv* envRef;
MyClass::JavaVM* jvmRef;
MyClass::jobject objRef;
//this is the method whose instructions are crashing
void MyClass::someCall(std::string msg)
{
//works assuming i call env->NewGlobalRef(MyClass::objRef) or setup/reattach from jvm in exported call or here
jstring passMsg = envRef->NewStringUTF(msg.c_str());
clsRef = envRef->GetObjectClass(objRef);
if(clsRef == NULL)
{
return;
}
//This doesn't cause crash, but if I call FindClass("where/is/MyClass"); it does... strange
jmethodID id = envRef->GetMethodID(clsRef, "something", "(Ljava/lang/String;)V");
if(id == NULL)
{
return;
}
//Crashes
//envRef->CallVoidMethod(clsRef, id, passMsg);
if(envRef->ExceptionCheck())
{
envRef->ExceptionDescribe();
}
//Also crashes
//jvmRef->DetachCurrentThread();
}
//this works
extern "C"
{
JNIEXPORT void JNICALL Java_com_my_project_class_callNative(JNIEnv* env, jobject obj)
{
MyClass::objRef = env->NewGlobalRef(obj);
MyClass::envRef = env;
//tried both
//MyClass::envRef->GetJavaVM(&MyClass::jvmRef);
env->GetJavaVM(&MyClass::jvmRef);

//Tried this
/*
int envStat = MyClass::jvmRef->GetEnv((void**)&MyClass::envRef, JNI_VERSION_1_6);
if(envStat == JNI_EDETACHED)
{
//TODO: LOG
//std::cout << "GetEnv: not attached" << std::endl;
if(MyClass::jvmRef->AttachCurrentThread(&MyClass::envRef, NULL) != 0)
{
//TODO: LOG
//std::cout << "Failed to attach" << std::endl;
}
}else if(envStat == JNI_OK)
{
//
}else if(envStat == JNI_EVERSION)
{
//TODO: LOG
//std::cout << "GetEnv: version not supported" << std::endl;
}
*/
//calling detachcurrentthread here crashes if set above
MyClassObj.someCall(an std::string);
}
}

我尝试了几种不同的方法,但它们都会导致崩溃。当我使用DeleteGlobalRef()时,我也会这样做,但它在那之前就崩溃了。任何见解都值得赞赏

EDIT#1:根据Michael的建议,我已经实现了JNI_OnLoad函数,并且只缓存了其中的JavaVM*。在MyClass::someCall(std::string)方法中,我使用JavaVM获取JNIEnv,使用env->FindClass初始化jclass对象,并获取something(string)java方法的methodID,但尝试使用CallVoidMethod回调java仍会导致崩溃。

OnLoad在MyClass.cpp:中定义为外部"C">

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void* reserved)
{
MyClass::jvmRef = jvm;
return JNI_VERSION_1_6;
}

更新的MyClass::someCall定义:

void MyClass::someCall(std::string msg)
{
//Get environment from cached jvm
JNIEnv* env;
jclass cls;
int envStat = MyClass::jvmRef->GetEnv((void**)&env, JNI_VERSION_1_6);
bool attached = false;
if(envStat == JNI_EDETACHED)
{
//TODO: LOG
if(JavaInterface::jvmRef->AttachCurrentThread(&env, NULL) != 0)
{
//TODO: LOG
// "Failed to attach"
return;
}else if(envStat == JNI_OK)
{
attached = true;
}else if(envStat == JNI_EVERSION)
{
//TODO: LOG
// "GetEnv: version not supported"
}
}
cls = env->FindClass("package/location/project/JavaClass");
if(cls == NULL)
{
//TODO: LOG
return;
}
jmethodID id = env->GetMethodID(cls, "something", "(Ljava/lang/String;)V");
if(id == NULL)
{
return;
}
jstring passMsg = env->NewStringUTF(msg.c_str());
//Crashes
env->CallVoidMethod(cls, id, passMsg);
if(attached)
jvmRef->DetachCurrentThread();
}

好吧,Edit#1更改后的错误是我向CallVoidMethod()传递了错误的对象。我最终做的是将callNative(JNIEnv*env,jobject-obj)传递的jobject存储为MyClass的静态成员,并将其传递给CallVoidMethod而不是cls。

早先的电话:

JNIEXPORT void JNICALL Java_path_to_project_JavaClass_nativeCall(JNIEnv* env, jobject obj)
{
JavaInterface::objRef = env->NewGlobalRef(obj)
}

DeleteGlobalRef(objRef)在不再需要后在其他地方被调用。然后,唯一的变化是在原生的"someCall"方法中:

void MyClass::someCall(std::string msg)
{
//everything here is the same
//Now either of these will work
cls = env->FindClass("com/empsoftworks/andr3ds/NativeInterface");
//cls = env->GetObjectClass(objRef);
//unchanged stuff
//This is what fixed it
env->CallVoidMethod(objRef, id, passMsg);
}