Introduction to the

Both JNI and JNA ultimately call native methods, but for JAVA programs, there must be an entrance to call native methods, that is to say, we need to define the native methods to be called in JAVA methods.

For JNI, we can use the native keyword to define native methods. So what are the ways in JNA to define native methods in JAVA code?

Library Mapping

To call native methods, the first thing you need to do is load native lib files. We call this process Library Mapping, which means Mapping the native Library into Java code.

There are two Library mapping methods in JNA, interface and Direct Mapping.

Let’s take a look at interface Mapping. If we want to load C Library, if we use interface Mapping, we need to create an interface inherited library:

public interface CLibrary extends Library {
    CLibrary INSTANCE = (CLibrary)Native.load("c", CLibrary.class);
}
Copy the code

Library is an interface in the code above, and all interface mapping needs to inherit from this Library.

The C library to be used is then loaded inside the interface using the native.load method.

In the code above, the load method takes two arguments, the first being the name of the library and the second being interfaceClass.

The following table shows the mapping between the Library Name and the Name passed in:

OS Library Name String
Windows user32.dll user32
Linux libX11.so X11
Mac OS X libm.dylib m
Mac OS X Framework /System/Library/Frameworks/Carbon.framework/Carbon Carbon
Any Platform current process null

In fact, load can also accept an Options Map parameter. By default, the method name to be called in the JAVA Interface is the method name defined in the Native Library, but in some cases we may need to use a different name in our JAVA code. In this case, we can pass in the third parameter map, The map key can be OPTION_FUNCTION_MAPPER, and its value is a FunctionMapper that maps method names in JAVA to the Native Library.

Each incoming native library can be represented by an instance of the NativeLibrary. The NativeLibrary instance can also by calling NativeLibrary. GetInstance (String).

Direct mapping is a static block that calls the native. Register method to load the native libary.

public class CLibrary { static { Native.register("c"); }}Copy the code

Function Mapping

Once we’ve loaded the Native Library, it’s time to define the functions that need to be called. This is actually doing a Mapping from JAVA code to a Function in native lib. We call this Function Mapping.

Like Library Mapping, Function Mapping has two ways. Interface Mapping and Direct Mapping respectively.

In interface Mapping, we only need to define an identical method according to the method name in native library. This method does not need to be implemented or modified using Native like JNI, as shown below:

public interface CLibrary extends Library {
    int atol(String s);
}
Copy the code

Note that the method names in JAVA do not have to be the same as those in the Native Library. You can do this by passing in a FunctionMapper to the native.

Alternatively, you can use direct mapping by adding a native modifier to the method:

public class HelloWorld { public static native double cos(double x); public static native double sin(double x); static { 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)); }}Copy the code

For direct Mapping, JAVA methods can map to any static or object method in the Native Library.

Although Direct Mapping is similar to Java JNI, there are some limitations to Direct Mapping.

In most cases, direct mapping and interface mapping has the same map type, but does not support Pointer/Structure/String WString/NativeMapped array as a function parameter values.

Direct Mapping does not support NIO Buffers or an array of primitive types as a return value when TypeMapper or NativeMapped is used.

If you want to use a wrapper class of the base type, you must use a custom TypeMapper.

Object method mapping in JAVA, which eventually creates a Function object.

Invocation Mapping

After Library Mapping and Function Mapping, we’ll look at Invocation Mapping.

The Invocation Mapping represents the OPTION_INVOCATION_MAPPER in the Library and the value is an InvocationMapper.

FunctionMapper allows you to define method names in JAVA that are different from those in Native Lib, but you can’t change the state or procedure of a method call.

InvocationMapper goes a step further and allows you to reconfigure function calls at will, including changing method names and reordering, adding, or removing parameters.

Here’s an example:

new InvocationMapper() { public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) { if (m.getName().equals("stat")) { final Function f = lib.getFunction("_xstat"); return new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) { Object[] newArgs = new Object[args.length+1]; System.arraycopy(args, 0, newArgs, 1, args.length); newArgs[0] = Integer.valueOf(3); // _xstat version return f.invoke(newArgs); }}; } return null; }}Copy the code

We implement getInvocationHandler in InvocationMapper to find the specific native lib according to the method in the given JAVA code. We then fetch function in the lib, and finally call function’s invoke method to make the final call to the method.

During this process, we can modify the parameters passed by the party, or do whatever we want.

Another case is inline functions or preprocessing macros in C, as shown below:

// Original C code (macro and inline variations)
   #define allocblock(x) malloc(x * 1024)
   static inline void* allocblock(size_t x) { return malloc(x * 1024); }
Copy the code

The above code defines an allocblock(x) macro that is actually equal to malloc(x * 1024). In this case, we can use InvocationMapper to replace the allocblock with a specific malloc:

// Invocation mapping new InvocationMapper() { public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) { if (m.getName().equals("allocblock")) { final Function f = lib.getFunction("malloc"); return new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) { args[0] = ((Integer)args[0]).intValue() * 1024; return f.invoke(newArgs); }}; } return null; }}Copy the code

Preventing VM crashes

There are definitely some problems with mapping JAVA methods to native methods. If the mapping method is not correct or the parameters do not match, memory access errors may occur and VM crashes may occur.

By calling native.setprotected (true), you can turn a VM crash into a corresponding JAVA exception. Of course, not all platforms support protection, and if they do not, So native.isprotected () returns false.

If you want to use protection, you should also use the JSIG Library to prevent signals from colliding with the JVM’s signals. Libjsig. so is stored in the lib directory of the JRE. Java.home /lib/{java.home}/lib/java.home/lib/{os.arch}/libjsig.so can be used by setting the environment variable to LD_PRELOAD (or LD_PRELOAD_64).

Performance considerations

We have mentioned two MAPPING modes of JNA, namely interface Mapping and Direct Mapping. Direct mapping is more efficient because it calls native methods more efficiently.

However, as mentioned above, direct Mapping has some limitations in use, so we need to make trade-offs when using it.

In addition, we need to avoid using the encapsulated class of the basic Type, because for native methods, only the matching of the basic Type, if we want to use the encapsulated class, we must use Type mapping, resulting in performance loss.

conclusion

JNA is a great tool for calling native methods, and if you know how many of them you can use, it’s a great addition.

This article is available at www.flydean.com/03-jna-libr…

The most popular interpretation, the most profound dry goods, the most concise tutorial, many tips you didn’t know waiting for you to discover!

Welcome to pay attention to my public number: “procedures those things”, understand technology, more understand you!