Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android NDK, keeping live C++ objects

I've got a following problem. I want to write an Android application, which uses my legacy C++ classes. I have to keep a C++ object alive during the whole application lifetime.

I wrote a similar application in C# and solved the problem by passing a pointer to a C++ class to C# and storing it there using IntPtr. Then, when I wanted to call a method on that object, I simply passed that pointer again to C++, converted to a class pointer and called a method on it.

How can I achieve similar result in Java and Android NDK? Does Java support storing pointers?

like image 970
Spook Avatar asked Nov 06 '13 09:11

Spook


2 Answers

Yes, you can do the exact same than what you did in C#.

To create your new C++ Object:

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

You can store the return value of this method in a Java ptr variable, and pass it to all NDK methods that need it:

void
Java_package_name_doSomething(JNIEnv *, jobject, jlong ptr) {
  CPP_Object *obj = (CPP_Object *)ptr;
  // do whatever you want with the object
}

And finally delete it with something like:

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

Instead of passing ptr to all methods that need it, you can also get it and set it directly from the NDK part using the SetLongField and GetLongField methods: this allows the Java ptr variable to be managed only from the NDK part of the code, which I find safer and easier to manage.

like image 144
mbrenon Avatar answered Sep 17 '22 20:09

mbrenon


I'm a bit late to the conversation, but since I couldn't find an SO post that answers this question thoroughly, I'll post my solution.

Java

On the Java side, I am creating a class with a long pointer to keep a reference to the C++ object. Wrapping the C++ methods in a Java class, allows us to use the C++ methods in multiple activities. Notice that I am creating the C++ object on the constructor, and I am deleting the object on cleanup. This is very important in order to prevent memory leaks:

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++

On the C++ side, I am defining functions to create, modify and delete the object. It's important to mention that we have to use new and delete to store the object in the HEAP memory to keep it alive throughout the lifecycle of the Java class instances. I am also storing the pointer to CppObject straight in the JavaClass, using getFieldId, SetLongField, and GetLongField:

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

}

NOTES:

  • Unlike Java, C++ does not have garbage collection, and the object will live on the HEAP memory, until you use delete.
  • I am using GetFieldID, SetLongField, and GetLongField to store the object reference from C++, but you could also store the jlong object pointer from Java as discussed on other answers.
  • On my final code, I implemented the JavaObject class as a Parcelable in order to pass my class to multiple activities using Intent with extras.
like image 45
Jaime Ivan Cervantes Avatar answered Sep 19 '22 20:09

Jaime Ivan Cervantes