Upcalls: Passing Java Code as a Function Pointer to a Foreign Function
An upcall is a call from native code back to Java code. An upcall stub enables you to pass Java code as a function pointer to a foreign function.
Consider the standard C library function qsort
, which sorts
the elements of an array:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
It takes four arguments:
base
: Pointer to the first element of the array to be sortednbemb
: Number of elements in the arraysize
: Size, in bytes, of each element in the arraycompar
: Pointer to the function that compares two elements
The following example calls the qsort
function to sort an
int
array. However, this method requires a pointer to a function
that compares two array elements. The example defines a comparison method named
Qsort::qsortCompare
, creates a method handle to represent this
comparison method, and then creates a function pointer from this method handle.
import java.lang.foreign.*;
import java.lang.invoke.*;
import java.lang.foreign.ValueLayout.*;
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.allocateArray(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();
}
}
}
The following sections describe this example in detail:
- Defining the Java Method That Compares Two Elements
- Creating a Downcall Method Handle for the qsort Function
- Creating a Method Handle to Represent the Comparison Method qsortCompare
- Creating a Function Pointer from the Method Handle compareHandle
- Allocating Off-Heap Memory to Store the int Array
- Calling the qsort Function
- Copying the Sorted Array Values from Off-Heap to On-Heap Memory
Defining the Java Method That Compares Two Elements
The following class defines the Java 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. A memory segment provides access
to a contiguous region of memory. To obtain a value from a memory segment, call one of
its get methods. This example calls the get(ValueLayout.OfInt, long), where the second argument is the
offset in bytes relative to the memory address's location. The second argument is 0
because the memory segments in this example store only one value.
Creating a Downcall Method Handle for the qsort Function
The following statements create 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));
Creating a Method Handle to Represent the Comparison Method qsortCompare
The following statement creates 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. It takes three arguments:
- The method's class
- The method's name
- The method's type: The first argument of MethodType::methodType is the method's return value's type. The rest are the types of the method's arguments.
Creating a Function Pointer from the Method Handle compareHandle
The following statement creates a function pointer from the method handle
compareHandle
:
// 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 method takes three arguments:
- The method handle from which to create a function pointer
- The function pointer's function descriptor; in this example, the
arguments for FunctionDescriptor.of correspond to the return value type
and arguments of
Qsort::qsortCompare
- The arena to associate with the function pointer. The static method Arena.ofAuto() creates a new arena that is managed, automatically, by the garbage collector.
Allocating Off-Heap Memory to Store the int Array
The following statements allocate off-heap memory, then store the
int
array to be sorted in it:
try (Arena arena = Arena.ofConfined()) {
// Allocate off-heap memory and store unsortedArray in it
MemorySegment array = arena.allocateArray(ValueLayout.JAVA_INT,
unsortedArray);
Calling the qsort Function
The following statement calls the qsort
function:
// Call qsort
qsort.invoke(array,
(long)unsortedArray.length,
ValueLayout.JAVA_INT.byteSize(),
compareFunc);
In this example, the arguments of MethodHandle::invoke correspond to those of the standard C
library qsort
function.