Skip Top Navigation Bar
Previous in the Series
Current Tutorial
How to Handle a Call from Native Code Back to Java Code

Previous in the Series: Access Native Data Types

Next in the Series: Troubleshoot Calls to Native Library Functions

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


Previous in the Series
Current Tutorial
How to Handle a Call from Native Code Back to Java Code

Previous in the Series: Access Native Data Types

Next in the Series: Troubleshoot Calls to Native Library Functions