Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Foreign Function and Memory API - jextract

Brice Dutheil
May 15, 2024
86

Foreign Function and Memory API - jextract

From Java, it is possible to exchange with the native world since JDK 1.1, JDK22 however, brings a newer approach that is both supported and safer to cross that bridge. Lets examine the key concepts of the FFM API and why it improves significantly the situation. Let's take a look at jextract as well, a tool to automate the mapping generation.

Brice Dutheil

May 15, 2024
Tweet

Transcript

  1. Bio Brice DUTHEIL est un ingénieur logiciel chez Datadog. Il

    a construit son expérience avec du backend type EJB/WAR/EAR, du Spring Boot, puis des pods kubernetes – d’abord en tant que développeur puis ingénieur de production – dans le secteur du transport et de la téléphonie avant de rejoindre Datadog. Il a également été un des gros contributeurs à Mockito. Aujourd'hui il travaille sur le développement de plugin pour les produits JetBrains. https://blog.arkey.fr https://twitter.com/BriceDutheil
  2. Native access history Jogl (Java OpenGL) Gluegen generate bindings (java

    & C code) Opensourced in 2010 FFM (Foreign Function & Memory API) to bind to native libraries JDK 1.0 1996 Native interface ? JDK 1.1 1997 JNI (Java Native Interface) JDK 1.5 2004 JNA (Java Native Access) 2006 JDK 8 2014 JDK 22 2023-03 JNR (Java Native Runtime) 2010 JDK 7 2011 Method handles JExtract utility (Oracle) to Java generate bindings (source and/or bytecode) SWIG (Simplified Wrapper and Interface Generator) Covers more that C or Java JavaCPP generate bindings (java & C code) JEP: 472: Prepare to Restrict the Use of JNI
  3. JEP-454 : Foreign Function & Memory API Introduce an API

    by which Java programs can interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI. “
  4. JEP-454 : Foreign Function & Memory API Introduce an API

    by which Java programs can interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI. Also part of the Panama Project Vector API 7th incubator (JEP-460) An API to express vectorized computation on supported CPU. “
  5. 9 years in the making JEP-191 Foreign Function Interface JEP-338

    Vector API (Incubator) JEP-370 Foreign Memory Access API (Incubator) JEP-383 Foreign Memory Access API (2nd Incubator) JEP-389 Foreign Linker API (Incubator) JEP-393 Foreign Memory Access API (3rd Incubator) JEP-414 Vector API (2nd Incubator) JEP-412 Foreign Function & Memory API JEP-417 Vector API (3nd Incubator) JEP-419 Foreign Function & Memory API (Second Incubator) JEP-424 Foreign Function & Memory API (Preview) JEP-426 Vector API (Fourth Incubator) JEP-434 Foreign Function & Memory API (Second Preview) JEP-438 Vector API (Fifth Incubator) JEP-442 Foreign Function & Memory API (Third Preview) JEP-448 Vector API (Sixth Incubator) JEP 454 Foreign Function & Memory API JEP 460 Vector API (Seventh Incubator) JDK 22 2014 2023
  6. Why native ? Native functions are needed for various reasons

    • OS: interacting with devices, file systems, network interfaces, … • Third party libraries ◦ Unique feature, unique implementation, performance ▪ e.g. Harfbuzz
  7. Why native ? Native functions are needed for various reasons

    • OS: interacting with devices, file systems, network interfaces, … • Third party libraries ◦ Unique feature, unique implementation, performance ▪ e.g. Harfbuzz, Libsodium, blake3
  8. Why native ? Native functions are needed for various reasons

    • OS: interacting with devices, file systems, network interfaces, … • Third party libraries ◦ Unique feature, unique implementation, performance ▪ e.g. Harfbuzz, Libsodium, blake3, vectorscan, fswatch, tcmalloc, libVLC, CEF, clang, etc.
  9. Why native ? Native functions are needed for various reasons

    • OS: interacting with devices, file systems, network interfaces, … • Third party libraries ◦ Unique feature, unique implementation, performance ▪ e.g. Harfbuzz, Libsodium, blake3, vectorscan, fswatch, tcmalloc, libVLC, CEF, clang, etc. ▪ OpenGL http://opening.download/spring-2021.html
  10. JNI package q.r.s; class NativeBinding { static { System.load("path/to/libNative.so"); }

    public static native boolean isatty( int fileDescriptor ); } #include "q_r_s_Native.h" #include <unistd.h> JNIEXPORT jboolean JNICALL Java_q_r_s_NativeBinding_isatty( JNIEnv *env, jclass cls, jint fileDescriptor ) { return isatty(fileDescriptor)? JNI_TRUE: JNI_FALSE; } $ javac -d classes -cp src \ -h jni q/r/s/Native.java $ gcc \ -I $JAVA_HOME/include \ -I $JAVA_HOME/include/linux \ -fpic \ -shared \ -o libNative.so \ Native.c 🥇 Available since Java 1.1 🏎 Efficient 🏗 Complex and painful build ☣ Can terminate the JVM 💡 Before JDK10 use javah. See JEP-313
  11. JNI The base of everything To call native code, the

    JVM must perform some tricks • Initialize a valid C stack • Initialize CPU register for the OS and CPU calling convention, and switch them back on return • Use CPU memory barrier instructions (fencing) • Prevent some JIT optimisations like inlining • If critical sections are declared (Get*Critical) prevent the GC to run at the very least on the impacted région • Data exchange (objects, arrays) likely require copy (unless in critical section) ◦ And likely serialization
  12. JNI JNA package q.r.s; class NativeBinding { static { System.load("path/to/libNative.so");

    } public static native boolean isatty( int fileDescriptor ); } #include "q_r_s_Native.h" #include <unistd.h> JNIEXPORT jboolean JNICALL Java_q_r_s_NativeBinding_isatty( JNIEnv *env, jclass cls, jint fileDescriptor ) { return isatty(fileDescriptor)? JNI_TRUE: JNI_FALSE; } $ javac -d classes -cp src \ -h jni q/r/s/Native.java $ gcc \ -I $JAVA_HOME/include \ -I $JAVA_HOME/include/linux \ -fpic \ -shared \ -o Native.so \ Native.c public interface JNA_Library extends Library { JNA_Library INSTANCE = (JNA_Library) Native.loadLibrary( "c", JNA_Library.class ); boolean isatty(int fileDescriptor); } 💪 Concept 1996, released 2006 📦 Third party 🎚 Easy to use, no C build 🚜 Versatile, not fast
  13. Featureful JNA • Supports native types (wstring, structure, union, etc.)

    • Lots of reflection used for ease-of-use • Handles serialization, memory segments • Based upon JNI (invisible) to invoke its own native library • JNA Native lib will perform dispatch via libffi libffi is an assembly handwritten library that sets up the C callsite (c stack, registers)
  14. JNI JNA JNR-FFI package q.r.s; class NativeBinding { static {

    System.load("path/to/libNative.so"); } public static native boolean isatty( int fileDescriptor ); } #include "q_r_s_Native.h" #include <unistd.h> JNIEXPORT jboolean JNICALL Java_q_r_s_NativeBinding_isatty( JNIEnv *env, jclass cls, jint fileDescriptor ) { return isatty(fileDescriptor)? JNI_TRUE: JNI_FALSE; } $ javac -d classes -cp src \ -h jni q/r/s/Native.java $ gcc \ -I $JAVA_HOME/include \ -I $JAVA_HOME/include/linux \ -fpic \ -shared \ -o Native.so \ Native.c public interface JNA_Library extends Library { JNA_Library INSTANCE = (JNA_Library) Native.loadLibrary( "c", JNA_Library.class ); boolean isatty(int fileDescriptor); } public interface IsATTY_JNRFFI { boolean isatty(int fileDescriptor); } import jnr.ffi.LibraryLoader; IsATTY_JNRFFI c = LibraryLoader .create(IsATTY_JNRFFI.class) .load("c"); 📦 Third party 🎚 Easy to use 🏎 Almost as fast as JNI
  15. JNR-FFI Simple and faster than JNA • Supports native types

    (wstring, structure, union, etc.) • Handles classloader • Generates bytecode instead of reflection, way more efficient • Based upon JNI (invisible) to invoke its own native library • Also rely on libffi • Inspiration for JEP-191, and Project Panama
  16. Foreign Function and Memory var linker = Linker.nativeLinker(); var lookup

    = linker.defaultLookup(); var isatty = lookup.find("isatty").orElseThrow();
  17. Foreign Function and Memory var linker = Linker.nativeLinker(); var lookup

    = linker.defaultLookup(); var isatty = lookup.find("isatty").orElseThrow(); var isatty_MH = linker.downcallHandle(isatty, FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.JAVA_INT ));
  18. Foreign Function and Memory var linker = Linker.nativeLinker(); var lookup

    = linker.defaultLookup(); var isatty = lookup.find("isatty").orElseThrow(); var isatty_MH = linker.downcallHandle(isatty, FunctionDescriptor.of( ValueLayout.JAVA_INT, ValueLayout.JAVA_INT )); int result = isatty_MH.invokeExact(fileDescriptor); ☕ First party, made by Oracle 🎚 Easy to use 🏎 Faster than JNI Thanks to inlining and less conversions
  19. JVM Restrictions JDK Team starting to enforce integrity and safety

    by default e.g. JDK 21, dynamic agent loading raise a warning, in later JDK disabled by default JNI might be restricted too Native memory must be explicitly enabled via cli or manifest (JDK 22) --enable-native-access=ALL-UNNAMED | {modules} Enable-Native-Access: ALL-UNNAMED ℹ JEP 472: Prepare to Restrict The Use of JNI: https://openjdk.org/jeps/472
  20. Bad code can crash the VM # # A fatal

    error has been detected by the Java Runtime Environment: # # SIGSEGV (0xb) at pc=0x0000000104a0e9d8, pid=52896, tid=9987 # # JRE version: OpenJDK Runtime Environment (22.0+26) (build 22-ea+26-2112) # Java VM: OpenJDK 64-Bit Server VM (22-ea+26-2112, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64) # Problematic frame: # V [libjvm.dylib+0x9b69d8] Unsafe_GetByte(JNIEnv_*, _jobject*, _jobject*, long)+0x14c # # No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again # # An error report file with more information is saved as: # /Users/brice.dutheil/opensource/panama-watch/hs_err_pid52896.log # # If you would like to submit a bug report, please visit: # https://bugreport.java.com/bugreport/crash.jsp #
  21. Foreign Function and Memory Suppose the printf function Standard libc

    function are documented in man pages man 3 printf
  22. Foreign Function and Memory Suppose the printf function Standard libc

    function are documented in man pages man 3 printf
  23. Foreign Function and Memory The printf is very similar to

    System.out.printf, 1. A format string 2. Variable arguments
  24. Foreign Function and Memory The printf is very similar to

    System.out.printf System.out.printf("%-10.3f%n", pi); printf("%-10.3f\n", pi);
  25. Simplified call without format arguments: printf("printed to stdout") Foreign Function

    and Memory var printf = LINKER.downcallHandle( SYMBOL_LOOKUP.find("printf").orElseThrow(), FunctionDescriptor.of( ValueLayout.JAVA_LONG, ValueLayout.ADDRESS ) );
  26. The process And then …handling memory The JVM The Over

    Simplified “memory chart” ™ A native lib Java heap message
  27. The process And then …handling memory The JVM A native

    lib Java heap message 1. Allocate new Off-heap segment of memory and copy content message The Over Simplified “memory chart” ™
  28. The process And then …handling memory The JVM A native

    lib Java heap message 2. Pass the pointer of native segment to function function message The Over Simplified “memory chart” ™
  29. The process And then …handling memory The JVM A native

    lib Java heap message Now there’s litter in the process memory ? function message The Over Simplified “memory chart” ™
  30. Foreign Function and Memory 4 concepts to grasp • Arenas

    : allow memory lifecycle management, form allocation to disposal • Memory segments : a contiguous region of bytes • Memory layouts : data model in a memory segment • MethodHandle, VarHandle : polymorphic glue to access native function & memory
  31. Foreign Function and Memory • FFM API is designed in

    a way to prevent misuse for memory in particular. • FFM comes with Arenas, they control the lifecycle of native memory segments. ◦ Multiple characteristics are available ▪ Global ⟹ Deallocation ❌ ▪ Automatic ⟹ Deallocation❓ (when GC is passing by) ▪ Confined ⟹ Deallocate at close 🚧 Singled 🧵 ▪ Shared ⟹ Deallocate at close 🚧, Shared by multiple 🧵🧵🧵🧵… ◦ Implementing custom memory management is possible, ▪ Interfaces: Arena ➝ SegmentAllocator
  32. For the printf example, let’s create a confined arena Foreign

    Function and Memory try (var arena = Arena.ofConfined()) { }
  33. For the printf example, then let’s allocate and copy Java

    string content Foreign Function and Memory try (var arena = Arena.ofConfined()) { var memorySegment = arena.allocateFrom(str); }
  34. For the printf example, then make the call Foreign Function

    and Memory try (var arena = Arena.ofConfined()) { var memorySegment = arena.allocateFrom(str); return (long) printf.invoke(memorySegment); }
  35. For the printf example, then make the call Foreign Function

    and Memory try (var arena = Arena.ofConfined()) { var memorySegment = arena.allocateFrom(str); return (long) printf.invoke(memorySegment); } Deallocation when closed
  36. MemorySegment Represents a contiguous “segment” of bytes, on-heap or off-heap

    All segments have a start address in memory, this value is used as the pointer Accessible via MemorySegment::address All segments have a size Accessible via MemorySegment::byteSize A segment with a zero-length is an address
  37. MemorySegment Represents a contiguous “segment” of memory The process The

    JVM A native lib Java heap 2E502E2E A0FB992E function Segments of memory The Over Simplified “memory chart” ™
  38. Pointers everywhere Pointers are links to everything Even native functions

    are actually memory address Actually an address, returns a zero-length MemorySegment var printf = LINKER.downcallHandle( SYMBOL_LOOKUP.find("printf").orElseThrow(), FunctionDescriptor.of( ValueLayout.JAVA_LONG, ValueLayout.ADDRESS ) );
  39. MemorySegment Represents a contiguous “segment” of memory The process The

    JVM A native lib Java heap 2E502E2E A0FB992E function Segments of memory 0-length The Over Simplified “memory chart” ™
  40. Exchanging structured data Passing strings and raw types is almost

    easy as usual factories are there, e.g. allocateFrom(elementLayout, elementsArray) Fun begin with complex layout, use MemoryLayout factories
  41. Exchanging structured data typedef struct { uint8_t buf_len; uint8_t flags;

    } basic_struct; MemoryLayout.structLayout( JAVA_BYTE.withName("buf_len"), JAVA_BYTE.withName("flags"), ).withName("basic_struct"); uint8_t buf[8]; MemoryLayout.sequenceLayout( 64, JAVA_BYTE ).withName("buf") typedef struct { uint8_t buf[8]; uint8_t buf_len; uint8_t flags; } basic_struct; MemoryLayout.structLayout( bufLayout, JAVA_BYTE.withName("buf_len"), JAVA_BYTE.withName("flags"), ).withName("basic_struct");
  42. Exchanging structured data Passing strings and raw types is almost

    easy as usual factories are there, e.g. allocateFrom(elementLayout, elementsArray) Fun begins with complex layout, use MemoryLayout factories • structLayout(…), structure like • sequenceLayout(…), array like • unionLayout(…), allow different structs over the same storage • paddingLayout(…), padding require understanding of what means alignment
  43. Exchanging structured data Structured access to elements basic_struct_LAYOUT = MemoryLayout.structLayout(bufLayout,

    JAVA_BYTE.withName("buf_len"), JAVA_BYTE.withName("flags") ).withName("basic_struct");
  44. Exchanging structured data Structured access to elements basic_struct_LAYOUT = MemoryLayout.structLayout(bufLayout,

    JAVA_BYTE.withName("buf_len"), JAVA_BYTE.withName("flags") ).withName("basic_struct"); VarHandle flags = basic_struct_LAYOUT.varHandle( MemoryLayout.PathElement.groupElement("flags"));
  45. Exchanging structured data Structured access to elements basic_struct_LAYOUT = MemoryLayout.structLayout(bufLayout,

    JAVA_BYTE.withName("buf_len"), JAVA_BYTE.withName("flags") ).withName("basic_struct"); VarHandle flags = basic_struct_LAYOUT.varHandle( MemoryLayout.PathElement.groupElement("flags")); flags.get(memorySegment, 0L); flags.set(memorySegment, 0L, (byte) 0x2); storage Base offset in segment value
  46. Exchanging structured data Structured access to elements basic_struct_LAYOUT = MemoryLayout.structLayout(bufLayout,

    JAVA_BYTE.withName("buf_len"), JAVA_BYTE.withName("flags")).withName("basic_struct"); VarHandle flags = basic_struct_LAYOUT.varHandle( MemoryLayout.PathElement.groupElement("flags")); flags.get(memorySegment, 0L); flags.set(memorySegment, 0L, (byte) 0x2); Notice the enforced type
  47. Exchanging structured data Structured access to elements basic_struct_LAYOUT = MemoryLayout.structLayout(bufLayout,

    JAVA_BYTE.withName("buf_len"), JAVA_BYTE.withName("flags")).withName("basic_struct"); VarHandle flags = basic_struct_LAYOUT.varHandle( MemoryLayout.PathElement.groupElement("flags")); flags.get(memorySegment, 0L); flags.set(memorySegment, 0L, (byte) 0x2); Exception in thread "main" java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(VarHandle,MemorySegment,long,byte)void to (VarHandle,MemorySegment,int)void Notice the enforced type, WrongMethodTypeException if incorrect
  48. Loading libraries Standard or common libraries are already loaded by

    the JVM process • Nothing to do • Symbols are available via Linker.nativeLinker().defaultLookup() e.g. printf, isatty, etc. SYMBOL_LOOKUP = Linker.nativeLinker().defaultLookup() SYMBOL_LOOKUP.find("symbol") Tied to symbol lookup
  49. Loading libraries JNI library loading mechanism via System::load or System::loadLibrary

    • ⚠ Lifecycle tied to the classloader • Library and symbols can only be “appearing” once SYMBOL_LOOKUP = SymbolLookup.loaderLookup() SYMBOL_LOOKUP.find("symbol")
  50. Loading libraries FFM API has a flexible loading mechanism •

    Not tied to classloader, but to an Arena, that can be explicitly controlled SYMBOL_LOOKUP = SymbolLookup.libraryLookup("path/to/libblake3.so", LIBRARY_ARENA) SYMBOL_LOOKUP.find("symbol") Usually LIBRARY_ARENA = Arena.ofAuto();
  51. Loading libraries FFM API has a flexible loading mechanism •

    Not tied to classloader, but to an Arena, that can be explicitly controlled • Composable lookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup("path/to/libblake3.so", LIBRARY_ARENA) .or(SymbolLookup.loaderLookup()) .or(Linker.nativeLinker().defaultLookup()); SYMBOL_LOOKUP.find("symbol")
  52. Handling error in C with errno errno is like a

    global error state in the C standard library, • it’s an integer variable set by the function that was called https://man7.org/linux/man-pages/man3/errno.3.html
  53. Handling error in C with errno errno is like a

    global error state in the C standard library, • it’s an integer variable set by the function that was called https://man7.org/linux/man-pages/man3/errno.3.html ioctl = LINKER.downcallHandle( SYMBOL_LOOKUP.find("ioctl").orElseThrow(), ioctlFunctionDescriptor, ); result = (int) ioctl.invokeExact(…)
  54. Handling error in C with errno errno is like a

    global error state in the C standard library, • it’s an integer variable set by the function that was called https://man7.org/linux/man-pages/man3/errno.3.html ioctl = LINKER.downcallHandle( SYMBOL_LOOKUP.find("ioctl").orElseThrow(), ioctlFunctionDescriptor, Linker.Option.captureCallState("errno") ); var csl = Linker.Option.captureStateLayout(); var errnoHandle = csl.varHandle( PathElement.groupElement("errno") ); result = (int) ioctl.invokeExact(…)
  55. Handling error in C with errno errno is like a

    global error state in the C standard library, • it’s an integer variable set by the function that was called https://man7.org/linux/man-pages/man3/errno.3.html ioctl = LINKER.downcallHandle( SYMBOL_LOOKUP.find("ioctl").orElseThrow(), ioctlFunctionDescriptor, Linker.Option.captureCallState("errno") ); var csl = Linker.Option.captureStateLayout(); var errnoHandle = csl.varHandle( PathElement.groupElement("errno") ); var storage = arena.allocate(csl); result = (int) ioctl.invokeExact(storage, …) switch ((int) errnoHandle.get(storage, 0L)) { 25 -> … // ENOTTY }
  56. MethodHandles As you noticed, this API relies a lot on

    invokedynamic features. • VarHandle • MethodHandle This means we benefit from all the features • binding arguments (similar to currying) • Interception • etc. and it’s errors • WrongMethodTypeException in particular
  57. Bad code example using printf Foreign Function and Memory try

    (var arena = Arena.ofConfined()) { var memorySegment = arena.allocateFrom(str); return (long) printf.invoke(memorySegment.address()); } Wrong
  58. Understanding WrongMethodTypeException If WrongMethodTypeException shows up, read the message Exception

    in thread "main" java.lang.invoke.WrongMethodTypeException: handle's method type (MemorySegment)long but found (long)long at java.base/java.lang.invoke.Invokers.newWrongMethodTypeException(Invokers.java:521) at java.base/java.lang.invoke.Invokers.checkExactType(Invokers.java:530) at io.whatever.App.c_printf(App.java:41)
  59. Understanding WrongMethodTypeException handle's method type (MemorySegment)long Indicates what was described

    in the descriptor but found (long)long Indicates what was seen on call site FunctionDescriptor.of( ValueLayout.JAVA_LONG, ValueLayout.ADDRESS ) ℹ ValueLayout.ADDRESS carrier type is MemorySegment (long) printf .invokeExact(segment.address());
  60. Variadic function in the MethodHandle section E.g. int printf(const char

    * restrict format, ...) • It’s not really supported in the JDK • JDK team opted for specialized downcall ◦ new downcall declaration for new sequence of argos • JDK advocate use of jextract for this feature to work. • Required to declare the downcallHandle with the index of the first variadic param LINKER.downcallHandle(…, Linker.Option.firstVariadicArg(2));
  61. Interacting with non-C language Linker and SymbolLookup works with C

    conventions Foreign code needs to visible as a C function …(usually declared with extern) Swift @_cdecl("authenticate_user_touchid") public func authenticateUserApi() { … } Rust #[no_mangle] pub extern "C" fn abort() { … } Objective-C extern void cameraCapture(); Also look at cbindgen to generate C binding first https://jornvernee.github.io/java/panama/rust/panama-ffi/2021/09/03/rust-panama-helloworld.html
  62. Graalvm native-image support Currently baking, e.g. in JDK 21 preview

    API • Missing features: variadic functions, shared arenas, upcalls, etc. • Requires some special configuration at build time https://www.graalvm.org/latest/reference-manual/native-image/native-code-interoperability/foreign-interface/ class FFMRegistrationFeature implements Feature { public void duringSetup(DuringSetupAccess access) { RuntimeForeignAccess.registerForDowncall( FunctionDescriptor.ofVoid() ); RuntimeForeignAccess.registerForDowncall( FunctionDescriptor.ofVoid(), Linker.Option.isTrivial() ); … } } --features=com.example.FFMRegistrationFeature -H:+UnlockExperimentalVMOptions -H:+ForeignAPISupport --enable-native-access=ALL-UNNAMED native-image.properties
  63. About sun.misc.Unsafe Used for low level concurrency library, and others

    • Netty (used by quarkus), Akka, Hazelcast, Apache Spark, Java based implementation of some codecs JDK developers aim to remove it at some point …but alternative supported API are not yet performant enough JEP 471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal A collection of methods for performing low-level, unsafe operations.
  64. Binding Automation: JavaCPP Supports rich C++ constructs Generation at build

    time Configuration medium : Java class Existing binding (javacpp-presets)
  65. Binding Automation: jextract https://twitter.com/devoxx/status/1715655857797550378 External CLI tool Supports C/C++ constructs

    • Even complex declarations • No C code generated Maintained by JDK team ⚠ Stable but still as mentions early access
  66. Binding Automation: jextract Navigate to https://jdk.java.net/jextract/ ℹ Possibly add the

    executable permission : chmod +x jextract-22/jextract Or remove macOs quarantine flags : sudo xattr -r -d com.apple.quarantine jextract-22/jextract/
  67. Basic usage If you have a simple library $ jextract

    \ --target-package org.lib \ --header-class-name Lib \ lib.h org.lib.Lib
  68. Generated code E.g. with BLAKE3/c/blake3.h (and --header-class-name blake3_h) Generated API

    usage var hasher = blake3_hasher.allocate(arena); blake3_h.blake3_hasher_init(hasher); var content = arena.allocateFrom("Hello Panama!"); blake3_h.blake3_hasher_update( hasher, content, content.byteSize() - 1 ); allocate returns MemorySegment with the blake3_hasher struct layout Functions and other symbols exposed as statics under blake3_h
  69. Generated code E.g. with BLAKE3/c/blake3.h (and --header-class-name blake3_h) /** *

    {@snippet lang=c : * struct { * uint32_t key[8]; * blake3_chunk_state chunk; * uint8_t cv_stack_len; * uint8_t cv_stack[1760]; * } * } */ public class blake3_hasher { … /** * {@snippet lang=c : * void blake3_hasher_init(blake3_hasher *self) * } */ public static void blake3_hasher_init(MemorySegment self) { …
  70. Configuring library loading Is library on system library path? If

    yes Specify shared lib name $ jextract … \ --library GL \ --library GLU \ … Lookup will look for standard locations /usr/lib /usr/lib64 /lib /lib64 ├── libGL.so └── libGLU.so /usr/local/lib /usr/local/lib64 generates SymbolLookup.libraryLookup(System.mapLibraryName("GL"), …); SymbolLookup.libraryLookup(System.mapLibraryName("GLU"), …);
  71. Or, prefix path with a colon : Configuring library loading

    Is library on system library path ? If no, either don’t pass --library flag, and load the library yourself before using bindings. Specify library path $ jextract … \ --library :path/to/libblake3.so \ … SymbolLookup.libraryLookup("path/to/libblake3.so", …) Explicit load SymbolLookup.libraryLookup( "path/to/libblake3.so", … ); // Then use generated bindings generates Note the colon
  72. Dealing with includes Add include dirs $ jextract \ --target-package

    org.lib \ --include-dir path1/to/include \ --include-dir path2/to/include \ lib.h E.g. Specific library include path : /usr/local/Cellar/ffmpeg@4/4.4.4/include Or system includes macOs: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ …or: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include Linux: /usr/include/ lib.h #include "relative/path1/a.h" #include "relative/path2/b.h" …
  73. Multiple headers Juste create a synthetic single header file Declare

    every included headers Pass synthetic header $ jextract …\ my-multiple.h my-multiple.h #include "path/to/libOneThing.h" #include "path/to/libOtherThing.h" …
  74. Choose generated binding explicitly Dumping symbol include configuration Dump all

    symbols first $ jextract … \ --dump-includes my-dump.txt \ blake3.h
  75. Choose generated binding explicitly How to be specific about which

    symbol ? Dump all symbols first $ jextract … \ --dump-includes my-dump.txt \ blake3.h my-dump.txt … #### Extracted from: …/blake3.h --include-struct blake3_chunk_state # header: …/c/blake3.h --include-struct blake3_hasher # header: …/blake3.h … --include-struct --include-function --include-constant --include-typedef --include-union --include-var
  76. Choose generated binding explicitly How to be specific about which

    symbol ? Dump all symbols first $ jextract … \ --dump-includes my-dump.txt \ blake3.h my-dump.txt … #### Extracted from: …/blake3.h --include-struct blake3_chunk_state # header: …/c/blake3.h --include-struct blake3_hasher # header: …/blake3.h … Selected “symbols” $ jextract … \ --include-struct blake3_chunk_state \ --include-struct blake3_hasher \ blake3.h Select which symbol should have a binding
  77. Use a argument file Like the java command, jextract supports

    arguments file my-config.txt $ jextract … \ @my-config.txt --output path/to/gen-sources/jextract-blake3/java --target-package blake3 --header-class-name blake3_h --include-struct blake3_chunk_state # header: …/c/blake3.h --include-struct blake3_hasher # header: …/blake3.h … blake3.h
  78. Caution Native headers may not be platform independent • OS

    specific macros (preprocessed by the C preprocessor before the binding) • Imprecise C types, e.g. long (different length depending on the platform), Precise types: int64_t, long long (instead of long) JExtract may produce bindings that may behave incorrectly on other platforms. ➡ Double check, jextract’s generated code on other platform, if unsure about library ☣ ℹ OpenGL is even more tricky, check out PanamaGL : https://gitlab.com/jzy3d/panama-gl (needs contributors)
  79. JEP 454: Foreign Function & Memory API Javadoc JDK 22

    java.base/java.lang.foreign Oracle Devguide for JDK 21: Foreign Function and Memory API JExtract : https://github.com/openjdk/jextract • From C to Java Code using Panama by Johannes Bechberger • The Panama Dojo: Black Belt Programming with Java 21 & the FFM API by Per Minborg • JVMLS: Project Panama - Foreign Function & Memory API by Maurizio Cimadamore Some projects • Vulkan on macOs : GitHub - chengenzhao/java-vulkan-mac Foreign Function and Memory API - jextract