I would like to use foreign function interface from project panama to access C library from Java19. The C interface is quite simple:
typedef struct {
int len;
char name[100];
} ent;
ent* foo();
When called, function foo returns pointer to struct ent, where len tells the size of the string name.
The corresponding Java side is:
private static final MemoryLayout ENT_LAYOUT = MemoryLayout.structLayout(
JAVA_INT.withName("len"),
MemoryLayout.sequenceLayout(100, ValueLayout.JAVA_BYTE).withName("name")
);
For ease of access I would like use VarHandle:
private static final VarHandle VH_ENT_LEN = ENT_LAYOUT.varHandle(groupElement("len"));
and later on
int len = (int)VH_ENT_LEN.get(segment);
String name = segment.asSlice(ENT_LAYOUT.byteOffset(groupElement("name")), len).getUtf8String(0);
Which is still a bit messy.
My naive expectation ware, that the solution should be something like:
private static final VarHandle VH_ENT_NAME = ENT_LAYOUT.varHandle(groupElement("name"), sequenceElement());
byte[] nameRaw = (byte[])VH_ENT_NAME.get(segment);
However I get:
java.lang.RuntimeException: java.lang.invoke.WrongMethodTypeException:
cannot convert MethodHandle(VarHandle,MemorySegment,long)byte to (VarHandle,MemorySegment)byte[]
So, the question is: is there an elegant solution to access arrays from java foreign API, or we should stick to mix of VarHandle and slice.
VarHandles, at their root, are only for accessing memory that can fit in a primitive type, and char[100] does not fit in a primitive.
What you get when doing:
ENT_LAYOUT.varHandle(groupElement("name"), sequenceElement());
Is a VarHandle that selects a single byte from the array, for which the index is supplied dynamically:
long index = 42; // select element 42
byte nameByte = (byte) VH_ENT_NAME.get(segment, index);
should stick to mix of
VarHandleandslice
Yes, slice is needed to access anything that's too big for a primitive. It is essentially the same as doing this in C:
ent* x = foo();
char* name = x->name;
You can use MemoryLayout::sliceHandle as well to get a MethodHandle that embeds the offset computations:
MethodHandle MH_ENT_NAME = ENT_LAYOUT.sliceHandle(groupElement("name"));
Method handles can also be combined further (just like varhandles), to create one that directly gets the string from the segment:
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);
Though, it is often simpler just to define a static helper method that does the above:
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);
}
}