Introduction
JAVA can call native methods. The official calling method is called JNI, and the full name is called java native interface. To use JNI, we need to define the native method in the JAVA code, then create a C language header file through the javah command, then use C or C++ language to implement the method in this header file, compile the source code, and finally compile the compiled The file is introduced into the classpath of JAVA and can be run.
Although JAVA officially provides a way to call native methods, it seems that this method is a bit cumbersome and not so convenient to use.
So is there a more concise form of calling native methods? The answer is yes, this is the JNA to be talked about today.
A Preliminary Study of JNA
The full name of JNA is Java Native Access, which provides us with a simpler way to access local shared library resources. If you use JNA, you only need to write the corresponding java code, no need to write JNI or local code, very convenient.
Essentially JNA uses a small JNI library stub to be able to call native methods dynamically.
JNA is a jar package. The latest version is 5.10.0. We can refer to it as follows:
<dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.10.0</version> </dependency>
JNA is a jar package. In addition to the basic JAVA class files, there are many platform-related files in it. These platform-related folders are all libjnidispatch* library files.
<img src="https://img-blog.csdnimg.cn/884d316db24a444fb9e8ea34d608e5a8.png" style="zoom:50%"/>
You can see that different platforms correspond to different dynamic libraries.
The essence of JNA is to encapsulate most of the native methods into a dynamic library in a jar package, and provide a series of mechanisms to automatically load this dynamic library.
Next, let's look at an example using JNA specifically:
public class JNAUsage { public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.load((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class); void printf(String format, Object... args); } public static void main(String[] args) { CLibrary.INSTANCE.printf("Hello, World\n"); for (int i=0;i < args.length;i++) { CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]); } } }
In this example, we want to load the system's c lib and use the printf method in the c lib.
The specific method is to create a CLibrary interface, which inherits from Library, then use the Native.load method to load the c lib, and finally define the methods in the lib to be used in this interface.
So how does JNA load the native lib? Let's take a look together.
The process of JNA loading native lib
Before explaining how JNA loads native lib, let's review how JNI loads native lib?
In JNI, we first define the native method to be called in the java code, then use the javah command to create a C header file, and then use C or C++ to implement this header file.
The next most important step is to add the generated dynamic link library to the classpath of JAVA, so that when JAVA calls the native method, it can be loaded into the corresponding library file.
For the JNA example above, running it directly yields the following results:
Hello, World
We can add JVM parameters to the program: -Djna.debug_load=true, so that the program can output some debugging information, and the result of running again is as follows:
12月24, 2021 9:16:05 下午com.sun.jna.Native extractFromResourcePath信息: Looking in classpath from jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7 for /com/sun/jna/darwin-aarch64/libjnidispatch.jnilib 12月24, 2021 9:16:05 下午com.sun.jna.Native extractFromResourcePath信息: Found library resource at jar:file:/Users/flydean/.m2/repository/net/java/dev/jna/jna/5.10.0/jna-5.10.0.jar!/com/sun/jna/darwin-aarch64/libjnidispatch.jnilib 12月24, 2021 9:16:05 下午com.sun.jna.Native extractFromResourcePath信息: Extracting library to /Users/flydean/Library/Caches/JNA/temp/jna17752159487359796115.tmp 12月24, 2021 9:16:05 下午com.sun.jna.NativeLibrary loadLibrary信息: Looking for library 'c' 12月24, 2021 9:16:05 下午com.sun.jna.NativeLibrary loadLibrary信息: Adding paths from jna.library.path: null 12月24, 2021 9:16:05 下午com.sun.jna.NativeLibrary loadLibrary信息: Trying libc.dylib 12月24, 2021 9:16:05 下午com.sun.jna.NativeLibrary loadLibrary信息: Found library 'c' at libc.dylib Hello, World
Looking closely at the output above, we can get an overview of the JNA workflow. The workflow of JNA can be divided into two parts, the first part is Library Loading, and the second part is Native Library Loading.
The classes corresponding to the two parts are com.sun.jna.Native and com.sun.jna.NativeLibrary.
The first part of Library Loading means to load the shared lib file jnidispatch into System. The loading order is as follows:
- jna.boot.library.path.
- Use System.loadLibrary(java.lang.String) to find from the system's library path. If you don't want to look in the system libary path, you can set jna.nosys=true.
- If it is not found from the above path, loadNativeDispatchLibrary will be called to decompress the jnidispatch in jna.jar to the local, and then load it. If you don't want to look it up from the classpath, you can set jna.noclasspath=true. If you don't want to unpack from the jna.jar file, you can set jna.nounpack=true.
- If your system has security restrictions on extracting files from jar files, such as SELinux, then you need to manually install jnidispatch at an accessible address, and then use 1 or 2 to set the loading method and path.
When jnidispatch is loaded, the system variable jna.loaded=true will be set, indicating that the lib of jna has been loaded.
By default, the name of the lib file we load is jnidispatch, you can also modify it by setting jna.boot.library.name.
Let's take a look at the core code of loadNativeDispatchLibrary:
String libName = "/com/sun/jna/" + Platform.RESOURCE_PREFIX + "/" + mappedName; File lib = extractFromResourcePath(libName, Native.class.getClassLoader()); if (lib == null) { if (lib == null) { throw new UnsatisfiedLinkError("Could not find JNA native support"); } } LOG.log(DEBUG_JNA_LOAD_LEVEL, "Trying {0}", lib.getAbsolutePath()); System.setProperty("jnidispatch.path", lib.getAbsolutePath()); System.load(lib.getAbsolutePath()); jnidispatchPath = lib.getAbsolutePath();
The first is to find the stub lib file: /com/sun/jna/darwin-aarch64/libjnidispatch.jnilib, by default this lib file is in the jna.jar package, so you need to call the extractFromResourcePath method to copy the lib file in the jar package into a temporary file, and then call the System.load method to load it.
The second part is to call the loadLibrary method in com.sun.jna.NativeLibrary to load the lib to be loaded in the JAVA code.
There are some search path rules when loadingLibrary are as follows:
- jna.library.path, the path of the user-defined jna lib, the search starts from the user-defined path first.
- jna.platform.library.path, the lib path related to the platform.
- If it is on the OSX operating system, it will search ~/Library/Frameworks, /Library/Frameworks, and /System/Library/Frameworks to query the corresponding Frameworks.
- Finally, it will look for the Context class loader classpath (classpath or resource path), the specific format is ${os-prefix}/LIBRARY_FILENAME. If the content is in a jar package, the file will be extracted to jna.tmpdir and then loaded.
All the search logic is implemented in the method loadLibrary of NativeLibrary. The method body is too long, so I won't list them here. Interested friends can explore by themselves.
Struct parameters in native methods
If the parameter passed in by the native method is a basic type, the basic type can be used to define the native method in JNA.
But sometimes, the parameter of the native method itself is a structure type, how should we deal with this situation?
Taking the kernel32 library in Windows as an example, there is a GetSystemTime method in this lib, which passes in a time structure.
We define the structure of the parameters by inheriting from Structure:
@FieldOrder({ "wYear", "wMonth", "wDayOfWeek", "wDay", "wHour", "wMinute", "wSecond", "wMilliseconds" }) public static class SYSTEMTIME extends Structure { public short wYear; public short wMonth; public short wDayOfWeek; public short wDay; public short wHour; public short wMinute; public short wSecond; public short wMilliseconds; }
Then define a Kernel32 interface:
public interface Kernel32 extends StdCallLibrary { Kernel32 INSTANCE = (Kernel32) Native.load("kernel32", Kernel32.class); Kernel32 SYNC_INSTANCE = (Kernel32) Native.synchronizedLibrary(INSTANCE); void GetSystemTime(SYSTEMTIME result); }
Finally call like this:
Kernel32 lib = Kernel32.INSTANCE; SYSTEMTIME time = new SYSTEMTIME(); lib.GetSystemTime(time); System.out.println("Today's integer value is " + time.wDay);
Summarize
The above is the basic use of JNA. Please look forward to the follow-up articles for the in-depth use of JNA.
Code for this article: https://github.com/ddean2009/learn-java-base-9-to-20.git
This article has been included in http://www.flydean.com/02-jna-overview/
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!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。