我想使用项目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访问数组,或者我们应该坚持VarHandle
和slice
的混合。
VarHandle
s在其根目录下仅用于访问可容纳基元类型的内存,而char[100]
不适合基元类型。
你在做什么:
ENT_LAYOUT.varHandle(groupElement("name"), sequenceElement());
是从数组中选择单个byte
的VarHandle
,为其动态提供索引:
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);
}
}