我想在Android上使用JNI從C++類調用Java函數。我搜查了,但沒有找到我的確切情況。我可以從Java的C++庫中調用方法,但是遇到相反的問題。我已經搞砸了兩天了,現在正在浪費時間,所以有人可以比我幫助我更瞭解情況嗎?從C++類崩潰的JNI回調到Java
完整目標:保留JNIEnv或僅保留JavaVM(以獲取並附加有效的JNIEnv)傳遞給Java的本地C++ JNI EXPORT調用,以供C++類方法(而非JNI EXPORT)稍後使用。
所以,Java類方法調用本地C++方法,傳遞它的JNIEnv *和jobject。將它們作爲靜態類成員存儲在C++類中。後來,該C++類的一個方法使用這些靜態成員來回調最初傳遞其上下文或相同類的Java方法。我試過使用env-> NewGlobalRef(someObj);我試過使用env-> NewGlobalRef(someObj);我試過使用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(),但在此之前崩潰了。任何見解表示讚賞
編輯#1: 按照邁克爾的建議,我已經實現了JNI_OnLoad函數,並從那裏只是緩存在JavaVM *。在MyClass :: someCall(std :: string)方法中,我使用JavaVM獲取JNIEnv,使用env-> FindClass初始化一個jclass對象,並獲取something(String)java方法的methodID,但嘗試回調帶有CallVoidMethod的Java仍然會導致崩潰。在MyClass.cpp定義爲extern 「C」
的OnLoad:
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();
}
你不應該緩存'JNIEnv'指針。 'JavaVM'指針可以安全地緩存,所以你可以做到這一點,例如在'JNI_OnLoad'中。然後使用'JavaVM *'使用'GetEnv' /'AttachCurrentThread'來獲得'JNIEnv *'。 – Michael
請注意,當您獲得'JNIEnv *'時,您需要跟蹤線程是否已連接到虛擬機。因爲除非先前在該線程上加載了AttachCurrentThread,否則不能調用DetachCurrentThread。 – Michael
好吧,我已經實現了一個超級簡單的JNI_OnLoad()並緩存了它的jvm。我可以從MyClass :: someCall(std :: string msg)中獲取JNI_Env,使用FindClass查找jclass的罰款,並獲取methodid,但CallVoidMethod仍然崩潰。 – ErnieB