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.
= null;
Systemd systemd
public void init() {
= Native.load("systemd", Systemd.class);
systemd }
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 */,
);
statusif (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.