So erhalten Sie den JNI-Schnittstellenzeiger (JNIEnv *) für asynchrone Aufrufe

74

Ich habe erfahren, dass der JNI-Schnittstellenzeiger (JNIEnv *) nur im aktuellen Thread gültig ist. Angenommen, ich habe einen neuen Thread innerhalb einer nativen Methode gestartet. Wie kann es Ereignisse asynchron an eine Java-Methode senden? Da dieser neue Thread keine Referenz von (JNIEnv *) haben kann. Das Speichern einer globalen Variablen für (JNIEnv *) funktioniert anscheinend nicht?

Akhilesh
quelle

Antworten:

77

Bei synchronen Aufrufen mit JNI von Java nach C ++ wurde die "Umgebung" bereits von der JVM eingerichtet, jedoch von einem beliebigen C ++ - Thread in die andere Richtung

Daher müssen Sie diese Schritte ausführen

  • Holen Sie sich den JVM-Umgebungskontext mit GetEnv
  • Fügen Sie den Kontext bei Bedarf mit hinzu AttachCurrentThread
  • Rufen Sie die Methode wie gewohnt mit auf CallVoidMethod
  • abnehmen mit DetachCurrentThread

Vollständiges Beispiel. Hinweis: Ich habe in der Vergangenheit in meinem Blog ausführlicher darüber geschrieben

void callback(int val) {
    JNIEnv * g_env;
    // double check it's all ok
    int getEnvStat = g_vm->GetEnv((void **)&g_env, JNI_VERSION_1_6);
    if (getEnvStat == JNI_EDETACHED) {
        std::cout << "GetEnv: not attached" << std::endl;
        if (g_vm->AttachCurrentThread((void **) &g_env, NULL) != 0) {
            std::cout << "Failed to attach" << std::endl;
        }
    } else if (getEnvStat == JNI_OK) {
        //
    } else if (getEnvStat == JNI_EVERSION) {
        std::cout << "GetEnv: version not supported" << std::endl;
    }

    g_env->CallVoidMethod(g_obj, g_mid, val);

    if (g_env->ExceptionCheck()) {
        g_env->ExceptionDescribe();
    }

    g_vm->DetachCurrentThread();
}
Adam
quelle
13
Die einzigen Teile dieser Antwort, die sich auf die Frage beziehen GetEnv, sind AttachCurrentThreadund DetachCurrentThreadwerden nicht einmal erklärt.
main--
1
Das löst mein Problem vollständig, aber die obige Erklärung von main war wunderschön
Akhilesh
g_obj = env-> NewGlobalRef (obj); -------> wirft Fehler: Anfrage für Mitglied 'NewGlobalRef' in etwas, das keine Struktur oder Vereinigung ist
Matical
habe den obigen Fehler selbst behoben. Ich habe die Datei als .c gespeichert, aber die C ++ - Syntax verwendet.
Matical
Vielen Dank! Dies scheint ein sehr hartnäckiges, aber zeitweise auftretendes Problem in meiner App behoben zu haben.
Alan Kinnaman
88

Sie können einen Zeiger auf die JVM ( JavaVM*) mit erhalten JNIEnv->GetJavaVM. Sie können diesen Zeiger sicher als globale Variable speichern. Später im neuen Thread können Sie entweder AttachCurrentThreadden neuen Thread an die JVM anhängen, wenn Sie ihn in C / C ++ erstellt haben, oder einfach, GetEnvwenn Sie den Thread in Java-Code erstellt haben, was ich nicht annehme, da JNI Ihnen JNIEnv*dann und dann übergeben würde Sie würden dieses Problem nicht haben.

    // JNIEnv* env; (initialized somewhere else)
    JavaVM* jvm;
    env->GetJavaVM(&jvm);
    // now you can store jvm somewhere

    // in the new thread:
    JNIEnv* myNewEnv;
    JavaVMAttachArgs args;
    args.version = JNI_VERSION_1_6; // choose your JNI version
    args.name = NULL; // you might want to give the java thread a name
    args.group = NULL; // you might want to assign the java thread to a ThreadGroup
    jvm->AttachCurrentThread((void**)&myNewEnv, &args);
    // And now you can use myNewEnv
Main--
quelle
16
Beachten Sie, dass das zweite Argument AttachCurrentThreadNULL sein kann, wenn Sie keine speziellen Einstellungen benötigen, und Sie sollten auf jeden Fall anrufen, DetachCurrentThreadwenn Sie fertig sind, wenn Sie zunächst nicht angehängt wurden (andernfalls sammeln Sie nutzlose ThreadObjekte, die dies können werde nie GC'd sein).
Technomage
Die Definition der AttachCurrentThread-Funktion ändert sich im NDK r9. Hier ist der Dokumentlink. docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/…
Zephyr
Sollte nicht als erster Parameter JNIEnv->GetJavaVMakzeptiert envwerden?
Denis Kniazhev
@DenisKniazhev ist im envGrunde der erste Parameter, da er über den Zeiger aufgerufen wird . GetJavaVMenv
main--
2
@ DenisKniazhev Richtig. C hat keine Klassen, daher können Sie keine Methode für einen Zeiger aufrufen. In C ++ stellt JNI Wrapper-Klassen bereit, die den env-Zeiger automatisch übergeben. In C müssen Sie ihn jedoch manuell übergeben.
main--