Calling a C Library Function with the Foreign Function and Memory API
The following example calls strlen with the Foreign Function and Memory API:
static long invokeStrlen(String s) throws Throwable {
try (Arena arena = Arena.ofConfined()) {
// Allocate off-heap memory and
// copy the argument, a Java string, into off-heap memory
MemorySegment nativeString = arena.allocateUtf8String(s);
// Link and call the C function strlen
// Obtain an instance of the native linker
Linker linker = Linker.nativeLinker();
// Locate the address of the C function signature
SymbolLookup stdLib = linker.defaultLookup();
MemorySegment strlen_addr = stdLib.find("strlen").get();
// Create a description of the C function
FunctionDescriptor strlen_sig =
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
// Create a downcall handle for the C function
MethodHandle strlen = linker.downcallHandle(strlen_addr, strlen_sig);
// Call the C function directly from Java
return (long)strlen.invokeExact(nativeString);
}
}
The following is the declaration of the strlen C standard library function:
size_t strlen(const char *s);
It takes one argument, a string, and returns the length of the string. To call this function from a Java application, you would follow these steps:
-
Allocate off-heap memory, which is memory outside the Java runtime, for the strlen function's argument.
-
Store the Java string in the off-heap memory that you allocated.
The
invokeStrlen
example performs the previous step and this step with the following statement:MemorySegment nativeString = arena.allocateUtf8String(s);
- Build and then call a method handle that points to the strlen function. The topics in this section show you how to do this.
The following sections describe this example in detail:
Obtaining an Instance of the Native Linker
The following statement obtains an instance of the native linker, which provides access to the libraries that adhere to the calling conventions of the platform in which the Java runtime is running. These libraries are referred to as "native" libraries.
Linker linker = Linker.nativeLinker();
Locating the Address of the C Function
To call a native method such as strlen
, you need a downcall
method handle, which is a MethodHandle instance that points to a native function. This
instance requires the native function's address.
The following statements obtain the address of the strlen
function:
SymbolLookup stdLib = SymbolLookup.libraryLookup("libc.so.6", arena);
MemorySegment strlen_addr = stdLib.find("strlen").get();
SymbolLookup.libraryLookup(String, Arena) creates a library
lookup, which locates all the symbols in a user-specified native library. It loads the
native library and associates it with an arena, which controls the library lookup's
lifetime. In this example, libc.so.6
is the file name of the C
standard library for many Linux systems.
Because strlen is part of the C standard library, you can use instead the native linker's default lookup. This is a symbol lookup for symbols in a set of commonly used libraries (including the C standard library). This means that you don't have to specify a system-dependent library file name:
// Obtain an instance of the C function
Linker linker = Linker.nativeLinker();
// Locate the address of the C function
SymbolLookup stdLib = linker.defaultLookup();
MemorySegment strlen_addr = stdLib.find("strlen").get();
Tip:
Call SymbolLookup.loaderLookup() to find symbols in libraries that are loaded with System.loadLibrary(String).Describing the C Function Signature
A downcall method handle also requires a description of the native function's signature, which is represented by a FunctionDescriptor instance. A function descriptor describes the layouts of the native function's arguments and its return value, if any.
Each layout in a function descriptor maps to a Java type, which is the type
that should be used when invoking the resulting downcall method handle. Most value
layouts map to a Java primitive type. For example, ValueLayout.JAVA_INT maps to an int
value.
However, ValueLayout.ADDRESS maps to a pointer.
Composite types such as struct
and union
types are
modeled with the GroupLayout interface, which is a supertype of StructLayout and UnionLayout. See Memory Layouts and Structured Access for an example of how to initialize and access a C structure.
The following creates a function descriptor for the strlen function:
// Create a description of the C function signature
FunctionDescriptor strlen_sig =
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
The first argument of the FunctionDescriptor::of method is the layout of the native function's return value. Native primitive types are modeled using value layouts whose size matches that of such types. This means that a function descriptor is platform-specific. For example, size_t has a layout of JAVA_LONG on 64-bit or x64 platforms but a layout of JAVA_INT on 32-bit or x86 platforms.
The subsequent arguments of FunctionDescriptor::of are the layouts of the native function's arguments. In this example, there's only one subsequent argument, a ValueLayout.ADDRESS. This represents the only argument for strlen, a pointer to a string.
Creating the Downcall Handle for the C Function
The following statement creates a downcall method handle for the strlen function with its address and function descriptor.
// Create a downcall handle for the C function
MethodHandle strlen = linker.downcallHandle(strlen_addr, strlen_sig);
Calling the C Function Directly from Java
The following statement calls the strlen function with a memory segment that contains the function's argument:
// Call the C function directly from Java
return (long)strlen.invokeExact(nativeString);
You need to cast a method handle invocation with the expected return type; in
this case, it's long
.