Was ist der beste Weg, um JNIEnv zu speichern*

Lesezeit: 7 Minuten

Was ist der beste Weg um JNIEnv zu speichern
lyc001

Ich habe ein Android-Projekt mit JNI. In der CPP-Datei, die eine Listener-Klasse implementiert, gibt es einen Callback x() . Wenn die Funktion x() aufgerufen wird, möchte ich eine andere Funktion in einer Java-Klasse aufrufen. Um diese Java-Funktion aufzurufen, muss ich jedoch auf JNIEnv* zugreifen.

Ich weiß, dass es in derselben cpp-Datei des Callbacks eine Funktion gibt:

static jboolean init (JNIEnv* env, jobject obj) {...}

Soll ich in der cpp-Datei JNIEnv* als Member-Variable speichern, wenn init(..) wird genannt? und später verwenden, wenn der Rückruf erfolgt?

Entschuldigung, aber ich bin ein Anfänger in JNI.

1644046387 852 Was ist der beste Weg um JNIEnv zu speichern
Michael

Caching a JNIEnv* ist keine besonders gute Idee, da Sie nicht dasselbe verwenden können JNIEnv* über mehrere Threads und ist möglicherweise nicht einmal in der Lage, es für mehrere native Aufrufe auf demselben Thread zu verwenden (siehe http://android-developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html)

Schreiben einer Funktion, die die erhält JNIEnv* und hängt den aktuellen Thread bei Bedarf an die VM an, ist nicht allzu schwierig:

bool GetJniEnv(JavaVM *vm, JNIEnv **env) {
    bool did_attach_thread = false;
    *env = nullptr;
    // Check if the current thread is attached to the VM
    auto get_env_result = vm->GetEnv((void**)env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (vm->AttachCurrentThread(env, NULL) == JNI_OK) {
            did_attach_thread = true;
        } else {
            // Failed to attach thread. Throw an exception if you want to.
        }
    } else if (get_env_result == JNI_EVERSION) {
        // Unsupported JNI version. Throw an exception if you want to.
    }
    return did_attach_thread;
}

Die Art und Weise, wie Sie es verwenden würden, ist:

JNIEnv *env;
bool did_attach = GetJniEnv(vm, &env);
// Use env...
// ...
if (did_attach) {
   vm->DetachCurrentThread();
}

Sie könnten dies in eine Klasse packen, die sich an die Konstruktion anfügt und sich bei der Zerstörung löst, im RAII-Stil:

class ScopedEnv {
public:
    ScopedEnv() : attached_to_vm_(false) {
        attached_to_vm_ = GetJniEnv(g_vm, &env_);  // g_vm is a global
    }

    ScopedEnv(const ScopedEnv&) = delete;
    ScopedEnv& operator=(const ScopedEnv&) = delete;

    virtual ~ScopedEnv() {
        if (attached_to_vm_) {
            g_vm->DetachCurrentThread();
            attached_to_vm_ = false;
        }
    }

    JNIEnv *GetEnv() const { return env_; }

private:
    bool attached_to_env_;
    JNIEnv *env_;
};

// Usage:

{
    ScopedEnv scoped_env;
    scoped_env.GetEnv()->SomeJniFunction();
}
// scoped_env falls out of scope, the thread is automatically detached if necessary

Bearbeiten: Manchmal haben Sie möglicherweise einen langwierigen nativen Thread, der eine benötigt JNIEnv* bei mehreren Gelegenheiten. In solchen Situationen möchten Sie möglicherweise vermeiden, den Thread ständig an die JVM anzuhängen und von der JVM zu trennen, aber Sie müssen dennoch sicherstellen, dass Sie den Thread nach der Zerstörung des Threads trennen.

Sie können dies erreichen, indem Sie den Thread nur einmal anhängen und dann angehängt lassen und einen Thread-Zerstörungs-Callback mit einrichten pthread_key_create und pthread_setspecific das kümmert sich um den Anruf DetachCurrentThread.

/**
 * Get a JNIEnv* valid for this thread, regardless of whether
 * we're on a native thread or a Java thread.
 * If the calling thread is not currently attached to the JVM
 * it will be attached, and then automatically detached when the
 * thread is destroyed.
 */   
JNIEnv *GetJniEnv() {
    JNIEnv *env = nullptr;
    // We still call GetEnv first to detect if the thread already
    // is attached. This is done to avoid setting up a DetachCurrentThread
    // call on a Java thread.

    // g_vm is a global.
    auto get_env_result = g_vm->GetEnv((void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (g_vm->AttachCurrentThread(&env, NULL) == JNI_OK) {
            DeferThreadDetach(env);
        } else {
            // Failed to attach thread. Throw an exception if you want to.
        }
    } else if (get_env_result == JNI_EVERSION) {
        // Unsupported JNI version. Throw an exception if you want to.
    }
    return env;
}

void DeferThreadDetach(JNIEnv *env) {
    static pthread_key_t thread_key;

    // Set up a Thread Specific Data key, and a callback that
    // will be executed when a thread is destroyed.
    // This is only done once, across all threads, and the value
    // associated with the key for any given thread will initially
    // be NULL.
    static auto run_once = [] {
        const auto err = pthread_key_create(&thread_key, [] (void *ts_env) {
            if (ts_env) {
                g_vm->DetachCurrentThread();
            }
        });
        if (err) {
            // Failed to create TSD key. Throw an exception if you want to.
        }
        return 0;
    }();

    // For the callback to actually be executed when a thread exits
    // we need to associate a non-NULL value with the key on that thread.
    // We can use the JNIEnv* as that value.
    const auto ts_env = pthread_getspecific(thread_key);
    if (!ts_env) {
        if (pthread_setspecific(thread_key, env)) {
            // Failed to set thread-specific value for key. Throw an exception if you want to.
        }
    }
}

Wenn __cxa_thread_atexit Ihnen zur Verfügung steht, können Sie vielleicht mit einigen dasselbe erreichen thread_local Objekt, das ruft DetachCurrentThread in seinem Destruktor.

  • Hallo @Michael, danke für die nette Möglichkeit, Threads beim Beenden mit TLS automatisch zu trennen – es ist auch nützlich, Threads zu trennen, die Ihr Code nicht besitzt (z. B. einige Framework-Threads).

    – Dmitri Timofejew

    22. Mai ’19 um 14:49 Uhr

  • > Das Zwischenspeichern eines JNIEnv* ist keine besonders gute Idee, […] und kann es möglicherweise nicht einmal für mehrere native Aufrufe im selben Thread verwenden – Ich sehe nicht, dass der verlinkte Artikel darauf hindeutet, dass sich der JNI-Schnittstellenzeiger für einen bestimmten Thread ändern könnte. Der Spezifikation sagt, dass “die VM garantiert denselben Schnittstellenzeiger an eine native Methode weitergibt, wenn sie mehrere Aufrufe an die native Methode von demselben Java-Thread aus durchführt”. Ich glaube nicht, dass es bei der Invocation API anders ist.

    – Dmitri Timofejew

    22. Mai 19 um 14:54 Uhr


  • @DmitryTimofeev: “Ich sehe nicht, dass der verlinkte Artikel darauf hindeutet, dass sich der JNI-Schnittstellenzeiger für einen bestimmten Thread ändern könnte.”. Das bezog sich auf die Formulierung von Google “Sie mag sein gültig, wenn der nächste native Aufruf im selben Thread erfolgt”dh ihre Wahl von “könnte sein” statt “wird sein”.

    – Michael

    22. Mai 19 um 14:57 Uhr


  • Danke, verstanden! Es wäre jedoch seltsam, wenn ihre Implementierung nicht der Spezifikation entsprechen würde. Wie auch immer, es ist definitiv zuverlässiger, es einfach zu verwenden JavaVM#GetEnv (wenn der Code erwartet, dass der Thread angehängt wird) oder JavaVM#AttachCurrentThread um einen gültigen Zeiger zu erhalten.

    – Dmitri Timofejew

    22. Mai 19 um 15:58 Uhr

  • Wenn ich darüber nachdenke, kann ich diese Formulierung eigentlich nicht verstehen – wenn JNIEnv über abgerufen wird AttachCurrentThread (JNIEnv ist seine Ausgang -Parameter), dann besteht die einzige vernünftige Erwartung des Aufrufers darin, dass er ihn – im selben Thread – verwenden kann, solange er angehängt bleibt (d. h. bis dieser Thread aufruft DetachCurrentThread).

    – Dmitri Timofejew

    24. Mai 19 um 13:34 Uhr

@Michael, gibt einen guten Überblick darüber, wie die JNI am besten abgerufen werden kann, indem die JVM zwischengespeichert wird. Für diejenigen, die pthread nicht verwenden möchten (oder nicht können, weil Sie sich auf einem Windows-System befinden) und c++ 11 oder höher verwenden, ist thread_local storage der richtige Weg.

Unten ist ein grobes Beispiel für die Implementierung einer Wrapper-Methode, die ordnungsgemäß an einen Thread angehängt und automatisch bereinigt wird, wenn der Thread beendet wird

JNIEnv* JNIThreadHelper::GetJniEnv() {

    // This method might have been called from a different thread than the one that created
    // this handler. Check to make sure that the JNI is attached and if not attach it to the 
    // new thread.

    // double check it's all ok
    int nEnvStat = m_pJvm->GetEnv(reinterpret_cast<void**>(&m_pJniEnv), JNI_VERSION_1_6);

    if (nEnvStat == JNI_EDETACHED) {

        std::cout << "GetEnv: not attached. Attempting to attach" << std::endl;

        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

        if (m_pJvm->AttachCurrentThread(&m_pJniEnv, &args) != 0) {
            std::cout << "Failed to attach" << std::endl;
            return nullptr;
        }

        thread_local struct DetachJniOnExit {
            ~DetachJniOnExit() {
                m_pJvm->DetachCurrentThread();
            }
        };


        m_bIsAttachedOnAThread = true;

    }
    else if (nEnvStat == JNI_OK) {
        //
    }
    else if (nEnvStat == JNI_EVERSION) {

        std::cout << "GetEnv: version not supported" << std::endl;
        return nullptr;
    }


    return m_pJniEnv;
}

  • Beachten Sie, dass es bereits eine akzeptierte Antwort auf diese Frage gibt. Bitte bearbeiten Sie Ihre Antwort, um sicherzustellen, dass sie andere Antworten verbessert, die bereits in dieser Frage vorhanden sind.

    – hongsy

    27. Januar 20 um 16:37 Uhr

.

780200cookie-checkWas ist der beste Weg, um JNIEnv zu speichern*

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy