Tags: java, programming

Simple Java to C bindings via JNA

This post is an introduction to JNI, an FFI system for Java.

Most languages offer a way to bind to (use) shared libraries, which are often written in C (Rust is becoming popular too). The general name for such a facility is foreign function interface (FFI). FFIs facilitate code reuse, and use of operating system-level functions that would not otherwise be possible.

There are two significant FFI systems for Java. The older is Java Native Interface (JNI)—an official Java standard. The JSS Java binding to the NSS cryptography and security library makes heavy use of JNI. The main drawbacks of JNI are that it involves writing C, and is boilerplate-heavy.

Java Native Access (JNA) offers a more lightweight approach. You import JNA as a library and define your binding as a native Java object. There is only a small amount of boilerplate to import the JNA packages, open the shared library, and declare Java method signatures for the functions you want to use. JNA performs automatic conversion between native Java and C types.

If you are familiar with Python, you might recognise that the JNA approach is similar to cffi. In fact, JNA and cffi use the same underlying FFI library, libffi.

Using JNA in Dogtag §

To simplify and speed up FreeIPA startup, I needed to implement systemd notification support in Dogtag PKI. Dogtag (when so configured) should call sd_notify(3) to notify the system service manager when it has started up and is ready to service requests.

Dogtag already uses JNI in a few places (as does some of its dependencies, including JSS). But I was not keen to use JNI, with all its complexity, for this small use case. A colleague pointed me to JNA, and I decided to give it a go.

The resulting code is so small I’ll just include it all here, with commentary. (I made some changes for clarity; you can review the actual patch in the pull request).

package com.netscape.cmscore.systemd;

import com.sun.jna.Library;
import com.sun.jna.Native;

public class SystemdStartupNotifier {

Import JNA and begin the class definition.

interface Systemd extends Library {
    public int sd_booted();
    public int sd_notify(int unset_env, String state);
}

Declare an interface to the shared library by extending sun.jna.Native. Method signatures must match the native function signatures, according to the type mappings.

Systemd systemd = null;

public void init() {
    systemd = Native.load("systemd", Systemd.class);
}

init() gets called by initialisation code. Native.load() loads libsystemd.so and initialises the foreign library proxy with respect to the Systemd interface. The proxy object is assigned to the instance variable systemd. An alternative approach is to assign the proxy object to a static variable in the interface definition (example).

boolean notify(String status) {
    if (!systemd.sd_booted()) {
        return true;
    } else {
        int r = systemd.sd_notify(
            0 /* don't unset environment */,
            status);
        if (r < 1) {
            System.err.println("sd_notify failed");
            return false;
        } else {
            return true;
        }
    }
}

notify() makes two foreign calls. First it calls sd_booted(3) to see if the system was booted using systemd. If not, we return (successfully). If the program is running under systemd it calls sd_notify(3), logging an error on failure.

That’s pretty much all there is to it. This is much, much nicer than JNI.

Discussion §

The adoption of JNA in Dogtag—which already (and still) uses JNI—was not without debate. But JNA is mature, widely available and supported in Dogtag’s target platforms (Fedora and RHEL). In the end, it was agreed that JNA is a nice approach. If JNA becomes problematic for any reason we can reimplement the binding to use JNI instead. The patch was accepted.

As the Dogtag experience demonstrates, where multiple FFI systems are available it is not necessarily an either/or choice. JNI and JNA now happily coexist in the Dogtag database. It would be nice to gradually migrate Dogtag away from JNI and use JNA exclusively, but this is not a priority.

There are more advanced topics that were not covered in this post. These include callbacks, custom type mapping and dealing with C struct and union types. The in-tree documentation provides guidance on these and other advanced topics.

Creative Commons License
Except where otherwise noted, this work is licensed under a Creative Commons Attribution 4.0 International License .