Search code examples
javaffijava-21java-22java-ffm

What is the correct way to obtain a String from a Foreign Function that returns a char pointer


Is there an efficient way to obtain a Java string from a Foreign Function that returns a C-style char pointer?

For example the SQLite library contains a function to return the library version number:

SQLITE_API const char *sqlite3_libversion(void);

Using Java's Foreign Function and Memory API I can call this function like so:

final MemorySegment ms = this.symbolLookup.find("sqlite3_libversion")
                .orElseThrow(() -> new RuntimeException("Could not find method 'sqlite3_libversion"));
final FunctionDescriptor fd = FunctionDescriptor.of(ValueLayout.ADDRESS);
final Linker linker = Linker.nativeLinker();
final MemorySegment result = (MemorySegment)linker.downcallHandle(ms, fd).invoke();
try (final Arena arena = Arena.ofConfined()){
    final MemorySegment ptr = result.reinterpret(10, arena, null);
    return ptr.getUtf8String(0);
}

The problem with this is that I have created a new MemorySegment of an arbitrary size 10. This is fine for this example but what is the correct way to obtain a String from a char * when I have no idea of the size of the char array?


Solution

  • You should be able to re-interpret the MemorySegment to a size for the UTF-8 conversion, with suitable value for byteSize. Some APIs/libraries may have documentation or header file definition which gives you the expected size:

    // JDK21:
    return result.reinterpret(byteSize).getUtf8String(0);
    
    // JDK22:
    return result.reinterpret(byteSize).getString(0);
    

    The reinterpret call does not re-allocate a chunk of memory with size byteSize - it just returns a MemorySegment that permits access to that range.

    Example JDK22 which uses large size:

    private static final SymbolLookup SQLITE = SymbolLookup.libraryLookup("sqlite3", Arena.global());
    private static final MemorySegment MS = SQLITE.find("sqlite3_libversion")
            .orElseThrow(() -> new RuntimeException("Could not find method 'sqlite3_libversion"));
    private static final Linker LINKER = Linker.nativeLinker();
    private static final MethodHandle MH = LINKER.downcallHandle(MS, FunctionDescriptor.of(ValueLayout.ADDRESS));
    
    public static void main(String... args) throws Throwable {
        final MemorySegment result = (MemorySegment)MH.invoke();
        String ver = result.reinterpret(Integer.MAX_VALUE).getString(0);
        System.out.println("SQLITE version "+ver);
    }
    

    Note that using jextract simplifies setting up the bindings.