Java JNA的使用
Java JNA
简介
JNA 全称‘Java Native Access’ 是一个建立在传统JNI技术上的Java开源框架。JNA提供了一组Java工具类用于运行期动态访问系统本地库(Native Library)而不需要编写任何Native、JNI代码。开发者只需要在Java接口中描述目标Native Library的函数与结构,JNA会自动实现Java接口到Native Function的映射。
1,dll和so是C函数的集合和容器,这与Java中的接口概念吻合,所以JNA把dll文件和so文件看成一个个接口。在JNA中定义一个接口就是相当于了定义一个DLL/SO文件的描述文件,该接口代表了动态链接库中发布的所有函数。而且,对于程序不需要的函数,可以不在接口中声明。
2,JNA定义的接口一般继承com.sun.jna.Library接口,如果dll文件中的函数是以stdcall方式输出函数,那么,该接口就应该继承com.sun.jna.win32.StdCallLibrary接口。
3,Jna难点:编程语言之间的数据类型不一致。
类型对照
Java类型 | C类型 | C类型原生表现 |
---|---|---|
boolean | int | 32位整数(可定制) |
byte | char | 8位整数 |
char | wchar_t | 平台依赖 |
short | short | 16位整数 |
int | int | 32位整数 |
long | long,__int64 | 64位整数 |
float | float | 32位浮点数 |
double | double | 64位浮点数 |
Buffer/Pointer | pointer | 平台依赖(32或64位指针) |
pointer/array | 32或64位指针(参数/返回值)邻接内存(结构体成员) | |
String | char* | /0结束的数组(nativeencodingorjna.encoding) |
WString | wchar_t* | /0结束的数组(unicode) |
String[] | char** | /0结束的数组的数组 |
WString[] | wchar_t** | /0结束的宽字符数组的数组 |
Structure | struct*/struct | 指向结构体的指针(参数或返回值)(或者明确指定是结构体指针)结构体(结构体的成员)(或者明确指定是结构体) |
Union | union | 等同于结构体 |
Structure[] | struct[] | 结构体的数组,邻接内存 |
Callback | (*fp)() | Java函数指针或原生函数指针 |
NativeMapped | varies | 依赖于定义 |
NativeLong | long | 平台依赖(32或64位整数) |
PointerType | pointer | 和Pointer相同 |
案例
下面的示例映射了标准C库中的printf函数并调用。
1 | package com.sun.jna.examples; |
运行程序,如果不带参数就会单独打印“Hello,World”,如果带参则会打印出所有参数。由此可以看出,不需要写一行C代码,就可以直接在Java中调用外部动态链接库里的函数。
注意:参照上面的类型对照表,可以发现,传参不支持Class。
关于C输出语句占位符
格式 | 含义 |
---|---|
%d, %i | 代表整数 |
%f | 浮点 |
%s | 字符串 |
%c | char |
%p | 指针 |
%fL | 长log |
%e | 科学计数 |
%g | 小数或科学计数。 |
%a,%A | 读入一个浮点值(仅C99有效)。 |
%c | 读入一个字符。 |
%d | 读入十进制整数。 |
%i | 读入十进制,八进制,十六进制整数。 |
%o | 读入八进制整数。 |
%x, | %X 读入十六进制整数。 |
%s | 读入一个字符串,遇空格、制表符或换行符结束。 |
%f,%F,%e,%E,%g,%G | 用来输入实数,可以用小数形式或指数形式输入。 |
%p | 读入一个指针。 |
%u | 读入一个无符号十进制整数。 |
%n | 至此已读入值的等价字符数。 |
%[] | 扫描字符集合。 |
%% | 读 % 符号 |
程序解读
1、首先定义一个接口用于Native Function映射,继承自Library或StdCallLibrary
默认继承Library,如果dll中的函数是以stdcall方式调用,那么就继承StdCallLibrary,WIN32 Api都采用stdcall调用方式 就例如Windows中最为重要的kernel32库
2、接口内部定义
接口内部需要一个公共的静态常量:INSTANCE,通过这个常量我们可以获得这个接口的实例,从而使用接口的方法,也就是最关键的,调用外部dll/so的函数。该常量通过Native.load()静态函数获得,该函数的弃用方法为 Native.loadLibrary() 该函数有两个参数:
第一个参数是动态链接库dll/so的名称,但不带.dll或.so这样的后缀,这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。搜索动态链接库路径的顺序是:先从当前类的当前文件夹找,如果没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,如果找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。比如上例中printf函数在Windows平台下所在的dll库名称是msvcrt,而在其它平台如Linux下的so库名称是c。
第二个参数是本接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。该实例由JNA通过反射自动生成。
1 | CLibrary getInstance = (CLibrary) Native.load(Platform.isWindows() ? "msvcrt" : "c", CLibrary.class); |
接口中只需要定义你要用到的函数或公共变量,不需要的可以不定义,如上面的例子之定义了printf函数
1 | void printf(String from, Object... args); |
定义的时候要注意参数和返回值的类型,需要同链接库中的函数类型保持一致
3、调用dll函数
定义好接口之后,我们就可以使用接口方法调用dll/so中相对应的函数了。
如果想调用自己实现的库函数,可以将自己实现的库函数编译成dll/so,放到当前目录下,然后和上述例子一样的去编写调用即可。
关于Windows下dll的函数导出列表可以自行查询。这里有一个网站记载了了Windows的所有dll
JNA构造结构体
在C里面没有Class类型,但是我们需要传递对象时可以通过转化为Struct