How to obtain JNI interface pointer (JNIEnv *) for asynchronous calls

JavaCMultithreadingJava Native-Interface

Java Problem Overview


I have learned that the JNI interface pointer (JNIEnv *) is only valid in the current thread. Suppose I started a new thread inside a native method; how it can asynchronously send events to a Java method? As this new thread can't have a reference of (JNIEnv *). Storing a global variable for (JNIEnv *) apparently will not work?

Java Solutions


Solution 1 - Java

You can obtain a pointer to the JVM (JavaVM*) with JNIEnv->GetJavaVM. You can safely store that pointer as a global variable. Later, in the new thread, you can either use AttachCurrentThread to attach the new thread to the JVM if you created it in C/C++ or simply GetEnv if you created the thread in java code which I do not assume since JNI would pass you a JNIEnv* then and you wouldn't have this problem.

	// 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

Solution 2 - Java

Within synchronous calls using JNI from Java to C++ the "environment" has already been setup by the JVM, however going in the other direction from an arbitrary C++ thread it may not have been

Therefore you need to follow these steps

  • get hold of the JVM environment context using GetEnv
  • attach the context if necessary using AttachCurrentThread
  • call the method as normal using CallVoidMethod
  • detach using DetachCurrentThread

Full example. Note I have written about this in the past in more detail on my blog

JavaVM* g_vm;
env->GetJavaVM(&g_vm);

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAkhileshView Question on Stackoverflow
Solution 1 - Javamain--View Answer on Stackoverflow
Solution 2 - JavaAdamView Answer on Stackoverflow