Reputation: 105
In android I use pthread_create to create a native thread , then in the Callback procedure ,call FindClass to get a Java class. but it does'nt work. I get tips from android jni tips I found the solution in FindClass from any thread in Android JNI
I modify 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);
.....
program is exit at env->GetMethodID. I get this error:
Invalid indirect reference 0x40d8bb20 in decodeIndirectRef.
if i remove resultClass = env->FindClass(name);
from findClass, it's OK. "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;
}
any confilct between env->FindClass(name);
and env->CallObjectMethod to loadClass
?
Is it a bug? What can be done to fix the problem?
Upvotes: 4
Views: 5962
Reputation: 105
Sorry, I make a silly mistake.
resultClass = env->FindClass(name);
env->FindClass(name) throw an NoClassDefFoundException and return NULL. Application continues on. VM abort in jmethodID methodID = env->GetMethodID(cls, "name", "()Ljava/lang/String;");
coincidentally .
May be one solution is:
jclass findClass(const char* name) {
JNIEnv *env = getEnv();
jclass resultClass = 0;
if(env)
{
resultClass = env->FindClass(name);
jthrowable mException = env->ExceptionOccurred();
if (mException )
{
env->ExceptionDescribe();
env->ExceptionClear();
return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name)));
}
}
return resultClass;
}
I found some helpful usage in http://android.wooyd.org/JNIExample Thanks!
Upvotes: 2
Reputation: 52303
Don't do this:
gClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
In particular, never take a local reference (which is what CallObjectMethod
returns) and store it in anything but a local variable.
You need to acquire a global reference, using NewGlobalRef
, if you want to access that value outside the function that acquired the local reference. Once execution returns to the VM in that thread, the local reference is invalidated.
See the "local and global references" section in the JNI Tips document.
Upvotes: 2
Reputation: 8033
I use a bit different code to get the actual JNIEnv*
pointer (it is based on a code sample posted here on SO somewhere). This approach proved to be working reliably.
class BaseJNI
{
protected:
BaseJNI(JNIEnv * env)
{
int ret = env->GetJavaVM(&jvm);
if( ret != JNI_OK )
{
LOG_INFO("Could not get JavaVM: %d", ret);
throw;
}
}
JNIEnv * GetEnv()
{
JNIEnv * env;
// double check it's all ok
int ret = jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
if (ret == JNI_OK)
return env;
if (ret == JNI_EDETACHED)
{
LOG_INFO("GetEnv: thread is not attached, trying to attach");
ret = jvm->AttachCurrentThread(&env, nullptr);
if( ret == JNI_OK )
{
LOG_INFO("GetEnv: attach successful");
return env;
}
LOG_INFO("Cannot attach JNI to current thread: %d", ret);
return nullptr;
}
LOG_INFO("could not get JNI ENV: %d", ret);
return nullptr;
}
protected:
JavaVM *jvm;
};
This class should be instantiated in your main thread when you surely have a valid JNIEnv*
pointer, then it can be used from background threads as well.
Could you try to get the JNIEnv*
pointer with the above code and see if the pointer is acquired properly?
Upvotes: 1