What?

Generating FFM API-compatible bindings for Java using jextract

Why?

The Java FFM API replaces JNI and other native binding approaches for native code/memory access.

Creating bindings for those native libraries can be highly-manual or inconsistent when performed manually. jextract been provided that simplifies and harmonizes the process and provides consistent outputs.

How?

  1. Get jextract using SDK Manager:
sdk install jextract
  1. Use the jextract tool to generate bindings using c headers and libraries.
$ jextract --help
Usage: jextract <options> <header file> [<header file>] [...]                                   
 
Option                             Description                                                  
------                             -----------                                                  
-?, -h, --help                     print help                                                   
-D --define-macro <macro>=<value>  define <macro> to <value> (or 1 if <value> omitted)          
-I, --include-dir <dir>            add directory to the end of the list of include search paths 
--dump-includes <file>             dump included symbols into specified file                    
--header-class-name <name>         name of the generated header class. If this option is not    
                                   specified, then header class name is derived from the header
                                   file name. For example, class "foo_h" for header "foo.h".   
--include-function <name>          name of function to include                                  
--include-constant <name>          name of macro or enum constant to include                    
--include-struct <name>            name of struct definition to include                         
--include-typedef <name>           name of type definition to include                           
--include-union <name>             name of union definition to include                          
--include-var <name>               name of global variable to include                           
-l, --library <libspec>            specify a shared library that should be loaded by the        
                                   generated header class. If <libspec> starts with :, then  
                                   what follows is interpreted as a library path. Otherwise,   
                                   <libspec> denotes a library name. Examples:                 
                                      -l GL                                                    
                                      -l :libGL.so.1                                           
                                      -l :/usr/lib/libGL.so.1                                  
--use-system-load-library          libraries specified using -l are loaded in the loader symbol 
                                   lookup (using either System::loadLibrary, or System::load). 
                                   Useful if the libraries must be loaded from one of the paths
                                   in java.library.path.                                     
--output <path>                    specify the directory to place generated files. If this      
                                   option is not specified, then current directory is used.    
-t, --target-package <package>     target package name for the generated classes. If this option
                                   is not specified, then unnamed package is used.             
--version                          print version information and exit                           

Example: Generating Bindings for an External Library (liburing)

$ jextract --output ./app/src/generated/java \
	--target-package org.example.bindings.linux.liburing \
	--library liburing.so /usr/include/liburing.h
 
$ tree ./app/src/generated/java
./app/src/generated/java
└── org
    └── example
        └── bindings
            └── linux
                └── liburing
                    ...
                    ├── liburing_h_1.java
                    ├── liburing_h.java
                    ...
 
5 directories, 108 files
liburing_h.java
// Generated by jextract
 
package org.example.bindings.linux.liburing;
 
import java.lang.invoke.*;
import java.lang.foreign.*;
import java.nio.ByteOrder;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
 
import static java.lang.foreign.ValueLayout.*;
import static java.lang.foreign.MemoryLayout.PathElement.*;
 
// ...<snip>
/**
     * {@snippet lang=c :
     * extern int io_uring_queue_init(unsigned int entries, struct io_uring *ring, unsigned int flags)
     * }
     */
    public static int io_uring_queue_init(int entries, MemorySegment ring, int flags) {
        var mh$ = io_uring_queue_init.HANDLE;
        try {
            if (TRACE_DOWNCALLS) {
                traceDowncall("io_uring_queue_init", entries, ring, flags);
            }
            return (int)mh$.invokeExact(entries, ring, flags);
        } catch (Throwable ex$) {
           throw new AssertionError("should not reach here", ex$);
        }
    }
// ...<snip>
Partial file generated from headers with jextract

References/Other