JNI GetMethodID causes an error in the native thread
In android, I create a local thread using pthread_create, and then during the callback, call FindClass to get a Java class. But it doesn’t work. I got hints from android jni tips
I FindClass from A solution was found in any thread in Android JNI
I modified it for my project like this
[edit].
JavaVM* gJvm = nullptr;
static jobject gClassLoader;
static jmethodID gFindClassMethod;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
gJvm = pjvm; cache the JavaVM pointer
auto env = getEnv();
replace with one of your classes in the line below
auto randomClass = env->FindClass("com/example/RandomClass");
jclass classClass = env->GetObjectClass(randomClass);
auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
"()Ljava/lang/ClassLoader;" );
gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
gClassLoader = env->NewGlobalRef(gClassLoader);
gFindClassMethod = env->GetMethodID(classLoaderClass, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;" );
check. this is ok
jclass cls = env->FindClass("com/example/data/DataTest");
jmethodID methoID = env->GetMethodID(cls, "name", "()Ljava/lang/String;" );
LOG_INFO("cls is %p\n", cls);
return JNI_VERSION_1_6;
}
JNIEnv* getEnv() {
JNIEnv *env;
int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
if(status < 0) {
status = gJvm->AttachCurrentThread(&env, NULL);
if(status < 0) {
return nullptr;
}
}
return env;
}
jclass findClass(const char* name) {
JNIEnv *env = getEnv();
jclass resultClass = 0;
if(env)
{
resultClass = env->FindClass(name);
it can not found class in native thread, use loadClass method
if (!resultClass)
{
LOG_INFO("can not find the class");
return value is not null.
return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name)));
}
}
return resultClass;
}
.......
thread callback
void *proc(void *)
{
JNIEnv *env = getEnv();
jclass cls = findClass("com/example/data/DataTest");
if (cls)
{
LOG_INFO("GetMethodID");
crash
jmethodID methodID = env->GetMethodID(cls, "name", "()Ljava/lang/String;" );
LOG_INFO("proc tag is %p\n", tag);
}
}
.....
pthread_create(&handle, NULL, proc, 0);
.....
The program exits at env->GetMethodID. I’m getting this error:
Invalid indirect reference 0x40d8bb20 in decodeIndirectRef.
If I remove resultClass = env->FindClass(name) from findClass;
, that’s it. “Proc tag is” can be printed.
//correct
jclass findClass(const char* name) {
JNIEnv *env = getEnv();
jclass resultClass = 0;
if(env)
{
if (!resultClass)
{
return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name)));
}
}
return resultClass;
}
in env->FindClass(name);
Is there any conflict with env->CallObjectMethod to loadClass
?
Is this a bug? What can be done to solve this problem?
Solution
Don’t do this:
gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
In particular, never store local references (what CallObjectMethod
returns) anywhere other than local variables.
If you want to access the value outside of the function that gets the local reference, you need to use NewGlobalRef
to get the global reference. Once execution returns to the VM in that thread, the local reference is invalidated.
See the Local and Global References section documentation in JNI Tips.