Android NDK,保持C++对象的活性

14

我有一个问题。我想编写一个Android应用程序,它使用我的旧的C++类。我必须在整个应用程序生命周期中保持C++对象的活动状态。

我以前用C#编写了一个类似的应用程序,并通过将指向C++类的指针传递到C#并使用IntPtr存储它来解决了这个问题。然后,当我想要调用该对象上的方法时,我只需再次将该指针传递给C++,转换为类指针并在其上调用方法。

我如何在Java和Android NDK中实现类似的结果? Java是否支持存储指针?

3个回答

19

是的,你可以做与C#相同的事情。

要创建你的新C++对象:

jlong
Java_package_name_new(JNIEnv *, jobject) {
  return (long)(new CPP_Object()); 
}

您可以将此方法的返回值存储在Java的ptr变量中,并将其传递给所有需要它的NDK方法:
void
Java_package_name_doSomething(JNIEnv *, jobject, jlong ptr) {
  CPP_Object *obj = (CPP_Object *)ptr;
  // do whatever you want with the object
}

最后,可以使用类似以下的内容将其删除:

void
Java_package_name_delete(JNIEnv *, jobject, jlong ptr) {
  delete (CPP_Object *)(ptr);
}

不必将ptr传递给所有需要它的方法,您还可以使用SetLongFieldGetLongField方法直接从NDK部分获取并设置它:这样允许Java ptr变量仅从代码的NDK部分管理,我认为这更安全且更易于管理。


3
请使用 long/jlong 而不是 int/jint 表示指针;否则,如果指针变成 64 位,则代码将会崩溃。 - fadden
完全正确,64位很可能很快就会在Android上实现(苹果已经做到了)。我正在修正我的答案! - mbrenon
非常好的回答@mbrenon,谢谢!您能否展示如何使用SetLongField和GetLongField? - dowi
2
使用GetLongField的缺点是必须将字段ID存储为静态变量,如果需要多个C对象的实例,则可能会出现问题。或者,您可以在每次调用JNI时调用GetFieldID,但这样会产生查找成本。或者,您可以将字段ID传回Java,这有点混乱,因为现在您必须在Java类中保留字段和字段ID的成员。虽然我不是Java专家,但欢迎任何人告诉我我错了,这样我就可以学到东西。 - orodbhen

5
我有点晚参与此对话,但由于我找不到一个SO帖子完整解答这个问题,所以我将发表我的解决方案。
Java
在Java端,我创建了一个类,其中包含一个指向C ++对象的long型指针。将C ++方法封装在Java类中,允许我们在多个活动中使用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在多个活动之间传递我的类。

1
你也可以使用SWIG来封装你的C++代码,在Java中构造本机对象,只要保持对它的引用,它就不会被销毁(失去引用会使其符合垃圾收集条件,当Java引用被终止时,将调用C++对象实例上的析构函数)。
然而,如果本机代码和Java代码之间的交互很少,使用SWIG可能太过于复杂。

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