如何通过Java Foreign Function & Memory API获取C++结构体返回值
问题:无法读取C++返回的Point结构体中的值
我在调用C++返回结构体的函数时,无法解析结构体中的值,相关代码和错误信息如下:
C++结构体定义
struct Point { int x; int y; };
C++函数实现
Point test_point(Point points[],long count) { if (count <= 0) { Point defaultPoint = { 0, 0 }; return defaultPoint; } Point maxPoint = points[0]; int maxSum = maxPoint.x + maxPoint.y; for (int i = 1; i < count; ++i) { int currentSum = points[i].x + points[i].y; if (currentSum > maxSum) { maxSum = currentSum; maxPoint = points[i]; } } int x = maxPoint.x; int y = maxPoint.y; std::cout << "x = " << x << ", y = " << y << std::endl; return maxPoint; }
Java调用代码
public static void dereferenceSegmentsStruct() throws Throwable { Linker linker = Linker.nativeLinker(); SymbolLookup symbolLookup = SymbolLookup.loaderLookup(); MethodHandle test_point = linker.downcallHandle( symbolLookup.find("test_point").orElseThrow(), FunctionDescriptor.of(ADDRESS, ADDRESS, JAVA_LONG) ); StructLayout structLayout = MemoryLayout.structLayout( JAVA_INT.withName("x"), JAVA_INT.withName("y")); SequenceLayout ptsLayout = MemoryLayout.sequenceLayout(10, structLayout); VarHandle xHandle // (MemorySegment, long) -> int = ptsLayout.varHandle(PathElement.sequenceElement(), PathElement.groupElement("x")); VarHandle yHandle // (MemorySegment, long) -> int = ptsLayout.varHandle(PathElement.sequenceElement(), PathElement.groupElement("y")); MemorySegment segment = Arena.ofAuto().allocate(ptsLayout); for (int i = 0; i < ptsLayout.elementCount(); i++) { xHandle.set(segment, /* index */ (long) i, /* value to write */ i); // x yHandle.set(segment, /* index */ (long) i, /* value to write */ i); // y } MemorySegment result = (MemorySegment) test_point.invoke(segment, ptsLayout.elementCount()); result = result.reinterpret(structLayout.byteSize()); System.out.println(result.getAtIndex(JAVA_INT, 0)); }
错误信息
Exception in thread "main" java.lang.IllegalArgumentException: Misaligned access at address: 38654705673 at java.base/java.lang.invoke.VarHandleSegmentViewBase.newIllegalArgumentExceptionForMisalignedAccess(VarHandleSegmentViewBase.java:57) at java.base/java.lang.invoke.VarHandleSegmentAsInts.offsetNoVMAlignCheck(VarHandleSegmentAsInts.java:100) at java.base/java.lang.invoke.VarHandleSegmentAsInts.get(VarHandleSegmentAsInts.java:111) at java.base/java.lang.foreign.MemorySegment.getAtIndex(MemorySegment.java:1921) at org.example/org.example.TestNative.dereferenceSegmentsStruct(TestNative.java:109) at org.example/org.example.TestNative.main(TestNative.java:33)
解决方案
问题根源
你错误地将C++函数返回的结构体值当成了指针(ADDRESS),导致结构体的二进制数据被解析成了无效的内存地址,进而触发对齐错误和非法内存访问。
修正步骤
修改函数描述符的返回类型
将返回类型从ADDRESS替换为定义好的structLayout,明确告诉Java Foreign Function API,函数返回的是结构体值而非指针:MethodHandle test_point = linker.downcallHandle( symbolLookup.find("test_point").orElseThrow(), FunctionDescriptor.of(structLayout, ADDRESS, JAVA_LONG) // 第一个参数是返回类型 );显式指定结构体对齐(可选但推荐)
确保Java侧的结构体对齐与C++侧一致,避免对齐问题:StructLayout structLayout = MemoryLayout.structLayout( JAVA_INT.withName("x"), JAVA_INT.withName("y") ).withByteAlignment(4); // 匹配C++中int的默认对齐正确读取结构体字段
调用函数后,返回的MemorySegment直接包含结构体的二进制数据,可通过字段偏移或VarHandle读取值:MemorySegment result = (MemorySegment) test_point.invoke(segment, ptsLayout.elementCount()); // 方式1:通过字段偏移读取 int x = result.get(JAVA_INT, structLayout.byteOffset(PathElement.groupElement("x"))); int y = result.get(JAVA_INT, structLayout.byteOffset(PathElement.groupElement("y"))); // 方式2:通过VarHandle读取(更直观) VarHandle xHandle = structLayout.varHandle(PathElement.groupElement("x")); VarHandle yHandle = structLayout.varHandle(PathElement.groupElement("y")); int x = (int) xHandle.get(result); int y = (int) yHandle.get(result); System.out.println("x = " + x + ", y = " + y);
完整修正后的Java代码
public static void dereferenceSegmentsStruct() throws Throwable { Linker linker = Linker.nativeLinker(); SymbolLookup symbolLookup = SymbolLookup.loaderLookup(); StructLayout structLayout = MemoryLayout.structLayout( JAVA_INT.withName("x"), JAVA_INT.withName("y") ).withByteAlignment(4); MethodHandle test_point = linker.downcallHandle( symbolLookup.find("test_point").orElseThrow(), FunctionDescriptor.of(structLayout, ADDRESS, JAVA_LONG) ); SequenceLayout ptsLayout = MemoryLayout.sequenceLayout(10, structLayout); VarHandle xHandle = ptsLayout.varHandle(PathElement.sequenceElement(), PathElement.groupElement("x")); VarHandle yHandle = ptsLayout.varHandle(PathElement.sequenceElement(), PathElement.groupElement("y")); MemorySegment segment = Arena.ofAuto().allocate(ptsLayout); for (int i = 0; i < ptsLayout.elementCount(); i++) { xHandle.set(segment, (long) i, i); yHandle.set(segment, (long) i, i); } MemorySegment result = (MemorySegment) test_point.invoke(segment, ptsLayout.elementCount()); // 使用VarHandle读取字段 VarHandle resultXHandle = structLayout.varHandle(PathElement.groupElement("x")); VarHandle resultYHandle = structLayout.varHandle(PathElement.groupElement("y")); int x = (int) resultXHandle.get(result); int y = (int) resultYHandle.get(result); System.out.println("x = " + x + ", y = " + y); }
内容的提问来源于stack exchange,提问作者jdragon
相关产品推荐
相关产品推荐

