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 <directory containing code generated by jextract> \
      -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 gensrc \
      -I /usr/include/python3.6m \
      -t org.python /usr/include/python3.6m/Python.h

    On Linux, to obtain the path of the Python shared library, you can run the following command:

    ldconfig -p | grep libpython

    Running this command prints output similar to the following:

            libpython3.6m.so.1.0 (libc6,x86-64) => /lib64/libpython3.6m.so.1.0
            libpython3.6m.so (libc6,x86-64) => /lib64/libpython3.6m.so
            libpython3.so (libc6,x86-64) => /lib64/libpython3.so
            libpython2.7.so.1.0 (libc6,x86-64) => /lib64/libpython2.7.so.1.0
            libpython2.7.so (libc6,x86-64) => /lib64/libpython2.7.so

    The value of the -l option is a path or name of a shared library that the generated header class should load. If it starts with a colon (:), then the value is interpreted as a library path. Otherwise, it's a library name such as GL for libGL.so.

    The jextract tool can resolve any library specifier known by the dynamic linker. Consequently, you can run this command as follows:

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

    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.

  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.allocateFrom(script);
                PyRun_SimpleStringFlags(
                    nativeString,
                    NULL);
                Py_Finalize();
            }
            Py_Exit(0);
        }
    }
  3. Compile PythonMain.java with the following command:
    javac -sourcepath gensrc PythonMain.java
  4. Run PythonMain with the following command:
    java -cp gensrc:. --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 <directory containing code generated by jextract> \
        -t org.unix <absolute path to stdlib.h>

    For example:

    jextract --output gensrc -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.

  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.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
                //
                // stdlib_h.C_INT is a constant generated by jextract
                
                MemorySegment array = a.allocateFrom(C_INT, unsortedArray);            
    
                // Create upcall stub for the comparison function
                //
                // MemorySegment org.unix.__compar_fn_t.allocate(__compar_fn_t, Arena)
                // is from __compar_fn_t.java, generated by jextract
                
                MemorySegment comparFunc = org.unix.__compar_fn_t.allocate(
                    (addr1, addr2) ->
                        Integer.compare(
                            addr1.get(C_INT, 0),
                            addr2.get(C_INT, 0)),
                        a);
               
                // Call qsort
                qsort(array, (long) unsortedArray.length, C_INT.byteSize(), comparFunc);      
    
                // Copy off-heap memory into new int[]
                int[] sortedArray = array.toArray(C_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 org.unix.__compar_fn_t.allocate(__compar_fn_t, SegmentScope)
                // is from __compar_fn-t.java, generated by jextract
                
                MemorySegment comparFunc = org.unix.__compar_fn_t.allocate(
                    (addr1, addr2) ->
                        Integer.compare(
                            addr1.get(C_INT, 0),
                            addr2.get(C_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 -sourcepath gensrc QsortMain.java
  4. Run QsortMain with the following command:
    java -cp gensrc:. --enable-native-access=ALL-UNNAMED QsortMain