Android - JNI crash in call to CallVoidMethodV

Viewed 479

I have JNI native code that calls C++ and it starts background threads. In one of those threads, JNI is called but it ends with error:

JNI DETECTED ERROR IN APPLICATION: mid == null in call to CallVoidMethodV
Runtime aborting...

The code crashes on call:

lockedEnv->CallVoidMethod(cachedCaller,
                javaCallback,
                snapshotData);

Interesting is that the problem (crash) orrurs in the Release build of C++ with optimizations while debug mode works correctly. Also, I am using the JNIClass wrapper on other places and no problems occur.

My JNI method is:

JNIClass * snapshotDataClass;

#define RETURN(type) JNIEXPORT type JNICALL
#define PACKAGE(fn) Java_com_example_NativeAPI__##fn

jbyteArray convertToJavaByteArray(JNIEnv *env, const jbyte *array, int elementCount) {
    if (elementCount == 0){
        return env->NewByteArray(1);
    }
    if (array == nullptr){
        return env->NewByteArray(elementCount);
    }

    jbyteArray res = env->NewByteArray(elementCount);
    env->SetByteArrayRegion(res, 0, elementCount, array);

    return res;
}


RETURN(void) PACKAGE(getData)(JNIEnv *env, jobject thiz, jobject caller) {

    if (dataClass == nullptr) {
        dataClass = new JNIClass(env, "com/example/Data");
        dataClass ->AddMethod("<init>", "()V");        
        dataClass ->AddMethod("setData", "([B)V");
    }


    jclass callbackClass = (jclass) env->NewGlobalRef(env->FindClass("com/example/Data/DataListener"));
    jobject cachedCaller = (jobject) env->NewGlobalRef(caller);
    jmethodID javaCallback = env->GetMethodID(callbackClass, "onSnapshotRetrieved", "(Lcom/example/Data;)V");

       CppGetData([=](Result res) {
        //this is called from thread
        JNIEnv *lockedEnv = dataClass->LockEnv();

        dataClass->InitNewInstance();
        dataClass->RunVoid("setData", convertToJavaByteArray(lockedEnv, reinterpret_cast<const jbyte *>(res.data),  res.size));
           
        lockedEnv->CallVoidMethod(cachedCaller,
            javaCallback,
            dataClass->instanceRef);

        lockedEnv->DeleteGlobalRef(callbackClass);
        lockedEnv->DeleteGlobalRef(cachedCaller);

        dataClass->UnLockEnv();
    });
}

And JNI class is a helper wrapper:

struct SimpleJNIClass {
    JavaVM* vm = nullptr;
    jobject instanceRef = nullptr;
    JNIEnv *lockedEnv = nullptr;

    SimpleJNIClass(JNIEnv *env, jobject instanceRef){       
        env->GetJavaVM(&this->vm);
        this->instanceRef = (jobject)env->NewGlobalRef(instanceRef);
    };

    SimpleJNIClass(JNIEnv * env){
        env->GetJavaVM(&this->vm);
    };

    SimpleJNIClass(const SimpleJNIClass & ) = delete;

    virtual ~SimpleJNIClass() {
        if (this->vm == nullptr) {
            return;
        }

        if (JNIEnv * env = GetEnv()){
            env->DeleteGlobalRef(this->instanceRef);
            this->instanceRef = nullptr;
            FreeEnv(env);
        }
    };


    JNIEnv * LockEnv() {
        this->envMutex.lock();
        this->lockedEnv = this->GetEnv();
        return this->lockedEnv;
    };

    void UnLockEnv() {
        this->lockedEnv = nullptr;
        this->FreeEnv(this->lockedEnv);
        this->envMutex.unlock();
    };


protected:
    bool attachCurrentThread = false;
    std::recursive_mutex envMutex;

    JNIEnv * GetEnv(){
        if (this->lockedEnv != nullptr){
            return this->lockedEnv;
        }

        JNIEnv * env = nullptr;
        int getEnvStat = vm->GetEnv((void **)&env, JNI_VERSION_1_4);

        if (getEnvStat == JNI_EDETACHED){
            if (vm->AttachCurrentThread(&env, NULL) != 0){
                MY_LOG_ERROR("AttachCurrentThread was not successful. "
                                     "This may be due to the thread being attached already to another JVM instance.");
            }
            else{
                attachCurrentThread = true;
            }
        }
        else if (getEnvStat == JNI_OK){
            //no need to attach, already attached
        }
        else if (getEnvStat == JNI_EVERSION){
            MY_LOG_ERROR("GetEnv: version not supported");
        }

        return env;
    };

    void FreeEnv(JNIEnv * env){
        if (this->lockedEnv != nullptr){
            return;
        }
        if (attachCurrentThread){
            vm->DetachCurrentThread();
            attachCurrentThread = false;
        }
    };
};


struct JNICallback : public SimpleJNIClass {    
    jmethodID callbackMethod = nullptr;

    JNICallback(JNIEnv *env, jobject classRef,
        const std::string & methodName, const std::string & methodSignature)
        : SimpleJNIClass(env, classRef){
        jclass callbackClass = (jclass)env->GetObjectClass(this->instanceRef);
        this->callbackMethod = env->GetMethodID(callbackClass, methodName.c_str(), methodSignature.c_str());
    };

    JNICallback(const SimpleJNIClass & ) = delete;

    virtual ~JNICallback() {
        callbackMethod = nullptr;
    };

};


struct JNIClass : public SimpleJNIClass {    
    jclass callbackClass;
    std::unordered_map<std::string, jmethodID> callbacks;    

    JNIClass(JNIEnv *env, jobject classRef)
        : SimpleJNIClass(env, classRef){
        this->callbackClass = (jclass)env->NewGlobalRef((jclass)env->GetObjectClass(this->instanceRef));
    };

    JNIClass(JNIEnv *env, const std::string & classRefName)
        : SimpleJNIClass(env){
        this->callbackClass = (jclass)env->NewGlobalRef(env->FindClass(classRefName.c_str()));
    };

    virtual ~JNIClass() {
        if (JNIEnv * env = this->GetEnv()){
            env->DeleteGlobalRef(callbackClass);
            callbackClass = nullptr;
            this->FreeEnv(env);
        }

        callbacks.clear();
    };



    void AddMethod(const std::string & methodName, const std::string & methodSignature) {
        if (this->lockedEnv != nullptr){
            this->callbacks[methodName] = this->lockedEnv->GetMethodID(this->callbackClass, methodName.c_str(), methodSignature.c_str());
            return;
        }

        JNIEnv *env = this->LockEnv();
        this->callbacks[methodName] = env->GetMethodID(this->callbackClass, methodName.c_str(), methodSignature.c_str());
        this->UnLockEnv();
    };


    template<typename... Args>
    void InitNewInstance(Args... args){
        if (this->lockedEnv != nullptr){
            jobject inst = this->lockedEnv->NewObject(this->callbackClass, this->callbacks["<init>"], args...);
            this->SetInstance(inst);
            return;
        }

        JNIEnv *env = this->LockEnv();
        jobject inst = env->NewObject(this->callbackClass, this->callbacks["<init>"], args...);
        this->SetInstance(inst);
        this->UnLockEnv();
    };

    template<typename... Args>
    void RunVoid(const std::string & name, Args... args) {
        if (this->lockedEnv != nullptr){
            this->lockedEnv->CallVoidMethod(this->instanceRef, this->callbacks[name], args...);
            return;
        }

        JNIEnv *env = this->LockEnv();
        env->CallVoidMethod(this->instanceRef, this->callbacks[name], args...);
        this->UnLockEnv();
    };
};

I have tried to simplify code as much as possible, but with JNI its hard.

0 Answers
Related