javac++project-panamajava-18

Java foreign function interface (FFI) interop with C++?


As of Java 18 the incubating foreign function interface doesn't appear to have a good way to handle C++ code. I am working on a project that requires bindings to C++ and I would like to know how to avoid creating a thunk library in C.

One of the C++ classes looks something like this:

namespace library {

typedef uint8_t byte;

class CppClass {
 public:
  static oncstexpr const char* DefaultArgument = "default";

  CppClass(const std::string& argument = DefaultArgument);
  virtual ~CppClass();

  bool doStuff();

  bool handleData(std::vector<byte>* data);

 private:
  std::unique_ptr<InternalType> internalState;
};

}

I would like to create a Java class that looks something like the following to mirror that (with error checking left out):

public final class CppClass implements AutoCloseable {
    public static final String DefaultArgument = "default";

    private static final MethodHandle NEW;
    private static final MethodHandle FREE;
    private static final MethodHandle DO_STUFF;
    private static final MethodHandle HANDLE_DATA;

    static{
        var binder = Natives.getBinder();
        NEW = binder.bind("(manged constructor)", ValueLayout.ADDRESS, ValueLayout.ADDRESS);
        FREE = binder.bindVoid("(manged deconstructor)", ValueLayout.ADDRESS);
        DO_STUFF = binder.bind("(manged doStuff)", ValueLayout.JAVA_BYTE, ValueLayout.ValueLayout.ADDRESS);
        HANDLE_DATA = binder.bind("manged handleData)", ValueLayout.JAVA_BYTE, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG);
    }

    private final MemorySegment pointer;

    public CppClass() {
        this(DefaultArgument);
    }

    public CppClass(String argument) {
        try(var scope = MemoryScope.newConfinedScope()) {
            var allocator = MemoryAllocator.nativeAllocator(scope);
            pointer = (MemoryAddress)NEW.invokeExact(
                allocator.allocateUtf8String(argument)
            );
        }
    }

    @Override
    public void close() {
        FREE.invokeExact(pointer);
    }

    public boolean doStuff() {
        return (byte)DO_STUFF.invokeExact(pointer) != 0;
    }

    public boolean handleData(MemorySegment segment) {
        return (byte)HANDLE_DATA.invokeEact(pointer, segment.address(), segment.byteSize()) != 0;
    }
}

where Binder looks something like this:

public interface Binder {
    MethodHandle bind(String name, FunctionDescriptor desc);
    MethodHandle bind(String name, MemoryLayout result, MemoryLayout... args);
    MethodHandle bindVoid(String name, MemoryLayout... args);
}

I am not sure what parts of this are correct. My biggest implementation questions are:


Solution

  • So the general answer seems to be "just create a shim library" because the C++ ABI is far more fluid and not supported by Java.

    As for the answers at the end: