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
.
VarHandle
s, 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
VarHandle
andslice
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);
}
}