Calling Native Functions with jextract

The jextract tool mechanically generates Java bindings from a native library header file. The bindings that this tool generates depend on the Foreign Function and Memory (FFM) API. With this tool, you don't have to create downcall and upcall handles for functions you want to invoke; the jextract tool generates code that does this for you.

Obtain the tool from the following site:

https://jdk.java.net/jextract/

Obtain the source code for jextract from the following site:

https://github.com/openjdk/jextract

This site also contains steps on how to compile and run jextract, additional documentation, and samples.

Run a Python Script in a Java Application

The following steps show you how to generate Java bindings from the Python header file, Python.h, then use the generated code to run a Python script in a Java application. The Python script prints the length of a Java string.

  1. Run the following command to generate Java bindings for Python.h:
    jextract -l <absolute path of Python shared library> \
      --output classes \
      -I <directory containing Python header files> \
      -t org.python <absolute path of Python.h>

    For example:

    jextract -l /lib64/libpython3.6m.so.1.0 \
      --output classes \
      -I /usr/include/python3.6m \
      -t org.python /usr/include/python3.6m/Python.h 

    Tip:

    • On Linux systems, to obtain the file name of the Python shared library, run the following command. This example assumes that you have Python 3 installed in your system.

      ldd $(which python3)

      Running this command prints output similar to the following:

              linux-vdso.so.1 =>  (0x00007ffdb4bd5000)
              libpython3.6m.so.1.0 => /lib64/libpython3.6m.so.1.0 (0x00007fb0386a7000)
              libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb03848b000)
              libdl.so.2 => /lib64/libdl.so.2 (0x00007fb038287000)
              libutil.so.1 => /lib64/libutil.so.1 (0x00007fb038084000)
              libm.so.6 => /lib64/libm.so.6 (0x00007fb037d82000)
              libc.so.6 => /lib64/libc.so.6 (0x00007fb0379b4000)
              /lib64/ld-linux-x86-64.so.2 (0x00007fb038bce000)
    • On Linux systems, if you can't find Python.h or the directory containing the Python header files, you might have to install the python-devel package.

    • If you want to examine the classes and methods that the jextract tool creates, run the command with the --source option. For example, the following command generates the source files of the Java bindings for Python.h:
      jextract --source \
        --output src \
        -I <directory containing Python header files> \
        -t org.python <absolute path of Python.h>
  2. In the same directory as classes, which should contain the Python Java bindings, create the following file, PythonMain.java:
    import java.lang.foreign.Arena;
    import java.lang.foreign.MemorySegment;
    import static java.lang.foreign.MemorySegment.NULL;
    import static org.python.Python_h.*;
    
    public class PythonMain {
        
        public static void main(String[] args) {
            String myString = "Hello world!";
            String script = """
                         string = "%s"
                         print(string, ': ', len(string), sep='')
                         """.formatted(myString).stripIndent();
            Py_Initialize();
            
            try (Arena arena = Arena.ofConfined()) {
                MemorySegment nativeString = arena.allocateUtf8String(script);
                PyRun_SimpleStringFlags(
                    nativeString,
                    NULL);
                Py_Finalize();
            }
            Py_Exit(0);
        }
    }
  3. Compile PythonMain.java with the following command:
    javac --enable-preview -source 21 -cp classes PythonMain.java
  4. Run PythonMain with the following command:
    java --enable-preview -cp classes:. --enable-native-access=ALL-UNNAMED PythonMain

Call the qsort Function from a Java Application

As mentioned previously, qsort is a C library function that requires a pointer to a function that compares two elements. The following steps create Java bindings for the C standard library with jextract, create an upcall handle for the comparison function required by qsort, and then call the qsort function.

  1. Run the following command to create Java bindings for stdlib.h, which is the header file for the C standard library:
    jextract --output classes -t org.unix <absolute path to stdlib.h>

    For example:

    jextract --output classes -t org.unix /usr/include/stdlib.h

    The generated Java bindings for stdlib.h include a Java class named stdlib_h, which includes a Java method named qsort(MemorySegment, long, long, MemorySegment), and a Java interface named __compar_fn_t, which includes a method named allocate that creates a function pointer for the comparison function required by the qsort function. To examine the source code of the Java bindings that jextract generates, run the tool with the --source option:

    jextract --source --output src -t org.unix <absolute path to stdlib.h>
  2. In the same directory where you generated the Java bindings for stdlib.h, create the following Java source file, QsortMain.java:
    import static org.unix.__compar_fn_t.*;
    import static org.unix.stdlib_h.*;
    import java.lang.foreign.Arena;
    import java.lang.foreign.MemorySegment;
    import java.lang.foreign.ValueLayout;
    
    public class QsortMain {
        
        public static void main(String[] args) {
            
            int[] unsortedArray = new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 };
    
            try (Arena a = Arena.ofConfined()) {
                
                // Allocate off-heap memory and store unsortedArray in it
                MemorySegment array = a.allocateArray(
                    ValueLayout.JAVA_INT,
                    unsortedArray);            
    
                // Create upcall for comparison function
                //
                // MemorySegment allocate(__compar_fn_t, Arena) is from
                // __compar_fn-t.java, generated by jextract
                
                MemorySegment comparFunc = allocate(
                    (addr1, addr2) ->
                        Integer.compare(
                            addr1.get(ValueLayout.JAVA_INT, 0),
                            addr2.get(ValueLayout.JAVA_INT, 0)),
                        a);
               
                // Call qsort
                qsort(array, (long) unsortedArray.length,
                    ValueLayout.JAVA_INT.byteSize(), comparFunc);      
    
                // Deference off-heap memory
                int[] sortedArray = array.toArray(ValueLayout.JAVA_INT);
    
                for (int num : sortedArray) {
                    System.out.print(num + " ");
                }
                System.out.println();        
            } 
        }
    }

    The following statement creates an upcall, comparFunc, from a lambda expression:

                // Create upcall for comparison function
                //
                // MemorySegment allocate(__compar_fn_t, SegmentScope) is from
                // __compar_fn-t.java, generated by jextract
                
                MemorySegment comparFunc = allocate(
                    (addr1, addr2) ->
                        Integer.compare(
                            addr1.get(ValueLayout.JAVA_INT, 0),
                            addr2.get(ValueLayout.JAVA_INT, 0)),
                        a);

    Consequently, you don't have to create a method handle for the comparison function as described in Upcalls: Passing Java Code as a Function Pointer to a Foreign Function.

  3. Compile QsortMain.java with the following command:
    javac --enable-preview -source 21 -cp classes QsortMain.java
  4. Run QsortMain with the following command:
    java --enable-preview -cp classes:. --enable-native-access=ALL-UNNAMED QsortMain