Introduction
Recently platform specific support for Foreign Function Interface (FFI) in OpenjDK on s390x platform is provided by OpenJDK team.
This blog covers an introduction to FFI and provides an example to invoke C functions from Java program using FFI feature.
Project Panama is a set of efforts within the Java Community Process (JCP) to improve the connection between the Java Virtual Machine (JVM) and native code.
The goal is to enhance the performance and productivity of Java applications by providing better support for non-Java code, particularly native code written in languages like C and C++.
More can be read about it here.
FFI feature in Java provides mechanisms to Java programs for seamless interaction with code written in other languages, such as C or C++.
This allows developers to leverage existing native libraries or access low-level system functionalities.
All this while, Java Native Interface (JNI) served as the means to invoke foreign functions from Java.
However, Project Panama addresses certain limitations of JNI by:
- Eliminating the necessity to create intermediate native code wrappers in Java.
- Substituting ByteBuffer APIs with more future-proof Memory APIs.
- Introducing a platform-agnostic, secure, and memory-efficient approach to invoke native code from Java.
The Foreign Function & Memory APIs uses some key abstractions
- Memory segment and its address - A set of APIs classes to work with native memory and pointer to it.
- Memory layout and descriptors - APIs to model foreign types (structures, primitives) and function descriptors.
- Memory session - An abstraction to manage the lifecycle of one or more memory resources.
- Linker and symbol lookup - A set of APIs classes to perform downcalls and upcalls.
Memory APIs allows efficient memory management and data exchange between Java and native code.
All memory segments provide strongly enforced spatial, temporal, and thread-confinement guarantees which make memory de-reference operation safe.
Read more about FFI and Foreign memory access APIs here.
Example
There are two way to use FFI APIs
Jextract Tool
A simple command line tool which auto generates Java APIs from one or more native C headers. This tool is shipped with OpenJDK Panama builds.
This tool helps to get rid writing an intermediate native wrapper code.
For Example
- To generate Java API for getpid()
jextract –source -t org.unix -I /usr/include/unistd.h
-
- Use generated Java API getpid() as below
class main {
Public static void main(String[] args) {
System.out.println(getpid());
}
}
2. FFI APIs invoked from Java Application program itself
Java program written below passes a string "Hello World! Panama style" to printf() function of C library invoked from FFI API.
/**
* Panama Hello World calling C functions.
*/
public class Hello_World{
public static void main(String[] args) {
try (var arena = Arena.ofConfined()) {
// MemorySegment C's printf using a C string
MemorySegment cString = arena.allocateFrom("Hello World! Panama Style\n");
/* A Linker serves as a connection between two binary interfaces: the Java Virtual Machine (JVM)
* and C/C++ native code, commonly referred to as the C Application Binary Interface (C ABI).
*/
Linker linker = Linker.nativeLinker();
// C function printf()
// int printf(const char *format, ...); a variadic function
var symbolName = "printf";
MemorySegment symbol = linker.defaultLookup()
.find(symbolName)
.or(() -> SymbolLookup.loaderLookup().find(symbolName))
.orElseThrow(() -> new RuntimeException("The symbol %s is not found".formatted(symbolName)));
/* downcallHandle: (Bind Native Function)
* Use the downcallHandle method to bind the native function from the loaded library.
* This creates a handle to the native function that can be used for making calls.
* FunctionDescriptor:
* We define a function signature using FunctionDescriptor that specifies the argument and return types of the native function.
* In this case, it's a function that takes a long as an argument and returns a long.
*/
MethodHandle printfMH = linker.downcallHandle(
symbol,
FunctionDescriptor.of(JAVA_INT, ADDRESS));
/* Make the Function Call:
* Use the downcall method handle to make the actual function call.
* Provide the arguments as described in FunctionDescriptor.
*/
printfMH.invoke(cString);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
Steps to build and test above java program
FFI feature on s390x got merged in jdk 22 version. If current jdk version is 22, skip first 2 steps.
1. Download latest available Nightly build "Linux s390x" platform of type "JDK" from here and untar.
Example
wget https://github.com/adoptium/temurin22-binaries/releases/download/jdk-22%2B24-ea-beta/OpenJDK-jdk_s390x_linux_hotspot_ea_22-0-24.tar.gz
tar -xvf OpenJDK-jdk_s390x_linux_hotspot_ea_22-0-24.tar.gz
2. set JAVA_HOME in Linux terminal and export JAVA_HOME to PATH environmental variable.
JAVA_HOME=/root/jdk-22+24
export PATH=$JAVA_HOME/bin:$PATH
3. Execute basic Java Program with below commands.
javac Hello_World.java
java --enable-native-access=ALL-UNNAMED --enable-preview --source 22 Hello_World
4. C printf() function from C library gets invoked and prints "Hello World! Panama Style" string as below.
Hello World! Panama style
Conclusion
The blog explained about the usage of FFI APIs to invoke non-java native library functions.
Similarly other shared C libraries can be created and invoked from java code using FFI APIs as above.
References