【问题标题】:JNI callback to Java from c++ class crashing从 C++ 类崩溃到 Java 的 JNI 回调
【发布时间】:2018-03-19 11:44:44
【问题描述】:

我正在尝试在 Android 上使用 JNI 从 C++ 类调用 Java 函数。我已经搜索和搜索,但没有找到我的确切情况。我可以从 Java 调用我的 c++ 库中的方法,但是在做相反的事情时遇到了问题。我已经搞砸了两天了,浪费时间,有比我更博学的人来帮助我吗?

完整目标:保留 JNIEnv 或仅保留 JavaVM(以便稍后获取并附加有效的 JNIEnv)从 Java 传递给本地 c++ JNI EXPORT 调用,以供 c++ 类方法(不是 JNI EXPORT)以后使用。

因此,Java 类方法调用本地 c++ 方法,传递其 JNIEnv* 和 jobject。将它们作为静态类成员存储在 c++ 类中。稍后,该 c++ 类的方法使用这些静态成员来回调最初传递其上下文或其他内容的同一类的 Java 方法。

我尝试过使用 env->NewGlobalRef(someObj);但这很奇怪,因为这将使引用对象的某些未来使用成功,但有些仍然失败。

这里有一些代码:

Java 代码:

//this is what I want to call from native code
public void something(String msg)
{
//do something with msg
}

public void somethingElse()
{
    callNative();
}

private native void callNatve();

//access native
static
{
    System.loadLibrary("someLib");
}

以上所有方法都可以正常工作,但 C++ 尝试做同样的事情却没有。 (注意:我需要将本机库中的类作为类而不是独立的静态调用)

C++ 代码: (注意:为简单起见,这里所有内容都是公开的)

MyClass.h:

#include <string>
#include <jni.h>

class MyClass
{
    //ctor
    //dtor

    void someCall(std::string)

    static JNIEnv* envRef;
    static JavaVM* jvmRef;
    static jobject objRef;
};

/////////////////////////////////////// ///////////////////////////////// 我的类.cpp

#include <MyClass.h>

//static members
MyClass:;:JNIEnv* envRef;
MyClass::JavaVM* jvmRef;
MyClass::jobject objRef;

//this is the method whose instructions are crashing
void MyClass::someCall(std::string msg)
{
     //works assuming i call env->NewGlobalRef(MyClass::objRef) or setup/reattach from jvm in exported call or here
     jstring passMsg = envRef->NewStringUTF(msg.c_str());

    clsRef = envRef->GetObjectClass(objRef);
    if(clsRef == NULL)
    {
        return;
    }

    //This doesn't cause crash, but if I call FindClass("where/is/MyClass"); it does... strange
    jmethodID id = envRef->GetMethodID(clsRef, "something", "(Ljava/lang/String;)V");
    if(id == NULL)
    {
        return;
    }

    //Crashes
    //envRef->CallVoidMethod(clsRef, id, passMsg);

    if(envRef->ExceptionCheck())
    {
        envRef->ExceptionDescribe();
    }

    //Also crashes
    //jvmRef->DetachCurrentThread();
}

//this works
extern "C"
{
    JNIEXPORT void JNICALL Java_com_my_project_class_callNative(JNIEnv* env, jobject obj)
    {
        MyClass::objRef = env->NewGlobalRef(obj);
        MyClass::envRef = env;

        //tried both
        //MyClass::envRef->GetJavaVM(&MyClass::jvmRef);
        env->GetJavaVM(&MyClass::jvmRef);


        //Tried this
        /*
        int envStat = MyClass::jvmRef->GetEnv((void**)&MyClass::envRef, JNI_VERSION_1_6);
        if(envStat == JNI_EDETACHED)
        {
            //TODO: LOG
            //std::cout << "GetEnv: not attached" << std::endl;
            if(MyClass::jvmRef->AttachCurrentThread(&MyClass::envRef, NULL) != 0)
            {
                //TODO: LOG
                //std::cout << "Failed to attach" << std::endl;
            }
        }else if(envStat == JNI_OK)
        {
            //
        }else if(envStat == JNI_EVERSION)
        {
            //TODO: LOG
            //std::cout << "GetEnv: version not supported" << std::endl;
        }
        */

        //calling detachcurrentthread here crashes if set above

        MyClassObj.someCall(an std::string);
    }
}

我尝试了几种不同的方法,但它们都会导致崩溃。当我使用它时,我也会使用 DeleteGlobalRef(),但在此之前它会崩溃。任何见解表示赞赏

编辑#1: 根据 Michael 的建议,我已经实现了 JNI_OnLoad 函数并从那里只缓存了 JavaVM*。在 MyClass::someCall(std::string) 方法中,然后我使用 JavaVM 获取 JNIEnv,使用 env->FindClass 初始化 jclass 对象并获取 something(String) java 方法的 methodID,但尝试回调到带有 CallVoidMethod 的 Java 仍然会导致崩溃。

OnLoad 在 MyClass.cpp 中定义为 extern "C":

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void* reserved)
{
    MyClass::jvmRef = jvm;

    return JNI_VERSION_1_6;
}

更新了 MyClass::someCall 定义:

void MyClass::someCall(std::string msg)
{
    //Get environment from cached jvm
    JNIEnv* env;
    jclass cls;

    int envStat = MyClass::jvmRef->GetEnv((void**)&env, JNI_VERSION_1_6);

    bool attached = false;
    if(envStat == JNI_EDETACHED)
    {
        //TODO: LOG
        if(JavaInterface::jvmRef->AttachCurrentThread(&env, NULL) != 0)
        {
            //TODO: LOG
            // "Failed to attach"
            return;
        }else if(envStat == JNI_OK)
        {
            attached = true;
        }else if(envStat == JNI_EVERSION)
        {
            //TODO: LOG
            // "GetEnv: version not supported"
        }
    }

     cls = env->FindClass("package/location/project/JavaClass");
    if(cls == NULL)
    {
        //TODO: LOG
        return;
    }

    jmethodID id = env->GetMethodID(cls, "something", "(Ljava/lang/String;)V");
    if(id == NULL)
    {
        return;
    }

    jstring passMsg = env->NewStringUTF(msg.c_str());

    //Crashes
    env->CallVoidMethod(cls, id, passMsg);

    if(attached)
        jvmRef->DetachCurrentThread();
 }

【问题讨论】:

  • 你不应该缓存JNIEnv指针。 JavaVM 指针可以安全缓存,因此您可以这样做,例如在JNI_OnLoad。然后你使用JavaVM* 得到JNIEnv* 使用GetEnv/AttachCurrentThread
  • 请注意,当您获得JNIEnv* 时,您需要跟踪线程是否已附加到VM。因为你不能调用DetachCurrentThread,除非你之前在该线程上调用了AttachCurrentThread
  • 好的,我已经实现了一个超级简单的 JNI_OnLoad() 并将 jvm 缓存在其中。我可以在 MyClass::someCall(std::string msg) 中从中获取 JNI_Env,使用 FindClass 找到 jclass,并获取 methodid,但 CallVoidMethod 仍然崩溃。
  • 在您更新的someCall 函数中,您在确保您确实有一个有效的JNIEnv* 之前正在执行env-&gt;FindClass

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


【解决方案1】:

好的,Edit #1 更改后的错误是我将错误的对象传递给 CallVoidMethod()。我最终做的工作是将 callNative(JNIEnv* env, jobject obj) 传递的 jobject 存储为 MyClass 的静态成员,并将其传递给 CallVoidMethod 而不是 cls。

之前的电话:

JNIEXPORT void JNICALL Java_path_to_project_JavaClass_nativeCall(JNIEnv* env, jobject obj)
{
    JavaInterface::objRef = env->NewGlobalRef(obj)
}

DeleteGlobalRef(objRef) 在不再需要后在其他地方调用。然后,唯一的变化是原生的“someCall”方法:

void MyClass::someCall(std::string msg)
{
    //everything here is the same

    //Now either of these will work
    cls = env->FindClass("com/empsoftworks/andr3ds/NativeInterface");
//cls = env->GetObjectClass(objRef);

    //unchanged stuff

    //This is what fixed it
    env->CallVoidMethod(objRef, id, passMsg);
}

【讨论】:

  • 从长远来看,这也行不通。您不能存储 jobjects acoss JNI 方法调用。你需要一个GlobalRef。你似乎没有读多少书。我建议你学习 JNI 参考。全部。
  • 我就是这么说的。你需要使用GlobalRef,否则从长远来看是行不通的。
  • 方法 ID 可以安全缓存,因此没有必要多次查找方法 ID(例如在 JNI_OnLoad 中)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多