Introduction

Whether it is JNI or JNA, the native method is ultimately called, but for a JAVA program, an entry to call the native method must be required, that is to say, we need to define the native method to be called in the JAVA method.

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

Library Mapping

To call a native native method, the first thing to do is to load the native lib file. We call this process Library Mapping, which means mapping the native library to the java code.

There are two methods of Library mapping in JNA, namely interface and direct mapping.

Let's look at the interface mapping first. If we want to load the C library, if we use the interface mapping method, we need to create an interface that inherits the Library:

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

The Library in the above code is an interface, and all interface mappings need to inherit this Library.

Then inside the interface, load the c library to be used by using the Native.load method.

In the above code, the load method passes in two parameters, the first parameter is the name of the library, and the second parameter is the interfaceClass.

The following table shows the mapping between Library Name and incoming name:

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 a Map parameter of options. 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 the JAVA code. In this case, the third parameter map can be passed in , the key of the map can be OPTION_FUNCTION_MAPPER, and its value is a FunctionMapper, which is used to map the method name in JAVA to the native library.

Each incoming native library can be represented by an instance of NativeLibrary. An instance of this NativeLibrary can also be obtained by calling NativeLibrary.getInstance(String).

Another way to load native libary is direct mapping. Direct mapping uses the method of calling Native.register in the static block to load the native library, as shown below:

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

Function Mapping

When we have loaded the native library, the next step is to define the functions that need to be called. In fact, it is a mapping from JAVA code to functions in native lib, which we call Function Mapping.

Like Library Mapping, Function Mapping also has two ways. They are interface mapping and direct mapping respectively.

In interface mapping, we only need to define a same method according to the method name in the native library. This method does not need to be implemented and does not need to be decorated with native like JNI, as shown below:

 public interface CLibrary extends Library {
    int atol(String s);
}
Note that we mentioned above that the method name in JAVA does not necessarily have to be the same as the method name in the native library. You can do this by passing a FunctionMapper to the Native.load method.

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));
    }
}

For direct mapping, JAVA methods can be mapped to any static or object methods in the native library.

Although direct mapping is somewhat similar to our commonly used java JNI, direct mapping has some limitations.

In most cases, direct mapping and interface mapping have the same mapping type, but do not support Pointer/Structure/String/WString/NativeMapped arrays as function parameter values.

In the case of using TypeMapper or NativeMapped, direct mapping does not support NIO Buffers or arrays of primitive types as return values.

If you want to use a wrapper class for the underlying type, you must use a custom TypeMapper.

In terms of method mapping in object JAVA, the mapping will eventually create a Function object.

Invocation Mapping

After talking about library mapping and function mapping, let's talk about Invocation Mapping.

Invocation Mapping represents OPTION_INVOCATION_MAPPER in Library, and its corresponding value is an InvocationMapper.

We mentioned FunctionMapper before, which can realize that the method name defined in JAVA is different from the method name in native lib, but it cannot modify the state or process of method invocation.

And InvocationMapper goes a step further, allowing you to arbitrarily reconfigure function calls, including changing method names and reordering, adding or removing parameters.

Here is 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;
       }
   }

Looking at the above call example, it feels a bit like a reflection call. We implement the getInvocationHandler method in the InvocationMapper, find the specific native lib according to the method in the given JAVA code, then get the function in the lib, and finally call the function The invoke method implements the final invocation of the method.

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

There is also a case of inline functions or preprocessor macros in the C language, as follows:

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

The above code defines an allocblock(x) macro, which is actually equal to malloc(x * 1024). In this case, you can use InvocationMapper to replace 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;
       }
   }

Prevent VM crashes

There will definitely be some problems with the mapping between JAVA methods and native methods. If the mapping method is incorrect or the parameters do not match, memory access errors are likely to occur, and the VM may crash.

By calling Native.setProtected(true), VM crashes can be converted into corresponding JAVA exceptions. Of course, not all platforms support protection. If the platform does not support protection, Native.isProtected() will return false.

If you want to use protection, you should also use the jsig library to prevent the conflict between signals and JVM signals. libjsig.so is generally stored in the lib directory of the JRE, ${java.home}/lib/${os.arch}/libjsig.so, which can be used by setting the environment variable to LD_PRELOAD (or LD_PRELOAD_64).

performance considerations

Above we mentioned two mapping methods of JNA, namely interface mapping and direct mapping. In comparison, direct mapping is more efficient because direct mapping calls native methods more efficiently.

But we also mentioned above that there are some restrictions on the use of direct mapping, so we need to make a trade-off when using it.

In addition, we need to avoid using the wrapper class of the base type, because for the native method, only the base type is matched. If you want to use the wrapper class, you must use Type mapping, resulting in performance loss.

Summarize

JNA is a powerful tool for calling native methods. If the number is mastered, it will definitely be even more powerful.

This article has been included in http://www.flydean.com/03-jna-library-mapping/

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com