How to Handle a Call from Native Code Back to Java Code
Introduction
There are use cases when you need to invoke foreign functions that have as arguments pointers to another function, like sorting the elements of an array
with the standard C library function qsort
:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
This function has four arguments:
base
is a pointer to the first element of the array to be sorted,nbemb
represents the number of elements in the array,size
is the size, in bytes, of each element in the array,compar
is a pointer to the function that compares two elements.
qsort
requires a pointer to a function that compares two array elements. From Java you should pass code as a function pointer to this foreign function.
Upcall stubs enable you to pass Java code as a function pointer to a native function and this tutorial covers how to do that in order to invoke the qsort
function in Java.
Create a Downcall Method Handle for the Foreign Function
Let's start with how to represent in Java the method that compares two elements, in this case two int
values::
class Qsort {
static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
return Integer.compare(elem1.get(ValueLayout.JAVA_INT, 0), elem2.get(ValueLayout.JAVA_INT, 0));
}
}
In this method, the int
values are represented by MemorySegment
objects. The Integer.compare
method compares two int
values numerically and requires access to them.
In order to achieve that, the example calls the get(ValueLayout.OfInt, long)
on each MemorySegment
, where the second argument is the offset in bytes relative to the memory address's location.
Next, let's link our Java code against the qsort
foreign function by creating a downcall method handle for the qsort
function:
// Obtain instance of native linker
final static Linker linker = Linker.nativeLinker();
static int[] qsortTest(int[] unsortedArray) throws Throwable {
int[] sorted = null;
// Create downcall handle for qsort
MethodHandle qsort = linker.downcallHandle(linker.defaultLookup().find("qsort").get(),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG,
ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
But you also need a method handle to represent the comparison method Qsort::qsortCompare
:
// Create method handle for qsortCompare
MethodHandle comparHandle = MethodHandles.lookup().findStatic(Qsort.class, "qsortCompare",
MethodType.methodType(int.class, MemorySegment.class, MemorySegment.class));
The MethodHandles.Lookup.findStatic(Class, String, MethodType)
method creates a method handle for a static method, and it needs three arguments:
Qsort.class
is the method's class,qsortCompare
is the method's name,- the method's type: the method's return value's type and the types of the method's arguments.
Having these elements, let's create a Java description of the C function implemented by a Java method.
Create a Function Pointer from a Method Handle
This step requires for the linker to allow foreign functions to call Java method handles.
To achieve that, you need a function descriptor and a function pointer for qsortCompare
:
// Create a Java description of a C function implemented by a Java method
FunctionDescriptor qsortCompareDesc = FunctionDescriptor.of(ValueLayout.JAVA_INT,
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT));
// Create function pointer for qsortCompare
MemorySegment compareFunc = linker.upcallStub(comparHandle, qsortCompareDesc, Arena.ofAuto());
The Linker.upcallStub(MethodHandle,FunctionDescriptor,Arena,Linker.Option...)
method needs three arguments:
comparHandle
is the method handle from which to create a function pointer,qsortCompareDesc
is the function pointer's function descriptor,Arena.ofAuto()
is the arena to associate with the function pointer.Arena.ofAuto()
creates a new arena that is managed, automatically, by the garbage collector.
Now you can proceed to call the qsort
native function for sorting an array.
Call the Native Function
The native qsort
function needs an array as input.
You can use a MemorySegment
to allocate off-heap memory and store in it the input
array:
try (Arena arena = Arena.ofConfined()) {
MemorySegment array = arena.allocateFrom(ValueLayout.JAVA_INT, input);
To call the qsort
function pass its corresponding Java arguments:
qsort.invoke(array, (long)input.length, ValueLayout.JAVA_INT.byteSize(), compareFunc);
If you are curious on how the sorted array looks like, you can copy the sorted array values from off-heap to on-heap memory:
sorted = array.toArray(ValueLayout.JAVA_INT);
Putting together all the snippets in this tutorial should produce a working example similar to the one below:
import java.lang.foreign.*;
import java.lang.invoke.*;
public class InvokeQsort {
class Qsort {
static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
return Integer.compare(elem1.get(ValueLayout.JAVA_INT, 0), elem2.get(ValueLayout.JAVA_INT, 0));
}
}
// Obtain instance of native linker
final static Linker linker = Linker.nativeLinker();
static int[] qsortTest(int[] unsortedArray) throws Throwable {
int[] sorted = null;
// Create downcall handle for qsort
MethodHandle qsort = linker.downcallHandle(
linker.defaultLookup().find("qsort").get(),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
ValueLayout.JAVA_LONG,
ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS));
// Create method handle for qsortCompare
MethodHandle comparHandle = MethodHandles.lookup()
.findStatic(Qsort.class,
"qsortCompare",
MethodType.methodType(int.class,
MemorySegment.class,
MemorySegment.class));
// Create a Java description of a C function implemented by a Java method
FunctionDescriptor qsortCompareDesc = FunctionDescriptor.of(
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT));
// Create function pointer for qsortCompare
MemorySegment compareFunc = linker.upcallStub(comparHandle,
qsortCompareDesc,
Arena.ofAuto());
try (Arena arena = Arena.ofConfined()) {
// Allocate off-heap memory and store unsortedArray in it
MemorySegment array = arena.allocateFrom(ValueLayout.JAVA_INT,
unsortedArray);
// Call qsort
qsort.invoke(array,
(long)unsortedArray.length,
ValueLayout.JAVA_INT.byteSize(),
compareFunc);
// Access off-heap memory
sorted = array.toArray(ValueLayout.JAVA_INT);
}
return sorted;
}
public static void main(String[] args) {
try {
int[] sortedArray = InvokeQsort.qsortTest(new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 });
for (int num : sortedArray) {
System.out.print(num + " ");
}
System.out.println();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Finally, you can also check the sorted array by running the entire example with java InvokeQsort.java
.
$ java InvokeQsort.java
WARNING: A restricted method in java.lang.foreign.Linker has been called
WARNING: java.lang.foreign.Linker::downcallHandle has been called by InvokeQsort in an unnamed module
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
0 1 2 3 4 5 6 7 8 9
Last update: December 17, 2024