2

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.

Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73
kofemann
  • 4,217
  • 1
  • 34
  • 39

1 Answers1

6

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 VarHandle and slice

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);
    }
}
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93