2017-10-08 83 views
1

我想在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(); 
} 
+1

你不應該緩存'JNIEnv'指針。 'JavaVM'指針可以安全地緩存,所以你可以做到這一點,例如在'JNI_OnLoad'中。然後使用'JavaVM *'使用'GetEnv' /'AttachCurrentThread'來獲得'JNIEnv *'。 – Michael

+0

請注意,當您獲得'JNIEnv *'時,您需要跟蹤線程是否已連接到虛擬機。因爲除非先前在該線程上加載了AttachCurrentThread,否則不能調用DetachCurrentThread。 – Michael

+0

好吧,我已經實現了一個超級簡單的JNI_OnLoad()並緩存了它的jvm。我可以從MyClass :: someCall(std :: string msg)中獲取JNI_Env,使用FindClass查找jclass的罰款,並獲取methodid,但CallVoidMethod仍然崩潰。 – ErnieB

回答

0

好的,編輯#後的誤差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(坷Ref)在其他地方調用。然後,唯一的變化是原生的「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); 
} 
+0

從長遠來看,這不起作用。您不能存儲'jobjects' acoss JNI方法調用。你需要一個'GlobalRef'。你似乎沒有做太多的閱讀。我建議你研究JNI參考。所有的。 – EJP

+0

以上已編輯並確實工作。 – ErnieB

+0

這就是我所說的。你需要使用'GlobalRef',否則從長遠來看它將不起作用。 – EJP