JNIEnv* env, threads (not what you think)

736 views
Skip to first unread message

gadget

unread,
May 21, 2012, 8:42:02 PM5/21/12
to android-ndk
So here's an odd one.

I have a native code with functions that call into a Java class. One
of the native functions is defined as a native member of the Java
class. All native functions are called from the UI thread, with the
exception of the java_callback.

NOTE: I'm simplifying the syntax for readability purposes

native.cpp:
-----------------------------------------------
void main()
{
// -- instantiate class
jobject _oMyClass = globalEnv->NewObject( jclsMyClass );
jobject oMyClass = globalEnv->NewGlobalRef( _oMyClass );
globalEnv->DeleteLocalRef(_oMyClass);

// -- call methods
globalEnv->CallVoidMethod(oMyClass, jmidMethodA);
globalEnv->CallVoidMethod(oMyClass, jmidMethodB);

// -- do some other stuff
}

void func_a()
{
globalEnv->CallVoidMethod(oMyClass, oMethodA);
}

void func_b()
{
globalEnv->CallVoidMethod(oMyClass, oMethodB);
}

void java_callback(JNIEnv* env, ... , jstring myString)
{
mutex_acquire();

// -- note that I'm using local JNIEnv*, rather than global
env->GetStringUTFChars(jstrItemID, NULL);

mutex_release();
}

myClass.java
------------------------------
public class myClass {

// -- native method declaration
private native void java_callback(String myString);

// -- methodA
public void methodA() {
java_callback("hello");
}

// -- methodB
public void methodB() {
new Thread(new Runnable() {
public void run() {
java_callback("hello");
}
}).start();
}
}

Ok, so what do we have so far? methodA calls the callback directly,
while methodB starts a new thread that in turn calls the callback. I
have a mutex to deal with concurrency, and I am using JNIEnv* from the
argument that is passed into the callback.

In other words -- as I am not sure which thread will call into the
native callback, I do not use globalEnv, which would be wrong, but
instead rely on dalvik to pass me the proper JNIEnv* as the argument.

However, in both instances (methodA and methodB) what ends up getting
passed into the callback is globalEnv ! This is of course not what I
had intended.

The only thing I can think of is since the java class gets created on
the UI thread, the native member function gets its JNIEnv* (the first
argument to every native function this class will call) hardcoded to
that the UI thread, instead of substituting it dynamically as the
native member function is called (from a diff thread, perhaps).

Is this true?

Mārtiņš Možeiko

unread,
May 21, 2012, 9:42:09 PM5/21/12
to andro...@googlegroups.com
JNI doesn't say that JNIEnv* pointer must be different for each
thread. Maybe it will be the same. But you can not rely on this fact,
because sometimes it will be different.

--
Mārtiņš Možeiko
> --
> You received this message because you are subscribed to the Google Groups "android-ndk" group.
> To post to this group, send email to andro...@googlegroups.com.
> To unsubscribe from this group, send email to android-ndk...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/android-ndk?hl=en.
>

Elliott Hughes

unread,
May 22, 2012, 10:49:37 AM5/22/12
to andro...@googlegroups.com
no. you're confused / doing something wrong.

you might find it helpful to run on >= ICS with CheckJNI turned on. it's good at catching JNIEnv-related bugs.

gadget

unread,
May 22, 2012, 6:01:17 PM5/22/12
to android-ndk
Ok, so I did some more digging, it seems that I was wrong about the
passed in JNIEnv* being incorrect. But the problem remains. First let
me show what I did.

In java_callback() I added some code, so that now it looks like this:

void java_callback(JNIEnv* env, ... , jstring myString) {

JNIEnv* localEnv;
jint status = gspGlue->vm->GetEnv((void**) &localEnv,
JNI_VERSION_1_6);
if (status == 0) {
LOGD("determined local env: %p (vs arg env: %p)",
localEnv, env);
env = localEnv;
} else {
LOGW("GetEnv failed, status: 0x%x", status);
}

mutex_acquire();

// -- note that I'm using local JNIEnv*, rather than global
env->GetStringUTFChars(jstrItemID, NULL);

mutex_release();
}

and indeed, the env that is passed in is equal to localEnv that is
obtained from the vm for both UI thread and auxiliary thread. However,
when called from the auxiliary thread GetStringUTFChars triggers an
error message:

05-22 14:45:56.790: D/Purchase(29492): determined local env: 0x271c8d0
(vs arg env: 0x271c8d0)
...
05-22 14:45:56.790: E/dalvikvm(29492): JNI ERROR: env->self != thread-
self (0x1f31830 vs. 0x2730d30); auto-correcting

It seems that dalvik wants me to use UI thread's env.. thoughts?

gadget

unread,
May 22, 2012, 6:06:23 PM5/22/12
to android-ndk
here's a better comparison of two cases of log outputs. ThreadId 1 is
the UI thread, ThreadId 475 is the auxiliary thread.

Aux
--------------
W/JavaPurchase(29492): about to call
Cross_iPurchase_EventPacker_Native
W/JavaPurchase(29492): from thread: 475
D/CPurchase(29492): determined local env: 0x271c8d0 (vs arg env:
0x271c8d0)
E/dalvikvm(29492): JNI ERROR: env->self != thread-self (0x1f31830 vs.
0x2730d30); auto-correcting

UI
----------------
W/JavaPurchase(29395): about to call
Cross_iPurchase_EventPacker_Native
W/JavaPurchase(29395): from thread: 1
D/CPurchase(29395): determined local env: 0x1f2e2c8 (vs arg env:
0x1f2e2c8)
<no JNI ERROR>

Elliott Hughes

unread,
May 23, 2012, 12:26:16 PM5/23/12
to andro...@googlegroups.com
"adb logcat -v threadtime" might help you understand what you're doing wrong, because it'll show the tid as well as the pid.

gadget

unread,
May 23, 2012, 1:08:40 PM5/23/12
to andro...@googlegroups.com
.. but doing this (inefficient as it may be) should guarantee that the JNIEnv* (localEnv, that is) is valid
regardless of where the callback came from, no?


void java_callback(JNIEnv* env, ... , jstring myString) {

  JNIEnv* localEnv;
  jint status = gspGlue->vm->GetEnv((void**) &localEnv,
                                    JNI_VERSION_1_6);

  ...

}

a1

unread,
May 24, 2012, 5:35:52 AM5/24/12
to andro...@googlegroups.com
Not really, GetEnv returns pointer to env for threads that are attached to VM. What you want to do is to call AttachCurrentThread to make get valid JNIEnv.

--
Bart

gadget

unread,
May 24, 2012, 12:55:56 PM5/24/12
to andro...@googlegroups.com
but this is already a Java thread, no attachment should be necessary
Reply all
Reply to author
Forward
0 new messages