使用 java.lang.foreign API 将 Java VarHandle 转换为 C 字符串

kof*_*ann 2 java java-native-interface project-panama java-18

我想使用巴拿马项目中的外部函数接口来访问 Java19 中的 C 库。C 接口非常简单:

typedef struct {
  int len;
  char name[100];
} ent;

ent* foo();
Run Code Online (Sandbox Code Playgroud)

调用时,函数 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")
);
Run Code Online (Sandbox Code Playgroud)

为了方便访问,我想使用VarHandle

private static final VarHandle VH_ENT_LEN = ENT_LAYOUT.varHandle(groupElement("len"));

Run Code Online (Sandbox Code Playgroud)

以及后来

int len = (int)VH_ENT_LEN.get(segment);
String name = segment.asSlice(ENT_LAYOUT.byteOffset(groupElement("name")), len).getUtf8String(0);

Run Code Online (Sandbox Code Playgroud)

这仍然有点混乱。

我天真的期望解决方案应该是这样的:

private static final VarHandle VH_ENT_NAME = ENT_LAYOUT.varHandle(groupElement("name"), sequenceElement());

byte[] nameRaw = (byte[])VH_ENT_NAME.get(segment);
Run Code Online (Sandbox Code Playgroud)

但是我得到:

java.lang.RuntimeException: java.lang.invoke.WrongMethodTypeException:
   cannot convert MethodHandle(VarHandle,MemorySegment,long)byte to (VarHandle,MemorySegment)byte[]
Run Code Online (Sandbox Code Playgroud)

所以,问题是:是否有一个优雅的解决方案来从 java 外部 API 访问数组,或者我们应该坚持混合使用VarHandleslice

Jor*_*nee 6

VarHandle从根本上讲,它们仅用于访问适合原始类型的内存,而不char[100]适合原始类型。

做的时候你会得到什么:

ENT_LAYOUT.varHandle(groupElement("name"), sequenceElement());
Run Code Online (Sandbox Code Playgroud)

是从数组中VarHandle选择一个的byte,其索引是动态提供的:

long index = 42; // select element 42
byte nameByte = (byte) VH_ENT_NAME.get(segment, index);
Run Code Online (Sandbox Code Playgroud)

应该坚持VarHandle和的混合slice

是的,slice需要访问任何对于基元来说太大的东西。它本质上与在 C 中执行此操作相同:

ent* x = foo();
char* name = x->name;
Run Code Online (Sandbox Code Playgroud)

您也可以使用MemoryLayout::sliceHandle来获得MethodHandle嵌入偏移计算的 a :

MethodHandle MH_ENT_NAME = ENT_LAYOUT.sliceHandle(groupElement("name"));
Run Code Online (Sandbox Code Playgroud)

方法句柄还可以进一步组合(就像 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);
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)