java高级用法之:调用本地方法的利器JNA
liuian 2025-07-09 14:16 49 浏览
简介
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*的库文件。
可以看到不同的平台对应着不同的动态库。
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中,加载的顺序是这样的:
- jna.boot.library.path.
- 使用System.loadLibrary(java.lang.String)从系统的library path中查找。如果不想从系统libary path中查找,则可以设置jna.nosys=true。
- 如果从上述路径中没有找到,则会调用loadNativeDispatchLibrary将jna.jar中的jnidispatch解压到本地,然后进行加载。如果不想从classpath中查找,则可以设置jna.noclasspath=true。 如果不想从jna.jar文件中解压,则可以设置jna.nounpack=true。
- 如果你的系统对于从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的时候有一些搜索路径的规则如下:
- jna.library.path,用户自定义的jna lib的路径,优先从用户自定义的路径中开始查找。
- jna.platform.library.path, 和platform相关的lib路径。
- 如果是在OSX操作系统上,则会去搜索 ~/Library/Frameworks, /Library/Frameworks, 和 /System/Library/Frameworks ,去查询对应的Frameworks。
- 最后会去查找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/最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
相关推荐
- 壁纸图片2025最新款(电脑桌面壁纸图片2025最新款)
-
要更换2023最新款壁纸图片,可以按照以下步骤操作:首先,找到您想要更换的壁纸图片并下载到您的设备上。其次,进入您的设备设置,找到“壁纸”或“桌面壁纸”选项,并点击进入。然后,选择“更换壁纸”并在相册...
- 清理垃圾的神器(清理垃圾的神器是什么)
-
1、《腾讯手机管家》这款可以帮助用户进行强力的清理,加速告别空间卡顿,缓慢延迟的问题的软件当中,用户可以随时随地登录软件进行自动清理和自动清理,自动清理包括图片,视频,语音文件在内的各种换成文件,为手...
- 苹果笔记本怎样重装系统(苹果笔记本怎样重装系统还原)
-
苹果笔记本电脑系统可以通过以下步骤进行重装:1.备份数据:在开始重装前,需要备份你的重要数据。你可以将数据存储到外部硬盘、云存储或其他可靠的设备中。2.下载安装器:从AppStore中下载macOS...
- 手机wifi打不开怎么办
-
手机wifi打不开的原因,可能集中在该手机出现了手机文件丢失、手机版本不稳定、手机文件出错以及手机wifi模块摔坏等故障造成的。手机wifi打不开修复教程1.wcnss_qcom_cfg文件丢失导...
- bios恢复出厂设置后无法开机
-
可通过进入BIOS界面设置bios恢复出厂设置的方法解决,步骤如下:1、通过按Delete或数字键盘中的Del键进入BIOS。2、按箭头键输入并将光标移动到“加载设置默认值”项,然后按enter确认。...
- 电脑硬盘打不开怎么办(电脑硬盘打不开怎么办)
-
电脑硬盘坏了是不能开机的。硬盘坏道的修复方法:1、逻辑坏道的修复对于逻辑坏道,Windows自带的“磁盘扫描程序(Scandisk)”就是最简便常用的解决手段。如果硬盘出现了坏道,我们可在Window...
- linux系统备份与还原工具(linux系统备份与还原工具在哪)
-
用GHOST对LINUX系统做备份1:要求将安装了LINUX系统的硬盘(原盘)整盘刻至另一硬盘(目标盘)。2:所需工具:DOS系统引导盘,GHOST2003(版本低的对文件格式不能很好的支持),原盘(...
- pdf怎么转换成xml格式(如何将pdf格式转换成xml格式)
-
将PDF转换为XML需要使用专业的PDF转换工具。以下是一些常用的PDF转XML工具:1.AdobeAcrobatDC:AdobeAcrobatDC是一款功能强大的PDF编辑软件,其中包括P...
- windows7iso文件(iso文件 win7)
-
利用winrar可以直接打开iso文件,如果双击不能直接打开需要设置winrar,步骤如下:1、启动winrar,点击选项菜单设置命令;2、点击综合选项卡,点击全部选择,点击确定即可。具体操作方法步骤...
- 路由器ip地址是什么意思(路由器的ip地址是)
-
路由器IP地址是指连接到互联网的路由器在局域网内的唯一标识符,一般为192.168.1.1或192.168.0.1等地址。通过路由器IP地址,用户可以通过浏览器等工具登录到路由器管理界面,进行网络设置...
-
- mediaplayer播放记录在哪里(mediaplayer历史记录)
-
《WindowsMediaPlayer》无法播放该文件,表示《WindowsMediaPlayer》目前的版本不支持该视频的格式编码。解决方法: 1.如果安装的是正版操作系统,点帮助→检查更新,稍待片刻,WindowsMed...
-
2026-01-14 02:37 liuian
- 电脑xp怎么换系统win7(电脑xp系统换win7教程)
-
第一种方法:自助安装win7系统 我们在进行自助安装win7系统之前我们要保证我们的电脑是联网的。为了能更加顺利的完成对xp系统的升级,我们的电脑最好是能高速上网的,只有能联网我们才可以下载最新的系...
- appstore官方网站(appstore.apple.com)
-
Appstore即applicationstore,通常理解为应用商店。Appstore是苹果公司基于iPhone的软件应用商店,向iPhone的用户提供第三方的应用软件服务,这是苹果开创的一...
- 电脑开不了机怎么办显示英文字母
-
win7操作系统电脑在开机的时候屏幕界面出现CLIENTMACADDR,然后就一直停在了这个界面,要等很长时间才能进入系统登入界面。出现这样问题的原因是什么?这是因为网卡启用了BOOTROM芯片...
- win7此windows副本不是正版(win7 此windows副本不是正版)
-
win7系统提示副本不是正版解决方法:1.打开设备,调出运行窗口,输入命令“cmd”,并按下回车键;2.这时命令提示符窗口便会自动弹出;3.输入命令“SLMGR-REARM”,再按下回车键;4.命令...
- 一周热门
-
-
飞牛OS入门安装遇到问题,如何解决?
-
如何在 iPhone 和 Android 上恢复已删除的抖音消息
-
Boost高性能并发无锁队列指南:boost::lockfree::queue
-
大模型手册: 保姆级用CherryStudio知识库
-
用什么工具在Win中查看8G大的log文件?
-
如何在 Windows 10 或 11 上通过命令行安装 Node.js 和 NPM
-
威联通NAS安装阿里云盘WebDAV服务并添加到Infuse
-
Trae IDE 如何与 GitHub 无缝对接?
-
idea插件之maven search(工欲善其事,必先利其器)
-
如何修改图片拍摄日期?快速修改图片拍摄日期的6种方法
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)
