Modern Java linking to external libraries
As a Java developer, have you ever encountered a situation where you have to call an external library from Java ?
Introduction
If your answer is "yes", then you know how complicated it is to use JNI (Java Native Interface) mechanism.
Luckily, there is a 3rd party open source project JNA (Java Native Access) which significantly simplifies the process.
However, it is not without limitations. This library requires deploying specially build native libraries aka "wrappers" for JNI and are integral part of the JNA itself. What might happen with JNA is that native wrappers might not be available for your OS.
For example, there is no support for IBM i.
Actually, all that is history now with arrival of Java 21 which introduces Panama project - JEP 454: Foreign Function & Memory API also called FFM API.
We estimate that after IBM introduce Java 21 for IBM i OS, there will be an explosion of projects being able to link to native IBM i libraries creating very interesting future solutions for that platform.
Java FFM API basics
NOTE: For better understanding the concept, we will use some terminology not common to Java, such as "pointers" and "exposed memory location". Even, the reality is a little bit different, we assume this terminology will be easier to grasp the "Java concept".
Java FFM API is a native Java JVM implementation of a mechanism used for code constructs to link Java virtual code with external library functions. It also relies on Java reflection replacement based on MethodHandle (also known as Invoke Dynamic).
More about this can be found here and JEP 416: Reimplement Core Reflection with Method Handles.
As Java does not have a "pointer" concept, while OS native libraries are all about pointers, MethodHandle and MemorySegment classes are "kind of" representation of pointers. Where MethodHandle is internal to the Java itself, a replacement for Reflection API and MemorySegment are for foreign functions in external native libraries.
When using FFM API, all data that flows between Java are either primitive types such as byte, char, int, long, float, double, boolean, or "pointer" types represented as
MemorySegment class. So, any other Java Class type including callbacks in form of a MethodHandle (kind of Java "pointer") must be converted into MemorySegment (kind of native lib pointer - out of Java).
While MethodHandle is a replacement for Java Reflection API which brings better performance, and can be considered Java internal "pointer" to methods to be called, when used as a callback to foreign functions, they also must be converted into MemorySegment - let's call them "remote pointers".
For example, imagine that we have a C/C++ external library "compression.dll" with a function...
Processing = function(int percentage)
*byte[] compress(int level, *byte[] data, *Processing callback)
The question is how to call this from Java with FFM API ? Here is how we would create a Java method...
interface ForeignCompression {
MemorySegment compress(int level, MemorySegment data, MemorySegment callback)
}
As you can see, primitive type can be passed directly to the external library. Other parameters, as they are pointers in C/C++ library, we can pass only as MemorySegment.
In this particular case, as "data" is a pointer to a byte array, we need to pass "exposed" memory location where data to be compressed is located. In other hand, to pass a callback, we need to define a MemorySegment where Java internal MethodHandle resides, so we can pass it to the external library.
Here we have three Java concepts used:
- Use of FFM API to load data from "Java side" into "exposed" memory. Data will be used as arguments for remote function.
- Use Java MethodHandle, created from Java Method, and then converted into a "pointer" - MemorySegment, so we can pass it to remote function.
- Use of FFM API to get remote function "pointer", then convert "pointer" into Java method signature - MethodHandle - used to call remote functions from Java.
Lets' see how to convert data to a "pointer"
import import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
byte[] data = {... SOME BYTES TO COMPRESS ...};
Arena arena = Arena.ofAuto();
MemorySegment data_pointer = arena.allocateArray(OfByte.JAVA_BYTE, data);
Before we create a callback "pointer", we need to create a MethodHandle for the Java method used as a callback. Let's say we have a class with a method of a signature equal to the C/C++ callback function.
class Handler {
public static void callback(int percentage) {
System.out.printline(String.format("Processed: %s%", percentage));
}
}
So, we need to create a MethodHandle first...
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// Java Method class representing reflected class method
Method method = Stream.of(Handler.class.getMethods())
.filter(m-> "callback".equal(m.getname()))
.findFirst().get();
// convert Method to MethodHandle
MethodHandle handle = MethodHandles.lookup().unreflect(method)
In the next step, we need to convert the method handle to a "pointer"...
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
// we must describe callback method signature
FunctionDescriptor descriptor = FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT);
// then, we create a pointer for a MethodHandle,
MemorySegment callback_pointer = Linker.nativeLinker()
.upcallStub(handle, descriptor, arena);
Now, we have both pointers, data_pointer and callback_pointer, so we are ready to make a call to an external function.
import java.lang.foreign.Arena;
import java.lang.foreign.SymbolLookup;
// initialize external library
Arena arena = Arena.ofShared();
SymbolLookup library = SymbolLookup.libraryLookup("compression.dll", arena);
// find foreign funcion and return "pointer" within loaded library
MemorySegment remote_function_pointer = library.find("compress").orElseThrow();
// create Java signature for remote function
// return type byte[]*, 1st arg Java int, 2nd arg byte[]*, 3rd arg callback
FunctionDescriptor descriptor = FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS);
// createa Java method signature from pointer
MethodHandle remote_function = Linker.nativeLinker()
.downcallHandle(remote_function_pointer, descriptor);
Ufff... looks complicated ? Not so much when you get the idea. But we won't stop here. As general principle is extremely simple, the whole process can be automated without all that manual work.
Actually, we can use "java.lang.reflect.Proxy" to wrap an "interceptor" around Java Interface that will do all the work. So, all programmer has to do is to create a proper interface methods for external library and simply to register that interface into a Proxy handler.
We did that already, and full source code can be found on our GitHub here...
It is generic enough to use any standard external library. The concept is the same as mentioned JNA library, but much smaller and without any native build dependencies.
Provided demo code showing how to use our JNA replacement is for widely used (and unfortunately abandoned) project WKHTMLTOX which can render web content into a PDF or multiple image formats.
What is not supported are complex structures, for which you will have to create your own MemorySegment structures. But, hey, this is just the first version. For one of the next releases, we might actually add that support also, so stay tuned to our blog and online channels.
And, happy coding... !!!