Search code examples
javajava-native-interfaceproject-panamajava-21

How to access a native byte array via Java java.lang.foreign API


I want to access a PKCS11 library from Java via FFI/Panama. Let's say we have the following packed group layout under Windows x64:

GroupLayout groupLayout = MemoryLayout.structLayout(
    MemoryLayout.sequenceLayout(64, JAVA_BYTE).withName("slotDescription"),
    MemoryLayout.sequenceLayout(32, JAVA_BYTE).withName("manufacturerId"),
    JAVA_INT_UNALIGNED.withName("flags"),
    MemoryLayout.structLayout(
        JAVA_BYTE.withName("major"),
        JAVA_BYTE.withName("minor")
    ).withName("hardwareVersion"),
    MemoryLayout.structLayout(
        JAVA_BYTE.withName("major"),
        JAVA_BYTE.withName("minor")
    ).withName("firmwareVersion")
).withName("CK_SLOT_INFO");

A naiv approach would be to write a helper method:

public static String getString(MemorySegment memorySegment, int offset, int length) {
    byte[] slicedData = new byte[length];
    MemorySegment slicedMemorySegment = memorySegment.asSlice(offset, length);
    slicedMemorySegment.asByteBuffer().get(slicedData);
    return new String(slicedData);
}

Then call it with the offset and length:

String manufacturerId = MemorySegmentUtils.getString(memorySegment, 64, 32);

Because PKCS11 uses different packing/padding, I don't want to hardcode these offsets and lengths. Using getUtf8String like mentioned in Java VarHandle to a C string with java.lang.foreign API doesn't work because the char strings are fixed and not zero terminated.

So How can I use a MethodHandle to read these bytes:

MethodHandle methodHandle = groupLayout.sliceHandle(MemoryLayout.PathElement.groupElement("manufacturerId"));

// What to do now?
String manufacturerId = ???

Solution

  • The solution looks like the following:

    public static String getFixedString(MemorySegment memorySegment, GroupLayout groupLayout, String name) throws Throwable {
        MethodHandle methodHandle = groupLayout.sliceHandle(MemoryLayout.PathElement.groupElement(name));
        MemorySegment namedMemorySegment = (MemorySegment) methodHandle.invokeExact(memorySegment);
        byte[] namedData = namedMemorySegment.toArray(ValueLayout.JAVA_BYTE);
        return new String(namedData);
    }