关于java:java高级用法之调用本地方法的利器JNA

5次阅读

共计 5488 个字符,预计需要花费 14 分钟才能阅读完成。

简介

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.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

仔细观察下面的输入后果,咱们能够大略理解 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/

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

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

正文完
 0