У меня есть следующий пример кода на 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;
}
И я успешно использую это в Java с FFI со следующим кодом:
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());
}
И правильно выводит 602.
Мой вопрос: как я могу представить SomeClass C++ в Java, чтобы я мог передать это представление из Java в C++ вместо MemorySegment someAddress в строке doSomething.invoke(someAddress) и чтобы позже я мог использовать это представление для вызова метода ThatRepresentation->GetCount() непосредственно в этом классе внутри Java?
У меня нет контроля над библиотекой C++. Это всего лишь пример, в котором я сейчас застрял. Каков был бы самый простой способ создать оболочку C вокруг этой dll в качестве моста между Java и C++?
Вам нужно использовать JNA и JNI
Я думаю, вам нужно будет обернуть вызовы, которые у вас есть сейчас, в класс Java или использовать JNI, как упоминает Фархуд.




Каков был бы самый простой способ создать оболочку C вокруг этой dll в качестве моста между Java и C++?
Обычно хороший способ представить класс C++ в Java — это непрозрачный указатель, где все взаимодействия с объектом происходят на собственной стороне.
Если у вас есть библиотека C++, к которой вы хотели бы получить доступ, проверьте, есть ли у нее уже интерфейс C, к которому вы можете подключиться напрямую с помощью FFM. В противном случае вам придется самостоятельно определить интерфейс C для связывания. К сожалению, это искусство, а не нечто большее, чем то, что можно сделать механически.
Для приведенного вами примера класса я мог бы написать что-то вроде этого:
Создайте заголовочный файл для интерфейса C (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
Затем создайте реализацию C++ (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();
}
}
(где CppHeader.hpp содержит определение SomeClass).
После компиляции (cl /LD Impl.cpp) я могу использовать интерфейс C через 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);
Я думаю, вы можете увидеть здесь закономерность: для каждой функции-члена C++, к которой вы хотите получить доступ, определите оболочку extern "C", которая принимает указатель на класс, а затем вызывает функцию, которую вы хотите вызвать.
Если ваш класс C++ представляет собой стандартный тип макета (или если вы можете надежно получить макет из класса C++), вы также можете получить более прямой доступ к его полям, создав GroupLayout, который представляет макет класса, а затем получение дескрипторов var для доступа к его полям.
Спасибо за полный пример. Я только что увидел, что dll ?GetCount@SomeClass@NMSPC@@UEAAIXZ экспортирована — это, я думаю, метод из этого объекта, который экспортируется. Кажется, я могу использовать lookup.find, чтобы найти этот метод, но не уверен, смогу ли я затем вызвать его и передать this из моего someAddress? На данный момент исключений нет, но на выходе я получаю странные результаты int (вместо ожидаемого 600).
@BojanVukasovic FFM поддерживает связывание только с функциями C. Если вы каким-либо образом попытаетесь связать функцию C++ и вызвать ее, вы по сути получите неопределенное поведение. Он может дать ожидаемый результат, может дать сбой или незаметно повредить память где-то еще в процессе.
Возможно, было бы более полезно иметь extern «C» для
createSomeClass() / GetCount(ISomeClass* someCl) / doSomething(ISomeClass* someCl), и тогда вы сможете управлять экземплярами и любыми вызовами, которые захотите, в экземпляре C++.