简介

JAVA是能够调用本地办法的,官网提供的调用形式叫做JNI,全称叫做java native interface。要想应用JNI,咱们须要在JAVA代码中定义native办法,而后通过javah命令创立C语言的头文件,接着应用C或者C++语言来实现这个头文件中的办法,编译源代码,最初将编译后的文件引入到JAVA的classpath中,运行即可。

尽管JAVA官网提供了调用原生办法的形式,然而如同这种办法有点繁琐,应用起来没有那么的不便。

那么有没有更加简洁的调用本地办法的模式吗?答案是必定的,这就是明天要讲的JNA。

JNA初探

JNA的全称是Java Native Access,它为咱们提供了一种更加简略的形式来拜访本地的共享库资源,如果你应用JNA,那么你只须要编写相应的java代码即可,不须要编写JNI或者本地代码,十分的不便。

实质上JNA应用的是一个小的JNI library stub,从而可能动静调用本地办法。

JNA就是一个jar包,目前最新的版本是5.10.0,咱们能够像上面这样援用它:

<dependency>            <groupId>net.java.dev.jna</groupId>            <artifactId>jna</artifactId>            <version>5.10.0</version>        </dependency>

JNA是一个jar包,它外面除了蕴含有根本的JAVA class文件之外,还有很多和平台相干的文件,这些平台相干的文件夹上面都是libjnidispatch*的库文件。

<img src="https://img-blog.csdnimg.cn/884d316db24a444fb9e8ea34d608e5a8.png" style="zoom:50%"/>

能够看到不同的平台对应着不同的动静库。

JNA的实质就是将大多数native的办法封装到jar包中的动静库中,并且提供了一系列的机制来主动加载这个动静库。

接下来咱们看一个具体应用JNA的例子:

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

这个例子中,咱们想要加载零碎的c lib,从而应用c lib中的printf办法。

具体做法就是创立一个CLibrary interface,这个interface继承自Library,而后应用Native.load办法来加载c lib,最初在这个interface中定义要应用的lib中的办法即可。

那么JNA到底是怎么加载native lib的呢?咱们一起来看看。

JNA加载native lib的流程

在解说JNA加载native lib之前,咱们先回顾一下JNI是怎么加载native lib的呢?

在JNI中,咱们首先在java代码中定义要调用的native办法,而后应用javah命令,创立C的头文件,而后再应用C或者C++来对这个头文件进行实现。

接下来最重要的一步就是将生成的动态链接库增加到JAVA的classpath中,从而在JAVA调用native办法的时候,可能加载到对应的库文件。

对于下面的JNA的例子来说,间接运行能够失去上面的后果:

Hello, World

咱们能够向程序增加JVM参数:-Djna.debug_load=true,从而让程序可能输入一些调试信息,再次运行后果如下所示:

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.jnilib12月 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.jnilib12月 24, 2021 9:16:05 下午 com.sun.jna.Native extractFromResourcePath信息: Extracting library to /Users/flydean/Library/Caches/JNA/temp/jna17752159487359796115.tmp12月 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: null12月 24, 2021 9:16:05 下午 com.sun.jna.NativeLibrary loadLibrary信息: Trying libc.dylib12月 24, 2021 9:16:05 下午 com.sun.jna.NativeLibrary loadLibrary信息: Found library 'c' at libc.dylibHello, World

仔细观察下面的输入后果,咱们能够大略理解JNA的工作流程。JNA的工作流程能够分为两局部,第一局部是Library Loading,第二局部是Native Library Loading。

两个局部别离对应的类是com.sun.jna.Native和com.sun.jna.NativeLibrary。

第一局部的Library Loading意思是将jnidispatch这个共享的lib文件加载到System中,加载的程序是这样的:

  1. jna.boot.library.path.
  2. 应用System.loadLibrary(java.lang.String)从零碎的library path中查找。如果不想从零碎libary path中查找,则能够设置jna.nosys=true。
  3. 如果从上述门路中没有找到,则会调用loadNativeDispatchLibrary将jna.jar中的jnidispatch解压到本地,而后进行加载。如果不想从classpath中查找,则能够设置jna.noclasspath=true。 如果不想从jna.jar文件中解压,则能够设置jna.nounpack=true。
  4. 如果你的零碎对于从jar文件中解压文件有平安方面的限度,比方SELinux,那么你须要手动将jnidispatch装置在一个能够拜访的地址,而后应用1或者2的形式来设置加载形式和门路。

当jnidispatch被加载之后,会设置零碎变量 jna.loaded=true,示意jna的lib曾经加载结束。

默认状况下咱们加载的lib文件名字叫jnidispatch,你也能够通过设置jna.boot.library.name来对他进行批改。

咱们看一下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();

首先是查找stub lib文件:/com/sun/jna/darwin-aarch64/libjnidispatch.jnilib, 默认状况下这个lib文件是在jna.jar包中的,所以须要调用extractFromResourcePath办法将jar包中的lib文件拷贝到临时文件中,而后调用System.load办法将其加载。

第二局部就是调用com.sun.jna.NativeLibrary中的loadLibrary办法来加载JAVA代码中要加载的lib。

在loadLibrary的时候有一些搜寻门路的规定如下:

  1. jna.library.path,用户自定义的jna lib的门路,优先从用户自定义的门路中开始查找。
  2. jna.platform.library.path, 和platform相干的lib门路。
  3. 如果是在OSX操作系统上,则会去搜寻 ~/Library/Frameworks, /Library/Frameworks, 和 /System/Library/Frameworks ,去查问对应的Frameworks。
  4. 最初会去查找Context class loader classpath(classpath或者resource path),具体的格局是${os-prefix}/LIBRARY_FILENAME。如果内容是在jar包中,则会将文件解压缩至 jna.tmpdir,而后进行加载。

所有的搜寻逻辑都放在NativeLibrary的办法loadLibrary中实现的,办法体太长了,这里就不一一列举了,感兴趣的敌人能够自行去摸索。

本地办法中的构造体参数

如果本地办法传入的参数是根本类型的话,在JNA中定义该native办法就用根本类型即可。

然而有时候,本地办法自身的参数是一个构造体类型,这种状况下咱们该如何进行解决呢?

以Windows中的kernel32 library为例,这个lib中有一个GetSystemTime办法,传入的是一个time构造体。

咱们通过继承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;}

而后定义一个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);}

最初这样调用:

Kernel32 lib = Kernel32.INSTANCE;SYSTEMTIME time = new SYSTEMTIME();lib.GetSystemTime(time);System.out.println("Today's integer value is " + time.wDay);

总结

以上就是JNA的根本应用,无关JNA依据深刻的应用,敬请期待后续的文章。

本文的代码:https://github.com/ddean2009/learn-java-base-9-to-20.git

本文已收录于 http://www.flydean.com/02-jna-overview/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」,懂技术,更懂你!