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.
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.
@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;
}
.