2017-10-08 6 views
1

Android에서 JNI를 사용하여 C++ 클래스에서 Java 함수를 호출하려고합니다. 나는 수색하고 찾았지만 정확한 사건을 발견하지 못했다. Java에서 내 C++ 라이브러리의 메서드를 호출 할 수 있지만 그 반대로 수행하는 데 문제가 있습니다. 나는 지금 이틀 동안 그것을 엉망으로 만들었고 시간을 낭비하고있다. 그래서 내가 도와주는 것보다 더 많은 지식을 가질 수 있을까?C++ 클래스 충돌에서 JNI 콜백

전체 목표 : 나중의 C++ 클래스 메소드 (JNI 내보내기가 아님)에서 사용할 수 있도록 Java에서 네이티브 C++ JNI EXPORT 호출에 전달 된 JNIEnv 또는 JavaVM 만 유지하거나 (유효한 JNIEnv 가져 오기 및 첨부).

따라서 Java 클래스 메소드는 JNIEnv * 및 jobject를 전달하여 원시 C++ 메소드를 호출합니다. 정적 클래스 멤버로 C++ 클래스에 저장합니다. 나중에, 그 C++ 클래스의 메소드는 정적 인 멤버를 사용하여 원래 컨텍스트를 통과 한 클래스와 같은 클래스의 Java 메소드를 콜백합니다.

나는 env-> NewGlobalRef (someObj)를 사용해 보았습니다. 그러나 참조 객체의 향후 사용을 성공하게 만들지 만 일부 객체는 여전히 실패하기 때문에 이상합니다.

자바 코드 : 위의

//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는 ++,하지 않습니다

는 여기에 몇 가지 코드입니다. (참고 : 편의상 여기에 모든 것이 공개)

에서 MyClass.h :

#include <string> 
#include <jni.h> 

class MyClass 
{ 
    //ctor 
    //dtor 

    void someCall(std::string) 

    static JNIEnv* envRef; 
    static JavaVM* jvmRef; 
    static jobject objRef; 
}; 

C++ 코드 (참고 : 나는 클래스로 내 네이티브 라이브러리에있는 클래스가 필요하고 정적 호출을 독립되지 않음)

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

나는 af를 시도했다. 다른 접근 방법이지만 모두 충돌을 일으 킵니다. 나는 그것을 사용할 때 DeleteGlobalRef()를하지만 너무 길다. 어떤 통찰력이

편집 # 1 감사합니다 : 마이클의 제안에 따라 경찰 을, 나는주는 JNI_OnLoad 기능을 구현했습니다과의 JavaVM * 거기에서 단지 캐시있다. MyClass :: someCall (std :: string) 메서드 내에서 JavaVM을 사용하여 JNIEnv를 가져오고 env-> FindClass를 사용하여 jclass 객체를 초기화하고 something (String) java 메서드에 대한 methodID를 가져 오지만 콜백을 시도합니다. CallVoidMethod가있는 Java는 여전히 충돌이 발생합니다. 인 MyClass.cpp에서 통근자 "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 *'를 얻을 때 스레드가 이미 VM에 연결되었는지 여부를 추적해야합니다. 왜냐하면 당신은 이전에 그 스레드에서 'AttachCurrentThread'를 호출하지 않는 한'DetachCurrentThread'를 호출해서는 안되기 때문입니다. – Michael

+0

좋아, 난 아주 간단한 JNI_OnLoad() 구현하고 jvm 캐시에. MyClass :: someCall (std :: string msg) 내부에서 JNI_Env를 가져올 수 있으며 FindClass로 jclass를 찾아 methodid를 가져 오지만 CallVoidMethod는 여전히 충돌합니다. – ErnieB

답변

0

좋아는 편집 후 # 오류가 하나의 변화입니다 I CallVoidMethod()에 잘못된 객체를 전달하고있었습니다. 내가 일을 마쳤다. 작동하는 것은 callNative (JNIEnv * env, jobject obj)가 전달한 jobject를 MyClass의 정적 멤버로 저장하고이를 cls 대신 CallVoidMethod에 전달하는 것이다.

이전 호출 : 그것은 더 이상 필요한 후

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); 
} 
+0

장기적으로는 작동하지 않습니다. 'jobjects' acoss JNI 메소드 호출을 저장할 수 없습니다. 'GlobalRef'가 필요합니다. 너는 많은 독서를 한 것처럼 보이지 않는다. 나는 당신이 JNI 참고 문헌을 공부할 것을 제안한다. 그것의 모든. – EJP

+0

위의 내용이 편집되었으며 실제로 작동합니다. – ErnieB

+0

그건 내가 말한거야. 'GlobalRef'를 사용해야합니다. 그렇지 않으면 장기적으로 작동하지 않습니다. – EJP