Как получить указатель интерфейса JNI (JNIEnv *) для асинхронных вызовов

Я узнал, что указатель интерфейса JNI (JNIEnv *) действителен только в текущем потоке. Предположим, я начал новый поток внутри собственного метода; как он может асинхронно отправлять события методу Java? Поскольку этот новый поток не может иметь ссылку (JNIEnv *). Сохранение глобальной переменной для (JNIEnv *), по-видимому, не сработает?

Ответ 1

В рамках синхронных вызовов с использованием JNI от Java до С++ "среда" уже была настроена JVM, однако, перемещаясь в другом направлении из произвольного потока С++, возможно, она не была

Поэтому вам необходимо выполнить следующие шаги

  • получить контекст среды JVM с помощью GetEnv
  • при необходимости свяжите контекст с помощью AttachCurrentThread
  • вызовите метод как обычно, используя CallVoidMethod
  • отсоединить с помощью DetachCurrentThread

Полный пример. Примечание. Я писал об этом в прошлом более подробно на blog

void callback(int val) {
    JNIEnv * g_env;
    // double check it 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();
}

Ответ 2

Вы можете получить указатель на JVM (JavaVM*) с JNIEnv->GetJavaVM. Вы можете безопасно хранить этот указатель как глобальную переменную. Позже в новом потоке вы можете использовать AttachCurrentThread для присоединения нового потока к JVM, если вы создали его на C/С++ или просто GetEnv, если вы создали поток в java-коде, который я не предполагаю, поскольку JNI передаст вам JNIEnv*, и у вас не было бы этой проблемы.

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