90

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?

user207421
  • 305,947
  • 44
  • 307
  • 483
Akhilesh
  • 1,400
  • 3
  • 15
  • 20

2 Answers2

106

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
Cromax
  • 1,822
  • 1
  • 23
  • 35
main--
  • 3,873
  • 1
  • 16
  • 37
  • 18
    Note that the second argument to `AttachCurrentThread` can be NULL if you don't need any special settings, and you should be sure to call `DetachCurrentThread` when you're finished if you weren't attached to begin with (otherwise you'll accumulate useless `Thread` objects that can't ever be GC'd). – technomage Oct 15 '12 at 18:30
  • the definition of AttachCurrentThread function changes in the NDK r9. here is the document link. http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/invocation.html – Zephyr Jun 06 '14 at 21:29
  • Shouldn't `JNIEnv->GetJavaVM` accept `env` as the first parameter? – Denys Kniazhev-Support Ukraine Jul 07 '14 at 12:59
  • @DenisKniazhev `env` basically **is** the first parameter because `GetJavaVM` is invoked on the `env` pointer. – main-- Jul 07 '14 at 13:19
  • @main-- should it work in NDK while using pure C? I get "request for member 'GetJavaVM' in something not a structure or union" unless I call it like `(*env)->GetJavaVM(env, &jvm);` – Denys Kniazhev-Support Ukraine Jul 07 '14 at 13:45
  • 2
    @DenisKniazhev Correct. C doesn't have classes, so you can't invoke a method on a pointer. In C++, JNI provides wrapper classes that automatically pass the env pointer, but in C you have to pass it manually. – main-- Jul 07 '14 at 20:29
92

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();
}
Adam
  • 35,919
  • 9
  • 100
  • 137
  • 14
    The only pieces of this answer that are related to the question are `GetEnv`, `AttachCurrentThread` and `DetachCurrentThread` and they are not even explained. – main-- Oct 15 '12 at 18:08
  • 1
    That solves my problem completely, however above explanation by main was beautiful – Akhilesh Oct 15 '12 at 18:14
  • g_obj = env->NewGlobalRef(obj); -------> is throwing error: Request for member 'NewGlobalRef' in something not a structure or union – Matical Jun 29 '15 at 15:00
  • resolved the above error myself. I saved the file as .c but was using c++ syntax. – Matical Jun 29 '15 at 15:13
  • Thank you! This seems to have fixed a very stubborn but intermittent problem in my app. – Alan Kinnaman Apr 05 '16 at 22:50
  • @Akhilesh quite nice explanation about global and local scope of references in JNI is in https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-7. – pevik Apr 08 '16 at 09:16
  • Shouldn't `DetachCurrentThread()` be conditioned to the execution of `AttachCurrentThread()` ? – Paulo Costa Dec 06 '18 at 19:46
  • Where do you get the `g_vm` variable from? – Razvan Cristian Lung Oct 22 '20 at 14:47
  • Wonder about the origin of `g_obj`; EDIT found in **blog** reference http://adamish.com/blog/archives/327 – Sam Ginrich May 17 '21 at 18:59
  • This looks like a very expensive operation. Has anyone measured it? – Lothar Jan 06 '22 at 02:39
  • There is another use case for this solution: You want to trigger a callback using a global reference, when in the callback function by design the current environment in not available. – Sam Ginrich Jan 08 '22 at 18:18