Java VarHandle到带有Java.lang.foreign API的C字符串



我想使用项目panama中的外部函数接口来访问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外部API访问数组,或者我们应该坚持VarHandleslice的混合。

VarHandles在其根目录下仅用于访问可容纳基元类型的内存,而char[100]不适合基元类型。

你在做什么:

ENT_LAYOUT.varHandle(groupElement("name"), sequenceElement());

是从数组中选择单个byteVarHandle,为其动态提供索引:

long index = 42; // select element 42
byte nameByte = (byte) VH_ENT_NAME.get(segment, index);

应该坚持VarHandleslice的混合

是的,需要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);
}
}

最新更新