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?
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.