Java Native Access (JNA)

repository·master·Indexed 27 days ago

https://github.com/java-native-access/jna

A library that allows Java applications to call native shared libraries directly without writing JNI code. It uses Java interfaces to describe native functions and structures, supporting automatic mapping of primitive types, string conversion, complex types (Structures, Unions), and callbacks. The project includes JNA Core for basic binding and JNA Platform for cross-platform mappings, including Win32 and Linux system statistics via LibC and libudev.

Tokens
21.8K
Snippets
36
Records
129
Agent score
94%

What's inside JNA

  1. Overview of Java Native Access (JNA)

    master
    JNA provides Java programs easy access to native shared libraries without writing JNI or native code. It allows you to call directly into native functions using natural Java method invocation, similar to Python's ctypes or Windows' Platform/Invoke. Developers use a Java interface to describe functions and structures in the target native library, which JNA then invokes via a small JNI library stub.
  2. Setup JNA on Android

    master

    To use JNA on Android, add the @aar dependency to your Gradle file and configure Proguard to prevent stripping JNA classes.

    compile 'net.java.dev.jna:jna:4.4.0@aar'

    Proguard Rules:

    -dontwarn java.awt.*
    -keep class com.sun.jna.* { *; }
    -keep class * extends com.sun.jna.* { *; }
    -keepclassmembers class * extends com.sun.jna.* { public *; }
  3. Build JNA for FreeBSD x86-64

    master

    This recipe allows building the FreeBSD x86-64 native library using QEMU. The process involves fetching a FreeBSD 13.2 amd64 image, resizing the disk, launching the amd64 emulator, installing prerequisites (OpenJDK 17, build tools, Apache Ant), transferring the JNA source, and running the build via Ant.

    # Fetch image
    wget https://download.freebsd.org/releases/VM-IMAGES/13.2-RELEASE/amd64/Latest/FreeBSD-13.2-RELEASE-amd64.qcow2.xz
    xz -d FreeBSD-13.2-RELEASE-amd64.qcow2.xz
    
    # Ensure there is enough space in the image
    qemu-img resize -f qcow2 FreeBSD-13.2-RELEASE-amd64.qcow2 +5G
    
    # Launch image
    qemu-system-amd64 -m 4096M -drive file=FreeBSD-13.2-RELEASE-amd64.qcow2
    
    gpart show /dev/ada0
    gpart recover /dev/ada0
    gpart show /dev/ada0
    gpart resize -i 4 /dev/ada0
    growfs /
    
    # Exit single user mode (BSD boots to multi-user)
    exit
    
    # Login as root
    
    # Set keyboard configuration
    kbdmap
    
    # Set current date and time (YYYYMMDDHHMM)
    date 202403081928
    
    # Install prerequisites - part 1 - java, build system, rsync
    pkg install openjdk17 wget automake rsync gmake gcc bash texinfo
    
    # Install prerequisites - part 2 - ant
    wget https://dlcdn.apache.org/ant/binaries/apache-ant-1.10.14-bin.zip
    unzip apache-ant-1.10.14-bin.zip
    
    # Transfer JNA source code to build environment
    rsync -av --exclude=.git USER@BUILD_HOST:src/jnalib/ jnalib/
    
    # Build JNA and run unittests
    cd jnalib
    chmod +x native/libffi/configure native/libffi/install-sh
    /root/apache-ant-1.10.14/bin/ant
    
    # Copy jna native library back to host system
    scp lib/native/freebsd-x86-64.jar USER@BUILD_HOST:src/jnalib/lib/native
  4. Map a native library using the Library interface

    master

    The standard way to map native functions is to create a Java interface that extends com.sun.jna.Library (or com.sun.jna.StdCallLibrary for Windows __stdcall conventions).

    Inside the interface, define a static INSTANCE using Native.load(String libraryName, Class<T> interfaceClass). You can also use Native.synchronizedLibrary(instance) to wrap calls in a synchronized block, ensuring only one native call occurs at a time.

    import com.sun.jna.Library;
    import com.sun.jna.Native;
    import com.sun.jna.Platform;
    
    public interface CLibrary extends Library {
        CLibrary INSTANCE = (CLibrary) Native.load((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class);
    
        void printf(String format, Object... args);
    }
    
    // Usage
    CLibrary.INSTANCE.printf("Hello, World\n");
  5. Use the JNA Platform Library for pre-mapped functions

    master

    JNA provides platform.jar, which contains cross-platform mappings and common platform functions (especially Win32). Before mapping your own native functions, check the com.sun.jna.platform package to see if they are already implemented.

    Mapping Conventions:

    • Structures: Mapped by header name. Example: ShlObj.h structures are in com.sun.jna.platform.win32.ShlObj.
    • Functions: Mapped by library name. Example: Advapi32.dll functions are in com.sun.jna.platform.win32.Advapi32.
    • Wrappers: Simplified interfaces for libraries are suffixed with Util. Example: com.sun.jna.platform.win32.Advapi32Util.

    Available Cross-Platform Utilities (com.sun.jna.platform):

    • FileMonitor: File system watcher.
    • FileUtils: File operations (e.g., moving to recycle bin).
    • KeyboardUtils: Keyboard state functions.
    • WindowUtils: Window management (e.g., transparent/non-rectangular windows).
  6. Use Direct Mapping for improved performance

    master

    JNA supports a direct mapping method that can substantially improve performance, approaching the speed of custom JNI. Unlike standard interface mapping, you define native methods directly within a class (as static native or instance methods) and register them using Native.register() inside a static initializer.

    Key Constraints:

    • Varargs are not supported.
    • Argument Types: Supports the same type mappings as interface mapping, except for arrays of Pointer, Structure, String, WString, or NativeMapped as function arguments.
    • Return Types: Does not support NIO Buffers or primitive arrays as types returned by type mappers or NativeMapped.
    • Primitive Wrappers: Primitive wrapper classes (e.g., Integer, Double) can only be used if a custom TypeMapper is provided. Since direct mapping is intended for performance, using wrappers is discouraged due to overhead.
    • Method Scope: Methods can be static or member methods; the class or object instance is ignored on the native side.
    import com.sun.jna.*;
    
    public class HelloWorld {
                
        public static native double cos(double x);
        public static native double sin(double x);
        
        static {
            // Native.register() takes the name of your native library,
            // same as Native.load() would.
            Native.register(Platform.C_LIBRARY_NAME);
        }
    
        public static void main(String[] args) {
            System.out.println("cos(0)=" + cos(0));
            System.out.println("sin(0)=" + sin(0));
        }
    }
  7. Develop JNA using IntelliJ IDEA

    master

    To develop against JNA without rebuilding the JNA JAR file between every code change, you can set up a local module in IntelliJ IDEA. This approach uses the idea-jar target to provide the necessary native components.

    1. Generate the necessary JAR: Run the idea-jar target (via Ant) to generate a JAR containing all native bits required by JNA.
    2. Create a Module: Create a new module for JNA within IntelliJ IDEA.
    3. Configure Source Folders: Mark the src folder as Sources and the testsrc folder as Test Sources.
    4. Add Libraries: Add a library to the module containing:
      • All JAR files from the lib directory.
      • All JAR files from the lib/test directory.
      • The idea-dispatch.jar generated by the idea-jar target.

    Once configured, you can use JNA in your own code via this module instead of including a standard JNA JAR, which significantly speeds up the development cycle.

  8. Use ByReference types for pointer-to-type arguments

    master

    When a C function accepts a pointer-to-type argument (e.g., void func(int* p)), you can use JNA's ByReference types to capture the value returned by the function. This allows the native code to write a value into the memory address provided by Java.

    Commonly used types include:

    • PointerByReference: For void** or similar pointer-to-pointer types.
    • IntByReference: For int* types.

    While you can use a single-element Java array as an alternative, using ByReference classes is the recommended convention as it more clearly conveys the intent of the code.

  9. Use Java Structures for C structs

    master

    When a native function requires a pointer to a struct, use a Java class that extends Structure.

    To ensure correct memory layout, you must specify the order of the fields. This is done by either:

    1. Using the @FieldOrder annotation with the field names in order.
    2. Overriding the getFieldOrder() method to return a list of field names in order.

    It is a best practice to define these structures as public static classes within your library interface definition so they can inherit custom type mappings defined for that interface.