--enable-preview
to enable it) which is specified by the JEP 454:By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI.
JEP 454
fopen
, reading the first line via gets
, and closing the file via fclose
.#include "stdio.h"
#include "stdlib.h"
int main(int argc, char *argv[]) {
FILE* file = fopen(argv[1], "r");
char* line = malloc(1024);
fgets(line, 1024, file);
printf("%s", line);
fclose(file);
free(line);
}
public static void main(String[] args) {
var file = fopen(args[0], "r");
var line = gets(file, 1024);
System.out.println(line);
fclose(file);
}
FILE* fopen(char* file, char* mode)
function which opens a file. Before we can call it, we have to get hold of its MethodHandle
:private static MethodHandle fopen = Linker.nativeLinker().downcallHandle(
lookup("fopen"),
FunctionDescriptor.of(/* return */ ValueLayout.ADDRESS,
/* char* file */ ValueLayout.ADDRESS,
/* char* mode */ ValueLayout.ADDRESS));
fopen
symbol in all the libraries that the current process has loaded, asking both the NativeLinker
and the SymbolLookup
. This code is used in many examples, so we move it into the function lookup
:public static MemorySegment lookup(String symbol) {
return Linker.nativeLinker().defaultLookup().find(symbol)
.or(() -> SymbolLookup.loaderLookup().find(symbol))
.orElseThrow();
}
fopen
and use it to create a MethodHandle that calls down from the JVM into native code. For this, we also have to specify the descriptor of the function so that the JVM knows how to call the fopen
handle properly.invokeExact
function (and an invoke
function that allows the JVM to convert data) that we can use. The only problem is that we want to pass strings to the fopen
call. We cannot pass the strings directly but instead have to allocate them onto the C heap, copying the chars into a C string:public static MemorySegment fopen(String filename, String mode) {
try (var arena = Arena.ofConfined()) {
return (MemorySegment) fopen.invokeExact(
arena.allocateUtf8String(filename),
arena.allocateUtf8String(mode));
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
fopen
, letting us return the FILE*
.char* fgets(char* buffer, int size, FILE* file)
function. This function is passed a buffer of a given size, storing the next line from the passed file in the buffer.fopen
:private static MethodHandle fgets = Linker.nativeLinker().downcallHandle(
PanamaUtil.lookup("fgets"),
FunctionDescriptor.of(ValueLayout.ADDRESS,
ValueLayout.ADDRESS,
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS));
public static String gets(MemorySegment file, int size) {
try (var arena = Arena.ofConfined()) {
var buffer = arena.allocateArray(ValueLayout.JAVA_BYTE, size);
var ret = (MemorySegment) fgets.invokeExact(buffer, size, file);
if (ret == MemorySegment.NULL) {
return null; // error
}
return buffer.getUtf8String(0);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
int fclose(FILE* file)
function to close the file:private static MethodHandle fclose = Linker.nativeLinker().downcallHandle(
PanamaUtil.lookup("fclose"),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS));
public static int fclose(MemorySegment file) {
try {
return (int) fclose.invokeExact(file);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
> ./run.sh HelloWorld LICENSE # build and run
Apache License
errno
:Several standard library functions indicate errors by writing positive integers toerrno
.
CPP Reference
fopen
returns a null pointer and sets errno
. You can find information on all the possible error numbers on the man page for the open
function.errno
directly after a call, we have to capture the call state and declare the capture-call-state option in the creation of the MethodHandle for fopen
:try (var arena = Arena.ofConfined()) {
// declare the errno as state to be captured,
// directly after the downcall without any interence of the
// JVM runtime
StructLayout capturedStateLayout = Linker.Option.captureStateLayout();
VarHandle errnoHandle =
capturedStateLayout.varHandle(
MemoryLayout.PathElement.groupElement("errno"));
Linker.Option ccs = Linker.Option.captureCallState("errno");
MethodHandle fopen = Linker.nativeLinker().downcallHandle(
lookup("fopen"),
FunctionDescriptor.of(POINTER, POINTER, POINTER),
ccs);
MemorySegment capturedState = arena.allocate(capturedStateLayout);
try {
// reading a non-existent file, this will set the errno
MemorySegment result =
(MemorySegment) fopen.invoke(capturedState,
// for our example we pick a file that doesn't exist
// this ensures a proper error number
arena.allocateUtf8String("nonexistent_file"),
arena.allocateUtf8String("r"));
int errno = (int) errnoHandle.get(capturedState);
System.out.println(errno);
return result;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
char* strerror(int errno)
function:// returned char* require this specific type
static AddressLayout POINTER =
ValueLayout.ADDRESS.withTargetLayout(
MemoryLayout.sequenceLayout(JAVA_BYTE));
static MethodHandle strerror = Linker.nativeLinker()
.downcallHandle(lookup("strerror"),
FunctionDescriptor.of(POINTER,
ValueLayout.JAVA_INT));
static String errnoString(int errno){
try {
MemorySegment str =
(MemorySegment) strerror.invokeExact(errno);
return str.getUtf8String(0);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
fopen
call, we get:No such file or directory
fopen
call.misc/headers.h
file to create MethodHandles in the class Lib
. The headers file includes all the necessary headers to run examples:#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
fgets
function, jextract generates as an entry point the following:public static MethodHandle fopen$MH() {
return RuntimeHelper.requireNonNull(constants$48.const$0,"fopen");
}
/**
* {@snippet :
* FILE* fopen(char* __filename, char* __modes);
* }
*/
public static MemorySegment fopen(MemorySegment __filename, MemorySegment __modes) {
var mh$ = fopen$MH();
try {
return (java.lang.foreign.MemorySegment)mh$.invokeExact(__filename, __modes);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
}
public static MemorySegment fopen(String filename, String mode) {
try (var arena = Arena.ofConfined()) {
// using the MethodHandle that has been generated
// by jextract
return Lib.fopen(
arena.allocateUtf8String(filename),
arena.allocateUtf8String(mode));
}
}
mvn package
to run the tool.You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
28 | |
27 | |
14 | |
13 | |
12 | |
10 | |
9 | |
7 | |
7 | |
6 |