Search code examples
javac++cjna

C++ API Wrapper in C for Java/JNA Object references


I'm writing a C API wrapper for my own C++ library. I want to use the C-API with JNA in Java.

For each class in the C++ library I wrapped the constructors in C function calls, returning a handle. With this handle I can call C functions, which are wrapping the object's member functions (casting the handle to it's class and calling its member function). This works fine.

To simplify the use of my API in Java, I wrote classes for all C++ classes. These Java classes only contain a JNA-Pointer (storing my objects handle) and the public member function of the C++ call. Each member function of the Java class calls the C function which is wrapping the C++ object's member function.

Now I'm facing a Problem:
When a C++ member function returns a reference to another C++ object, then my C-API returns the pointer. But how do I construct my Java objects out of this pointer? Is it legit to create a new Java Object and set its pointer value to the returned value. Although in Java are now 2 Objects, which holding the same pointer value.
Or should I have a List in Java, keeping track of all C++ objects, so I can get the returned reference from C++ out of this list.

When does the the Garbage Collector remove the JNA-Pointer objects from heap? And when gets my C++ object deleted?

(Some code to specify my answer)
In my C-API I have the following Function to create an Object

    int C_API_createFoo(void **handle) {
        try {
            *handle = reinterpret_cast<void*>(new Foo());
            return SUCCESS;
        } catch (std::exception &err) {
            std::cout << err.what() << std::endl;
            return ERROR;
        }
    }

In Java/JNA I load my C-lib and call the function with the following code:

    public Pointer createFoo(){
        PointerByReference pRef = new PointerByReference();
        int eC = MyClib.INSTANCE.C_API_createFoo(pRef);
        CHECK(eC);
        return pRef.getValue();
    }

Where Foo is:

    public class Foo{
    private Pointer handle;
    public Foo(){
            this.handle = createFoo();
        }
    public Foo(Pointer handle){
            this.handle = handle;
        }
    }

So now the class Foo stores the Object handle and can be used as a normal Java Object.

But suggest I have a C++ class for example FooWorker which has a member variable of Type Foo and has a getter Function for Foo:

    int C_API_FooWorker_getFoo(void* FooWorkerHandle, void** fooHandle) {
        try {
            FooWorker* fW= reinterpret_cast<FooWorker*>(FooWorkerHandle);
            Foo *foo = fW.getFoo();
            *fooHandle = reinterpret_cast<void*>(foo);
            return SUCCESS;
        } catch (std::exception &err) {
            std::cout << err.what() << std::endl;
            return ERROR;
        }
    }

Which is mapped in JNA like:

    public Foo FooWorker_getFoo(Pointer fooWorkerHandle){
        PointerByReference pRef = new PointerByReference();
        int eC = MyClib.INSTANCE.C_API_FooWorker_getFoo(fooWorkerHandle, pRef);
        CHECK(eC);
        return new Foo(pRef.getValue());
    }

So when I call the function FooWorker_getFoo(Pointer fooWorkerHandle) multiple times with the same value for fooWorkerHandle, than I will get multiple Java Objects of type Foo, but all containing the same value for their handle (i.e. the same C++ object reference).

Question

My Question is now: Is this save or will it cause any pain? And will all these Foo Objects (and in the end the one and only C++ Object, all the handles pointing to) be collected by the garbage collection?


Solution

  • The code as you've written it will not cause any problems, assuming that you have a native method for releasing the Foo object in C.

    What your description seems to be missing is the difference between Java side memory and Native memory. They are separately allocated and handled. Some key points:

    • calling new Foo() inside the C_API_createFoo() function allocates native side memory for that object. You have not shown the code where that memory is released; or any guarantees on how long that object will last. This is not something that happens automatically with Java, and you need to make sure the native API allows you to both persist and dispose of the object when you're done with it.
      • You should probably have C methods corresponding to JNA mappings like dispose(PointerByReference pRef) (or similar) where you can pass the pointer to Foo to release that memory. Some APIs auto-dispose based on a counter when the pointer is accessed and you may need something like keep(PointerByReference pRef) to increment that counter on the native side and make sure it doesn't release that memory too early.
    • The objects you are creating on the Java side are just plain Java objects that will be garbage collected whenever there are no more references to them.
      • In particular, if that object is a PointerByReference or Pointer inside your Java Foo object, it has the value of the native Pointer address, but it doesn't do anything special with that native address when it's GC'd, you have to intentionally do it.
      • To "intentionally" release it (as with any native handle) and avoid handle leaks, I prefer try-with-resources or try-finally blocks.

    You don't need to create a Java class just to encapsulate a Pointer. You might prefer to extend the PointerType class (which does exactly that) which gives you a decorated Pointer, e.g.,

    class FooHandle extends PointerType() {
    }
    

    Then you could change your createFoo() method to:

    public FooHandle createFoo(){
        PointerByReference pRef = new PointerByReference();
        int eC = MyClib.INSTANCE.C_API_createFoo(pRef);
        CHECK(eC);
        // call code to make sure the native object is persisted
        return new FooHandle(pRef.getValue());
    }
    

    And as I mentioned you'd need a disposeFoo() method when you're done, to release the native side memory.

    For the second example where you are just using a worker, you are not creating a new Foo() object on the native side, you're just calling a "get" method. This implies you're just retrieving a copy of a pointer address, and you can get (pun intended) as many of them as you want, without any problems.