How to Save Jnienv*

What is the best way to save JNIEnv*

Caching a JNIEnv* is not a particularly good idea, since you can't use the same JNIEnv* across multiple threads, and might not even be able to use it for multiple native calls on the same thread (see http://android-developers.blogspot.se/2011/11/jni-local-reference-changes-in-ics.html)

Writing a function that gets the JNIEnv* and attaches the current thread to the VM if necessary isn't too difficult:

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

The way you'd use it is:

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

You could wrap this in a class that attaches upon construction and detaches upon destruction, RAII-style:

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

Edit: Sometimes you might have a long-ish running native thread that will need a JNIEnv* on multiple occasions. In such situations you may want to avoid constantly attaching and detaching the thread to/from the JVM, but you still need to make sure that you detach the thread upon thread destruction.

You can accomplish this by attaching the thread only once and then leaving it attached, and by setting up a thread destruction callback using pthread_key_create and pthread_setspecific that will take care of calling 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.
}
}
}

If __cxa_thread_atexit is available to you, you might be able to accomplish the same thing with some thread_local object that calls DetachCurrentThread in its destructor.

Is it ok to save JavaVM * and JNIEnv * as a global variable and use it elsewhere?

Is it ok to save JavaVM * as a global variable and use it elsewhere?

Yes.

Is it ok to save JNIEnv * as a global variable and use it elsewhere?

No. That represents a context for a specific JNI method invocation. It is dependent on the current thread, the arguments to the current method, the local references that have been created by the current method, all sorts of things, and it becomes invalid once that JNI method returns.

Keeping a global reference to the JNIEnv environment

You cannot cache the JNIEnv pointer. Read about it here:

The JNI interface pointer (JNIEnv) is valid only in the current thread. Should another thread need to access the Java VM, it must first call AttachCurrentThread() to attach itself to the VM and obtain a JNI interface pointer. Once attached to the VM, a native thread works just like an ordinary Java thread running inside a native method. The native thread remains attached to the VM until it calls DetachCurrentThread() to detach itself.

What you can do is to cache the JavaVM pointer instead.

static JavaVM *jvm;

[JNICALL etc] void init(JNIENv* env, [etc])
{
jint rs = (*env)->GetJavaVM(env, &jvm);
assert (rs == JNI_OK);
}

And then whenever you need then JNIEnv pointer from a context where it is not given you do this:

void someCallback() {
JNIEnv *env;
jint rs = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
assert (rs == JNI_OK);
// Use the env pointer...
}

But whenever you call a native method from Java the env pointer to use is given:

JNIEXPORT jint JNICALL Java_package_Class_method(JNIEnv *env, jobject obj) {
// just use the env pointer as is.
}

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

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

Calling into a saved java object via JNI from a different thread

Accessing object from different threads is fine. Problem is that JNI calls get objects as local references. If you want to keep reference to jobject between JNI calls you need to make it global reference:

myobj = env->NewGlobalRef(jObj);

Remember to free it after you're done using it otherwise garbage collector won't collect it and you'll get memory leaks:

myEnv->DeleteGlobalRef(myobj);

Read about global vs local references here.

Keeping a native object in memory between calls from Java using JNI

It was a multithreading issue. There is no guarantee that JNI will execute native code on the same thread as Java. Making all native functions synchronized resolved the issue.

//initialized Foo library
private native synchronized int fooLibInit();

//start the process
public native synchronized int fooStart(String message);

//continue the process after a delay
public native synchronized int fooContinue(String message);

//retrieve milliseconds to schedule next event
public native synchronized long fooGetMsToNextEvent();

//method that gets called from native code
public static synchronized long getCurrentTime(){
return System.currentTimeMillis();
}

//method that gets called from native code, returning results
public static synchronized void deliverResult(String result){
//display result to the user
}

C++ Callback to Java - Why can't I retrieve JNIEnv in callback class when coming from a different thread?

Your logic in playBackProgress wrong.

The only time your try to attach the current thread is when g_env is non-NULL. But if g_env is non-NULL then GetEnv probably succeeded (you should of course also check that getEnvStat == JNI_OK) and AttachCurrentThread isn't necessary.

The case in which you need to call AttachCurrentThread is when g_env is NULL and getEnvStat is JNI_EDETACHED.

You also need to keep track of whether you did call AttachCurrentThread, since you should call DetachCurrentThread at some point in those cases. See this answer for more info on that.



Related Topics



Leave a reply



Submit