跨多个JNI调用保持某种类型的C++对象存在

8
我的Java代码将调用现有的C++代码来解析文件。它将生成一个保存许多数据的对象。 我将调用jni的第二个方法来访问这些数据, 当我调用第二个方法时,我必须再次解析文件。这显然是正确的行为。
是否有一种处理方式? 顺便说一下:我是新手C++开发者。

我不确定你在问什么。你是在问是否有一种方法在C++中解析文件吗?答案肯定是肯定的。也许你可以解释一下你尝试解决这个问题的方法以及遇到的问题。 - Peter Lawrey
首先,我调用 JNI 方法 parseFile()。 - David Guo
首先,我调用jni方法parseFile()(c++代码),并将一些数据返回给Java。之后,根据用户的操作,例如点击按钮,我将调用另一个jni方法selectNote()。在第二个jni方法的c++代码中,它将使用parseFile()生成的数据,因此我必须在selectNote()(c++代码)中再次调用parseFile()。 - David Guo
如果你想要缓存parseFile()第一次运行的结果,你可以先检查文件名是否相同且未被修改,然后可以重复使用上一次调用的数据结构(否则清理该结构)。 - Peter Lawrey
2个回答

15

我不确定我是否正确理解了你的问题。但我猜你想做的是在多个jni调用中保持某种c++对象的存活。

你可以采取多种方法。首先解析你的文件并将c++对象存储在全局变量中。这是最简单的解决方案,但不是一种好的方案。

你也可以将c++对象的生命周期移到java中。

jlong java_some_class_jni_method(...)
{
    .... parse your text file ....
    MyParseclass* cls = new MyParseclass(...);
    ....
    return (jlong) cls;
}

但请记住,您需要再次删除此本地C++类。因此,您需要一个JNI方法来执行此操作并确保调用它。

void java_some_calls_jni_method(..., jlong clsPtr)
{
    MyParseclass* cls = (MyParseclass*)clsPtr;
    ... do maybe do something with cls and access the data...
    delete cls; // do not use the jlong again in any call
}

顺便说一句:如果您能发布一些代码,则会更有帮助。但我希望这里的伪代码能够稍微帮到您一点。


太好了。这就是我想要的。我会尝试。 - David Guo
@DavidGuo,我删除了cls,但仍然可以从Java long访问对象,并且在删除后未减少本机内存。 - Pwn
我认为你需要在你的Java代码中删除任何对cls的引用,这样就不会有一个变量在栈中具有对cls的引用,否则它将被删除(当从栈中退出创建框架时,JNI本地引用将被删除)。最后,由于没有引用,本地对象将被垃圾回收。 - zeitgeist

7

这个问题与这个问题非常类似。

以下是我为了从多个JNI调用中引用它们而保持C++对象存活的解决方案:

Java

在Java一侧,我正在创建一个具有long指针的类,以保持对C++对象的引用。将C++方法封装在Java类中,允许我们在多个activity中使用C++方法。请注意,我在构造函数中创建C++对象,并在清理时删除该对象。这非常重要,以防止内存泄漏:

public class JavaClass {
    // Pointer (using long to account for 64-bit OS)
    private long objPtr = 0;

    // Create C++ object
    public JavaClass() {
        createCppObject();
    }

    // Delete C++ object on cleanup
    public void cleanup() {
        deleteCppObject();
        this.objPtr = 0;
    }

    // Native methods
    public native void createCppObject();
    public native void workOnCppObject();
    public native void deleteCppObject();

    // Load C++ shared library
    static {
        System.loadLibrary("CppLib");
    }

}

C++

在C++方面,我正在定义创建、修改和删除对象的函数。重要的是要提到,我们必须使用newdelete来将对象存储在堆内存中,以便在整个Java类实例的生命周期中保持其活动状态。我还直接将指向CppObject的指针存储在JavaClass中,使用getFieldIdSetLongFieldGetLongField

// Get pointer field straight from `JavaClass`
jfieldID getPtrFieldId(JNIEnv * env, jobject obj)
{
    static jfieldID ptrFieldId = 0;

    if (!ptrFieldId)
    {
        jclass c = env->GetObjectClass(obj);
        ptrFieldId = env->GetFieldID(c, "objPtr", "J");
        env->DeleteLocalRef(c);
    }

    return ptrFieldId;
}

// Methods to create, modify, and delete Cpp object
extern "C" {

    void Java_com_test_jnitest_JavaClass_createCppObject(JNIEnv *env, jobject obj) {
        env->SetLongField(obj, getPtrFieldId(env, obj), (jlong) new CppObject);
    }

    void Java_com_test_jnitest_JavaClass_workOnCppObject(JNIEnv *env, jobject obj) {
        CppObject* cppObj = (CppObject*) env->GetLongField(obj, getPtrFieldId(env, obj));

        // Write your code to work on CppObject here
    }

    void Java_com_test_jnitest_JavaClass_deleteCppObject(JNIEnv *env, jobject obj) {
        CppObject* cppObj = (CppObject*) env->GetLongField(obj, getPtrFieldId(env, obj));

        delete cppObj;
    } 

}

注意:

  • C++不像Java一样具有垃圾回收功能,对象将存活在HEAP内存中,直到使用delete删除。
  • 我使用GetFieldIDSetLongFieldGetLongField来存储C++中的对象引用,但是您也可以像其他答案中讨论的那样存储Java的jlong对象指针。
  • 在我的最终代码中,我将JavaObject类实现为一个Parcelable,以便通过带有extras的Intent传递我的类到多个活动中。

C++确实有垃圾回收机制。它还有RAII,而Java却没有。 - Mitch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接