Java与C++之间的外部函数接口(FFI)互操作?

4

从Java 18开始,孵化的外部函数接口似乎没有很好地处理C++代码的方法。我正在开发一个需要绑定C++的项目,并且想知道如何避免在C中创建thunk库。

其中一个C++类大致如下:

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;
};

}

我希望创建一个类似以下Java类的东西来反映它(省略了错误检查):

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;
    }
}

Binder 大致如下:

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

我不确定其中哪些部分是正确的。我的最大实现问题是:

  • 调用构造函数和析构函数的正确方式是什么?
  • 调用方法的正确方式是什么?
  • 处理std类型(如std::string、std::vector)的正确方式是什么?
  • C++编译器会在编译时添加默认参数值,还是会生成多个方法?

3
你似乎在寻找 C++ ABI (应用程序二进制接口)。并没有一个标准化的ABI。每个编译器都定义了自己的,即使是同一编译器的不同版本也经常会有所变化。 - Igor Tandetnik
3
许多编程语言都支持C ABI,因为它相当稳定,在几乎每个平台上有明确定义,是许多操作系统的“本地”ABI,并且几乎普遍存在。使用C ABI可能会让FORTRAN与Pascal通信。Java可能会使用JNI与由C++支持的C ABI通信。 - Eljay
3
目前外部链接器不支持任何C++ ABI。因此,要调用C++,您需要公开具有C语言链接的函数。 - Jorn Vernee
2
@JohannesKuhn 是的,如果有人满足于通过C ABI进行操作,他们可以构建自己的链接器。也就是说,使用当前的C链接器理论上可以(尽管非常困难)构建C++链接器。为此,不需要实现Linker。那只是用于在JDK中公开实现的接口。但是,没有API需要提供Linker实例。因此,它被封闭并不重要。 - Jorn Vernee
显示剩余5条评论
1个回答

4

所以一般的解决方案似乎是“创建一个shim库”,因为C++ ABI非常流动并且不受Java支持。

至于结尾的答案:

  • 您可以像平常一样使用void*指针
  • this作为void*传递,并将其视为不透明指针
  • 根据我所了解的,shim会自动处理,std::string会复制并具有内部引用计数
  • 默认参数在编译时处理

3
是的,这通常是正确的,不仅适用于C++和Java。如果您想让语言X与语言Y进行通信(对于任何X和Y),通常的答案是“通过C进行”,除非特定语言存在更好的方法。更好的方法的例子包括:两个JVM语言可以始终通过Java进行通信,在浏览器中运行的任何东西都应该能够与JavaScript交流,等等。 - Silvio Mayolo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接