Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we'll see how to use the Java Native Access library (JNA for short) to access native libraries without writing any JNI (Java Native Interface) code.

2. Why JNA?

For many years, Java and other JVM-based languages have, to a large extent, fulfilled its “write once, run everywhere” motto. However, sometimes we need to use native code to implement some functionality:

  • Reusing legacy code written in C/C++ or any other language able to create native code
  • Accessing system-specific functionality not available in the standard Java runtime
  • Optimizing speed and/or memory usage for specific sections of a given application.

Initially, this kind of requirement meant we'd have to resort do JNI – Java Native Interface. While effective, this approach has its drawbacks and was generally avoided due to a few issues:

  • Requires developers to write C/C++ “glue code” to bridge Java and native code
  • Requires a full compile and link toolchain available for every target system
  • Marshaling and unmarshalling values to and from the JVM is a tedious and error-prone task
  • Legal and support concerns when mixing Java and native libraries

JNA came to solve most of the complexity associated with using JNI. In particular, there's no need to create any JNI code to use native code located in dynamic libraries, which makes the whole process much easier.

Of course, there are some trade-offs:

  • We can't directly use static libraries
  • Slower when compared to handcrafted JNI code

For most applications, though, JNA's simplicity benefits far outweigh those disadvantages. As such, it is fair to say that, unless we have very specific requirements, JNA today is probably the best available choice to access native code from Java – or any other JVM-based language, by the way.

3. JNA Project Setup

The first thing we have to do to use JNA is to add its dependencies to our project's pom.xml:

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.6.0</version>
</dependency>

The latest version of jna-platform can be downloaded from Maven Central.

4. Using JNA

Using JNA is a two-step process:

  • First, we create a Java interface that extends JNA's Library interface to describe the methods and types used when calling the target native code
  • Next, we pass this interface to JNA which returns a concrete implementation of this interface that we use to invoke native methods

4.1. Calling Methods from the C Standard Library

For our first example, let's use JNA to call the cosh function from the standard C library, which is available in most systems. This method takes a double argument and computes its hyperbolic cosine. A-C program can use this function just by including the <math.h> header file:

#include <math.h>
#include <stdio.h>
int main(int argc, char** argv) {
    double v = cosh(0.0);
    printf("Result: %f\n", v);
}

Let's create the Java interface needed to call this method:

public interface CMath extends Library { 
    double cosh(double value);
}

Next, we use JNA's Native class to create a concrete implementation of this interface so we can call our API:

CMath lib = Native.load(Platform.isWindows()?"msvcrt":"c", CMath.class);
double result = lib.cosh(0);

The really interesting part here is the call to the load() method. It takes two arguments: the dynamic library name and a Java interface describing the methods that we'll use. It returns a concrete implementation of this interface, allowing us to call any of its methods.

Now, dynamic library names are usually system-dependent, and C standard library is no exception: libc.so in most Linux-based systems, but msvcrt.dll in Windows. This is why we've used the Platform helper class, included in JNA, to check which platform we're running in and select the proper library name.

Notice that we don't have to add the .so or .dll extension, as they're implied. Also, for Linux-based systems, we don't need to specify the “lib” prefix that is standard for shared libraries.

Since dynamic libraries behave like Singletons from a Java perspective, a common practice is to declare an INSTANCE field as part of the interface declaration:

public interface CMath extends Library {
    CMath INSTANCE = Native.load(Platform.isWindows() ? "msvcrt" : "c", CMath.class);
    double cosh(double value);
}

4.2. Basic Types Mapping

In our initial example, the called method only used primitive types as both its argument and return value. JNA handles those cases automatically, usually using their natural Java counterparts when mapping from C types:

  • char => byte
  • short => short
  • wchar_t => char
  • int => int
  • long => com.sun.jna.NativeLong
  • long long => long
  • float => float
  • double => double
  • char * => String

A mapping that might look odd is the one used for the native long type. This is because, in C/C++, the long type may represent a 32- or 64-bit value, depending on whether we're running on a 32- or 64-bit system.

To address this issue, JNA provides the NativeLong type, which uses the proper type depending on the system's architecture.

4.3. Structures and Unions

Another common scenario is dealing with native code APIs that expect a pointer to some struct or union type. When creating the Java interface to access it, the corresponding argument or return value must be a Java type that extends Structure or Union, respectively.

For instance, given this C struct:

struct foo_t {
    int field1;
    int field2;
    char *field3;
};

Its Java peer class would be:

@FieldOrder({"field1","field2","field3"})
public class FooType extends Structure {
    int field1;
    int field2;
    String field3;
};

JNA requires the @FieldOrder annotation so it can properly serialize data into a memory buffer before using it as an argument to the target method.

Alternatively, we can override the getFieldOrder() method for the same effect. When targeting a single architecture/platform, the former method is generally good enough. We can use the latter to deal with alignment issues across platforms, that sometimes require adding some extra padding fields.

Unions work similarly, except for a few points:

  • No need to use a @FieldOrder annotation or implement getFieldOrder()
  • We have to call setType() before calling the native method

Let's see how to do it with a simple example:

public class MyUnion extends Union {
    public String foo;
    public double bar;
};

Now, let's use MyUnion with a hypothetical library:

MyUnion u = new MyUnion();
u.foo = "test";
u.setType(String.class);
lib.some_method(u);

If both foo and bar where of the same type, we'd have to use the field's name instead:

u.foo = "test";
u.setType("foo");
lib.some_method(u);

4.4. Using Pointers

JNA offers a Pointer abstraction that helps to deal with APIs declared with untyped pointer – typically a void *. This class offers methods that allow read and write access to the underlying native memory buffer, which has obvious risks.

Before start using this class, we must be sure we clearly understand who “owns” the referenced memory at each time. Failing to do so will likely produce hard to debug errors related to memory leaks and/or invalid accesses.

Assuming we know what we're doing (as always), let's see how we can use the well-known malloc() and free() functions with JNA, used to allocate and release a memory buffer. First, let's again create our wrapper interface:

public interface StdC extends Library {
    StdC INSTANCE = // ... instance creation omitted
    Pointer malloc(long n);
    void free(Pointer p);
}

Now, let's use it to allocate a buffer and play with it:

StdC lib = StdC.INSTANCE;
Pointer p = lib.malloc(1024);
p.setMemory(0l, 1024l, (byte) 0);
lib.free(p);

The setMemory() method just fills the underlying buffer with a constant byte value (zero, in this case). Notice that the Pointer instance has no idea to what it is pointing to, much less its size. This means that we can quite easily corrupt our heap using its methods.

We'll see later how we can mitigate such errors using JNA's crash protection feature.

4.5. Handling Errors

Old versions of the standard C library used the global errno variable to store the reason a particular call failed. For instance, this is how a typical open() call would use this global variable in C:

int fd = open("some path", O_RDONLY);
if (fd < 0) {
    printf("Open failed: errno=%d\n", errno);
    exit(1);
}

Of course, in modern multi-threaded programs this code would not work, right? Well, thanks to C's preprocessor, developers can still write code like this and it will work just fine. It turns out that nowadays, errno is a macro that expands to a function call:

// ... excerpt from bits/errno.h on Linux
#define errno (*__errno_location ())

// ... excerpt from <errno.h> from Visual Studio
#define errno (*_errno())

Now, this approach works fine when compiling source code, but there's no such thing when using JNA. We could declare the expanded function in our wrapper interface and call it explicitly, but JNA offers a better alternative: LastErrorException.

Any method declared in wrapper interfaces with throws LastErrorException will automatically include a check for an error after a native call. If it reports an error, JNA will throw a LastErrorException, which includes the original error code.

Let's add a couple of methods to the StdC wrapper interface we've used before to show this feature in action:

public interface StdC extends Library {
    // ... other methods omitted
    int open(String path, int flags) throws LastErrorException;
    int close(int fd) throws LastErrorException;
}

Now, we can use open() in a try/catch clause:

StdC lib = StdC.INSTANCE;
int fd = 0;
try {
    fd = lib.open("/some/path",0);
    // ... use fd
}
catch (LastErrorException err) {
    // ... error handling
}
finally {
    if (fd > 0) {
       lib.close(fd);
    }
}

In the catch block, we can use LastErrorException.getErrorCode() to get the original errno value and use it as part of the error handling logic.

4.6. Handling Access Violations

As mentioned before, JNA does not protect us from misusing a given API, especially when dealing with memory buffers passed back and forth native code. In normal situations, such errors result in an access violation and terminate the JVM.

JNA supports, to some extent, a method that allows Java code to handle access violation errors. There are two ways to activate it:

  • Setting the jna.protected system property to true
  • Calling Native.setProtected(true)

Once we've activated this protected mode, JNA will catch access violation errors that would normally result in a crash and throw a java.lang.Error exception. We can verify that this works using a Pointer initialized with an invalid address and trying to write some data to it:

Native.setProtected(true);
Pointer p = new Pointer(0l);
try {
    p.setMemory(0, 100*1024, (byte) 0);
}
catch (Error err) {
    // ... error handling omitted
}

However, as the documentation states, this feature should only be used for debugging/development purposes.

5. Conclusion

In this article, we've shown how to use JNA to access native code easily when compared to JNI.

As usual, all code is available over on GitHub.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
guest
0 Comments
Inline Feedbacks
View all comments