我想使用项目巴拿马的外部函数接口从Java19访问C库。C接口非常简单:
typedef struct {
int len;
char name[100];
} ent;
ent* foo();
调用时,函数foo返回指向struct ent
的指针,其中len
告诉字符串name
的大小。
相应的Java端是:
private static final MemoryLayout ENT_LAYOUT = MemoryLayout.structLayout(
JAVA_INT.withName("len"),
MemoryLayout.sequenceLayout(100, ValueLayout.JAVA_BYTE).withName("name")
);
为了便于访问,我想使用VarHandle
private static final VarHandle VH_ENT_LEN = ENT_LAYOUT.varHandle(groupElement("len"));
后来
int len = (int)VH_ENT_LEN.get(segment);
String name = segment.asSlice(ENT_LAYOUT.byteOffset(groupElement("name")), len).getUtf8String(0);
但还是有点乱。
我天真的期望,这个解决方案应该是:
private static final VarHandle VH_ENT_NAME = ENT_LAYOUT.varHandle(groupElement("name"), sequenceElement());
byte[] nameRaw = (byte[])VH_ENT_NAME.get(segment);
不过,我明白了:
java.lang.RuntimeException: java.lang.invoke.WrongMethodTypeException:
cannot convert MethodHandle(VarHandle,MemorySegment,long)byte to (VarHandle,MemorySegment)byte[]
因此,问题是:是否有一个优雅的解决方案来访问来自java的数组,或者我们应该坚持VarHandle
和slice
的混合。
发布于 2022-11-04 13:41:28
VarHandle
在根上只用于访问可以适应基本类型的内存,而char[100]
不适合于原语。
你在做的时候得到了什么:
ENT_LAYOUT.varHandle(groupElement("name"), sequenceElement());
是一个VarHandle
,它从数组中选择一个byte
,为其动态提供索引:
long index = 42; // select element 42
byte nameByte = (byte) VH_ENT_NAME.get(segment, index);
应该坚持
VarHandle
和slice
的混合
是的,对于原语来说,访问任何太大的东西都需要slice
。这在本质上与在C中这样做是一样的:
ent* x = foo();
char* name = x->name;
您还可以使用MemoryLayout::sliceHandle
获得嵌入偏移计算的MethodHandle
:
MethodHandle MH_ENT_NAME = ENT_LAYOUT.sliceHandle(groupElement("name"));
方法句柄也可以进一步组合(就像varhandles一样),以创建一个直接从段中获取字符串的句柄:
MethodHandle MH_getUtf8String = MethodHandles.lookup().findVirtual(MemorySegment.class, "getUtf8String", MethodType.methodType(String.class, long.class));
MethodHandle mh = MethodHandles.insertArguments(MH_getUtf8String, 1, 0); // always access string at offset 0
mh = MethodHandles.filterArguments(result, 0, MH_ENT_NAME);
String name = (String) mh.invokeExact(segment);
尽管如此,仅仅定义一个执行上述操作的static
助手方法通常更简单:
public static String getName(MemorySegment segment) {
try {
MemorySegment nameSegment = (MemorySegment) MH_ENT_NAME.invokeExact(segment);
return nameSegment.getUtf8String(0);
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
https://stackoverflow.com/questions/74314769
复制相似问题