2017-10-08 1 views
1

J'essaie d'appeler une fonction Java à partir d'une classe C++ en utilisant JNI sur Android. J'ai cherché et cherché mais n'ai pas trouvé mon cas exact. Je peux appeler des méthodes dans ma bibliothèque C++ de Java, mais j'ai des problèmes à faire l'inverse. Je me suis trompé pendant deux jours et je perds du temps, alors quelqu'un d'autre pourrait-il mieux savoir que moi pour m'aider? Objectif complet: Conserver le JNIEnv OU simplement le JavaVM (pour obtenir et attacher un JNIEnv valide plus tard) passé à un appel C++ JNI EXPORT de Java pour une utilisation ultérieure par une méthode de classe C++ (pas un EXPORT JNI). Ainsi, la méthode de classe Java appelle la méthode C++ native, en passant son JNIEnv * et jobject. Stockez-les en tant que membres de classe statiques dans une classe C++. Plus tard, une méthode de cette classe C++ utilise ces membres statiques pour rappeler une méthode Java de la même classe qui a passé son contexte ou quoi que ce soit d'autre.JNI callback to Java à partir de la classe C++ crash

J'ai essayé d'utiliser env-> NewGlobalRef (someObj); mais c'est étrange parce que cela fera en sorte que certaines utilisations futures des objets de référence réussissent, mais certaines échouent toujours.

Voici quelques code:

code 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"); 
} 

Tous les travaux ci-dessus bien, C++ essayer de faire la même chose cependant, ne fonctionne pas. (Note: J'ai besoin de la classe dans ma bibliothèque native en tant que classe et pas autonome appels statiques)

code C++: (Note: pour plus de simplicité ici tout est public)

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

J'ai essayé af eW différentes approches, mais ils causent tous des accidents. Je fais aussi DeleteGlobalRef() quand je l'utilise, mais il se bloque bien avant. Toute idée est appréciée

EDIT # 1: Selon la suggestion de Michael, j'ai mis en place la fonction JNI_OnLoad et ont mis en cache tout le JavaVM * à partir de là. Dans la méthode MyClass :: someCall (std :: string), j'utilise ensuite JavaVM pour obtenir JNIEnv, initialiser un objet jclass en utilisant env-> FindClass et obtenir l'identificateur de méthode pour la méthode java (String), mais en essayant de rappeler Java avec CallVoidMethod entraîne un crash immobile.

OnLoad défini comme extern "C" dans MyClass.cpp:

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

    return JNI_VERSION_1_6; 
} 

Mis à jour MyClass :: someCall Définition:

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

Vous n'êtes pas censé mettre en cache les pointeurs 'JNIEnv'. Le pointeur 'JavaVM' est sûr de mettre en cache, donc vous pouvez le faire par exemple. dans 'JNI_OnLoad'. Et puis vous utilisez ce 'JavaVM *' pour obtenir un 'JNIEnv *' en utilisant 'GetEnv' /' AttachCurrentThread'. – Michael

+0

Notez que lorsque vous obtenez le 'JNIEnv *', vous devez garder une trace si le thread était déjà attaché à la machine virtuelle. Parce que vous ne devez pas appeler 'DetachCurrentThread', sauf si vous avez précédemment appelé' AttachCurrentThread' sur ce thread pendant qu'il était dérouté. – Michael

+0

Ok, j'ai implémenté un super simple JNI_OnLoad() et cache le jvm dedans. Je peux obtenir le JNI_Env de cela alors que dans MyClass :: someCall (std :: string msg), trouver le jclass bien avec FindClass, et obtenir la méthode, mais CallVoidMethod se bloque toujours. – ErnieB

Répondre

0

D'accord, l'erreur après la modification Edit # 1 est que je passait le mauvais objet à CallVoidMethod(). Ce que j'ai fini par faire, qui fonctionne, est de stocker le jobject passé par callNative (JNIEnv * env, jobject obj) en tant que membre statique de MyClass et de le passer à CallVoidMethod au lieu de cls.

Un appel plus tôt:

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

DeleteGlobalRef (objref) est appelé ailleurs une fois qu'il est plus nécessaire. Ensuite, le seul changement était dans la méthode native "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

Cela ne fonctionnera pas non plus à long terme. Vous ne pouvez pas stocker les appels de méthode JNI 'jobjects'. Vous avez besoin d'un 'GlobalRef'. Vous ne semblez pas avoir beaucoup lu. Je vous suggère d'étudier la référence JNI. Tout. – EJP

+0

Ce qui précède a été édité et fonctionne effectivement. – ErnieB

+0

C'est ce que j'ai dit. Vous devez utiliser un 'GlobalRef', sinon cela ne fonctionnera pas à long terme. – EJP