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?
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.