Explain how to use the Java Native Access library (JRA) to access native libraries. References: Using JNA to Access Native Dynamic Libraries

1. Introduction

  • 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.
  • Trace-offs:
    • Can’t directly use static libraries
    • Slower when compared to handcrafted JNI code. => JNA today is probably the best available choice to access native code from Java.

2. JNA Project Setup

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

3. Using JNA

  • Step1: Create a Java interface that extends JNA’s Library interface to describe the methods and types used when calling the target native code.
  • Step2: Pass this interface to JNA which returns a concrete implementation of this interface that we use to invoke native methods.

3.1. Example 1: Calling methods from the C Standard Library

  1. CMath.java

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

public interface CMath extends Library {
    CMath INSTANCE = Native.load(Platform.isWindows() ? "msvcrt" : "c", CMath.class);
    double cosh(double value);
}
  • load() method takes two arguments, the return a concrete implementation of this interface, allowing to call any of its methods.
    • The dynamic lib name
    • The Java interface describing the methods that we’ll use
  • We don’t have to add the .so (Linux) or .dll(Window) extension as they’are implied. Also, for Linux-based systems, we don’t need to specific the lib prefix.
  1. Main.java
public class Main {

	public static void main(String[] args) {
		System.out.println(CMath.INSTANCE.cosh(180));
	}

}

3.2. Example 2: Basic types Mapping

char => byte
short => short
wchar_t => char
int => int
long => com.sun.jna.NativeLong
long long => long
float => float
double => double
char * => String
  • JNA provides the NativeLong type, which uses the proper type depending on the system’architecture.

3.3. Structures and Unions

  • Given this C struct and union
struct foo_t {
    int field1;
    int field2;
    char *field3;
};

union foo_u {
    String foo;
    double bar;
};
  • Java peer class would be
@FieldOrder({"field1","field2","field3"})
public class FooType extends Structure {
    int field1;
    int field2;
    String field3;
};

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

MyUnion u = new MyUnion();
u.foo = "test";
u.setType(String.class);
lib.some_method(u);
  • Java requires the @FiledOrder annotation so it can properly serialize data into a memory buffer before using it as an argument to the target method.