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.
- 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 thepython-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 forPython.h
:jextract --source \ --output src \ -I <directory containing Python header files> \ -t org.python <absolute path of Python.h>
-
- 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); } }
- Compile
PythonMain.java
with the following command:javac --enable-preview -source 21 -cp classes PythonMain.java
- 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.
- 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 namedstdlib_h
, which includes a Java method namedqsort(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 theqsort
function. To examine the source code of the Java bindings thatjextract
generates, run the tool with the --source option:jextract --source --output src -t org.unix <absolute path to stdlib.h>
- 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.
- Compile
QsortMain.java
with the following command:javac --enable-preview -source 21 -cp classes QsortMain.java
- Run
QsortMain
with the following command:java --enable-preview -cp classes:. --enable-native-access=ALL-UNNAMED QsortMain