mario_qiu
mario_qiu

Reputation: 105

JNI GetMethodID cause error in native thread

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

Answers (3)

mario_qiu
mario_qiu

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

fadden
fadden

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

Mark Vincze
Mark Vincze

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

Related Questions