javac++java-native-interfaceffijava-ffm

How to represent C++ class in java to be used via FFI?


I have following sample code in C++

class SomeClass: public ISomeClass
{
private:
    int myMember;

public:
    SomeClass(int value) : myMember(value) {}
    __declspec(dllexport)
    int GetCount() override
    {
        return myMember;
    }
};

extern "C" __declspec(dllexport)
int doSomething(ISomeClass** someCl)
{
    *someCl = new SomeClass(600);
    return (*someCl)->GetCount() + 2;
}

And I'm successfully using this in Java with FFI with the following code:

try(Arena arena = Arena.ofConfined()) {
        Linker linker = Linker.nativeLinker();
        SymbolLookup my = SymbolLookup.libraryLookup("C:\\Path\\to\\my.dll", arena);
        MethodHandle doSomething = linker.downcallHandle(
                my.find("doSomething").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS)
        );
        MemorySegment someAddress = arena.allocate(ValueLayout.ADDRESS);
        int res = (int)doSomething.invoke(someAddress);
        System.out.println("Result is " + res);
    }
    catch (Throwable t)
    {
        System.out.println("Error is " + t.getMessage());
    }

And it correctly outputs 602.

My question is - how can I represent C++'s SomeClass in Java, so that I can pass that representation from Java to C++ instead of MemorySegment someAddress on the line doSomething.invoke(someAddress) and that I can later use that representation to call ThatRepresentation->GetCount() method directly on that class inside Java?


Solution

  • What would be the easiest way to create some C wrapper around this dll as a bridge between java and C++?

    Typically, a good way to represent a C++ class in Java is as an opaque pointer, where all interactions with the object happen on the native side.

    If you have a C++ library you would like to access, check to see if it already has a C interface that you can link against directly using FFM. If not, you would have to define a C interface yourself to link against. This is an art, more than something that can be done mechanically, unfortunately.

    For the example class you've given, I might write something like this:

    Create a header file for the C interface (CHeader.h):

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    typedef struct SomeClass SomeClass;
    
    __declspec(dllexport)
    SomeClass* new_SomeClass(int);
    
    __declspec(dllexport)
    void delete_SomeClass(SomeClass*);
    
    __declspec(dllexport)
    int SomeClass_GetCount(SomeClass*);
    
    #ifdef __cplusplus
    }
    #endif
    

    Then create a C++ implementation (Impl.cpp):

    #include "CHeader.h"
    #include "CppHeader.hpp"
    
    extern "C" {
    
    __declspec(dllexport)
    SomeClass* new_SomeClass(int value) {
        return new SomeClass(value);
    }
    
    __declspec(dllexport)
    void delete_SomeClass(SomeClass* inst) {
        delete inst;
    }
    
    __declspec(dllexport)
    int SomeClass_GetCount(SomeClass* inst) {
        return inst->GetCount();
    }
    
    }
    

    (where CppHeader.hpp contains the definition of SomeClass).

    After compiling (cl /LD Impl.cpp), I can then use the C interface through FFM:

    System.loadLibrary("Impl");
    Linker linker = Linker.nativeLinker();
    SymbolLookup lookup = SymbolLookup.loaderLookup();
    MethodHandle newSomeClass = linker.downcallHandle(
        lookup.find("new_SomeClass").orElseThrow(),
        FunctionDescriptor.of(ADDRESS, JAVA_INT));
    MethodHandle deleteSomeClass = linker.downcallHandle(
        lookup.find("delete_SomeClass").orElseThrow(),
        FunctionDescriptor.ofVoid(ADDRESS));
    MethodHandle SomeClassGetCount = linker.downcallHandle(
        lookup.find("SomeClass_GetCount").orElseThrow(),
        FunctionDescriptor.of(JAVA_INT, ADDRESS));
    
    MemorySegment inst = (MemorySegment) newSomeClass.invoke(600);
    System.out.println((int) SomeClassGetCount.invoke(inst));
    deleteSomeClass.invoke(inst);
    

    I think you can see the pattern here: for every C++ member function you would like to access, define an extern "C" wrapper that takes a pointer to the class, and then calls the function you want to call.


    If your C++ class is a standard layout type (or if you can reliably derive the layout from a C++ class), you may also access its fields more directly, by creating a GroupLayout that represents the layout of the class, and then deriving var handles to access its fields.