如何在Java中通过FFI表示C++类并直接调用其方法?
问题背景与需求
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。
核心需求
如何在Java中表示C的SomeClass类,使其能够替代MemorySegment传递给C,并且后续可以在Java中直接调用该表示对象的GetCount()方法?
实现方案
要在Java中映射C类并调用其成员方法,核心是通过Java FFI处理C的虚函数表(因为ISomeClass是基类,GetCount是虚函数),具体步骤如下:
1. 定义Java接口映射C++的ISomeClass
创建对应C++基类的Java接口,声明要调用的方法:
public interface ISomeClass { int getCount(); }
2. 封装Native对象的Java包装类
创建Java类持有C对象的MemorySegment指针,实现ISomeClass接口,通过FFI调用C虚函数:
import jdk.incubator.foreign.Arena; import jdk.incubator.foreign.Linker; import jdk.incubator.foreign.MemorySegment; import jdk.incubator.foreign.FunctionDescriptor; import jdk.incubator.foreign.ValueLayout; import java.lang.invoke.MethodHandle; public class SomeClassWrapper implements ISomeClass { private final MemorySegment nativeInstance; private final MethodHandle getCountHandle; // 构造函数:接收C++对象的MemorySegment指针与内存域 public SomeClassWrapper(MemorySegment nativeInstance, Arena arena) throws Exception { this.nativeInstance = nativeInstance; Linker linker = Linker.nativeLinker(); // 读取C++对象的虚函数表地址(多态对象首地址为虚表指针) MemorySegment vtable = nativeInstance.get(ValueLayout.ADDRESS, 0); // 读取虚表中第一个条目(对应GetCount方法,需与C++基类虚函数顺序一致) MemorySegment getCountFunc = vtable.get(ValueLayout.ADDRESS, 0); // 创建方法句柄:C++虚函数默认以this指针作为第一个参数 this.getCountHandle = linker.downcallHandle( getCountFunc, FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS) ); } @Override public int getCount() { try { // 传入this指针调用C++的GetCount方法 return (int) getCountHandle.invoke(nativeInstance); } catch (Throwable e) { throw new RuntimeException("调用GetCount失败", e); } } // 可选:手动释放C++对象(需C++端配合导出销毁函数) public void destroy() throws Throwable { // 示例:若C++导出delete函数 // extern "C" __declspec(dllexport) void deleteISomeClass(ISomeClass* obj) { delete obj; } // Linker linker = Linker.nativeLinker(); // MethodHandle deleteHandle = linker.downcallHandle( // SymbolLookup.libraryLookup("C:\\Path\\to\\my.dll", Arena.ofConfined()).find("deleteISomeClass").orElseThrow(), // FunctionDescriptor.ofVoid(ValueLayout.ADDRESS) // ); // deleteHandle.invoke(nativeInstance); } }
3. 修改Java调用代码,使用包装类
调整原有逻辑,将C++返回的指针包装为Java对象,直接调用方法:
try(Arena arena = Arena.ofConfined()) { Linker linker = Linker.nativeLinker(); SymbolLookup my = SymbolLookup.libraryLookup("C:\\Path\\to\\my.dll", arena); // 调用doSomething获取C++对象指针 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); // 读取C++对象指针并包装为Java对象 MemorySegment nativeObj = someAddress.get(ValueLayout.ADDRESS, 0); SomeClassWrapper javaObj = new SomeClassWrapper(nativeObj, arena); // 直接调用Java对象的getCount方法 int count = javaObj.getCount(); System.out.println("GetCount返回值:" + count); // 输出600 } catch (Throwable t) { System.out.println("Error is " + t.getMessage()); }
关键注意事项
- 虚函数表偏移:C++虚函数表中条目的顺序与基类声明的虚函数顺序一致,若
ISomeClass有多个虚函数,需按顺序调整读取偏移量(64位系统每个指针占8字节,32位占4字节)。 - 内存生命周期:若C++端创建对象,需确保Java不再使用时通过对应销毁函数释放内存,避免内存泄漏。
- 调用约定:Windows下C++虚函数默认使用
thiscall,Java FFI会自动适配,但需确保函数描述符的参数顺序正确(第一个参数为this指针)。
内容的提问来源于stack exchange,提问作者Bojan Vukasovic
相关产品推荐
相关产品推荐

