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 <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 asGL
forlibGL.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 thepython-devel
package. - 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); } }
- Compile
PythonMain.java
with the following command:javac -sourcepath gensrc PythonMain.java
- 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.
- 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 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. - 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.
- Compile
QsortMain.java
with the following command:javac -sourcepath gensrc QsortMain.java
- Run
QsortMain
with the following command:java -cp gensrc:. --enable-native-access=ALL-UNNAMED QsortMain