JNI - 在NewObjectArray上崩溃

时间:2018-03-23 18:57:53

标签: android c++ java-native-interface

我有这段代码:

JNI电话的包装:

/// <summary>
/// Simple JNI class wrapper
/// </summary>
struct SimpleJNIClass{
    JavaVM* vm = nullptr;
    jobject instanceRef = nullptr;
    JNIEnv *lockedEnv = nullptr;

    SimpleJNIClass(JNIEnv *env, jobject instanceRef){
        //Returns the Java VM interface (used in the Invocation API) associated with the current thread.
        //The result is placed at the location pointed to by the second argument, vm.
        env->GetJavaVM(&this->vm);
        this->instanceRef = (jobject)env->NewGlobalRef(instanceRef);
    };

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

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

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


    JNIEnv * LockEnv() {
        this->lockedEnv = this->GetEnv();

        return this->lockedEnv;
    }

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

    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;
        }
    }

protected:
    bool 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());
    };

    virtual ~JNICallback() {
        callbackMethod = nullptr;
    }

    template<typename... Args>
    void RunVoid(Args... args) {
        JNIEnv *env = this->GetEnv();
        env->CallVoidMethod(this->instanceRef, this->callbackMethod, args...);
        FreeEnv(env);
    }
};

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 = GetEnv()) {
            env->DeleteGlobalRef(callbackClass);
            callbackClass = nullptr;
            FreeEnv(env);
        }
        callbacks.clear();
    };

    void AddMethod(const std::string & methodName, const std::string & methodSignature) {

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


    template<typename... Args>
    void InitNewInstance(Args... args) {
        JNIEnv *env = this->GetEnv();
        this->SetInstance(env->NewObject(this->callbackClass, this->callbacks["<init>"], args...));
        FreeEnv(env);
    }

    void SetInstance(jobject instRef) {
        JNIEnv *env = this->GetEnv();
        env->DeleteGlobalRef(this->instanceRef);
        this->instanceRef = env->NewGlobalRef(instRef);
        FreeEnv(env);
    }

    template<typename... Args>
    void RunVoid(const std::string & name, Args... args) {
        JNIEnv *env = this->GetEnv();
        env->CallVoidMethod(this->instanceRef, this->callbacks[name], args...);
        FreeEnv(env);
    };  
};

在JNI_OnLoad中我设置了全局变量JNIClass * fcDataClass

 fcDataClass = new JNIClass(env, "com/example/MyClass");

后来在Android的一些JNI回调中,我有:

 int resCount = 3;
 JNIEnv *lockedEnv = fcDataClass->LockEnv();
 jobjectArray dataArray = lockedEnv->NewObjectArray(resCount, fcDataClass->callbackClass, NULL);

 //do something with data
 fcDataClass->UnLockEnv();

问题是,NewObjectArray使整个应用程序崩溃:SIGSEGV (signal SIGSEGV: invalid address (fault address: 0x9c))

使用callstack:

art::JavaVMExt::JniAbort(char const*, char const*) 0x00000000ef74815c
art::JavaVMExt::JniAbortV(char const*, char const*, std::__va_list) 0x00000000ef7495ba
art::ScopedCheck::AbortF(char const*, ...) 0x00000000ef5d8eee
art::ScopedCheck::CheckThread(_JNIEnv*) 0x00000000ef5d8a0a
art::ScopedCheck::CheckPossibleHeapValue(art::ScopedObjectAccess&, char, art::JniValueType) 0x00000000ef5d7af2
art::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::JniValueType*) 0x00000000ef5d6fce
art::CheckJNI::NewObjectArray(_JNIEnv*, int, _jclass*, _jobject*) 0x00000000ef5d3808
_JNIEnv::NewObjectArray(int, _jclass*, _jobject*) jni.h:858

1 个答案:

答案 0 :(得分:1)

请参阅CheckThread()来源。如果未附加当前线程,或者您尝试使用属于不同trhead的JNIEnv*,则会中止。

您的fcDataClass->LockEnv()似乎与线程无关。获得正确JNIEnv*的合法方法是致电AttachCurrentThread()see how WebRTC does this)。在处理这样的线程之前不要忘记使用DetachCurrentThread()