Как представить класс C++ в Java для использования через FFI?

У меня есть следующий пример кода на 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?

Возможно, было бы более полезно иметь extern «C» для createSomeClass() / GetCount(ISomeClass* someCl) / doSomething(ISomeClass* someCl), и тогда вы сможете управлять экземплярами и любыми вызовами, которые захотите, в экземпляре C++.

DuncG 10.05.2024 20:39

У меня нет контроля над библиотекой C++. Это всего лишь пример, в котором я сейчас застрял. Каков был бы самый простой способ создать оболочку C вокруг этой dll в качестве моста между Java и C++?

Bojan Vukasovic 10.05.2024 20:47

Вам нужно использовать JNA и JNI

Farhood Naqizade 10.05.2024 21:02

Я думаю, вам нужно будет обернуть вызовы, которые у вас есть сейчас, в класс Java или использовать JNI, как упоминает Фархуд.

markspace 10.05.2024 21:22
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
4
151
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Каков был бы самый простой способ создать оболочку 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).

Bojan Vukasovic 10.05.2024 21:53

@BojanVukasovic FFM поддерживает связывание только с функциями C. Если вы каким-либо образом попытаетесь связать функцию C++ и вызвать ее, вы по сути получите неопределенное поведение. Он может дать ожидаемый результат, может дать сбой или незаметно повредить память где-то еще в процессе.

Jorn Vernee 10.05.2024 22:05

Другие вопросы по теме