JNA简介

JNA全称Java Native Access,是一个建立在经典的JNI技术之上的Java开源框架(https://github.com/twall/jna)。JNA提供一组Java工具类用于在运行期动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。

JNA包:

https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna/4.0.0/jna-4.0.0.jar

https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna-platform/4.0.0/jna-platform-4.0.0.jar

JNA在线帮助文档:http://twall.github.io/jna/4.0/javadoc/

JNA入门示例:https://github.com/twall/jna/blob/master/www/GettingStarted.md

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的数据类型对照

Java和C的数据类型对照表

Java 类型

C 类型

原生表现

 

boolean

int

32位整数(可定制)

 

byte

char

8位整数

 

char

wchar_t

平台依赖

 

short

short

16位整数

 

int

int

32位整数

 

long

long long, __int64

64位整数

 

float

float

32位浮点数

 

double

double

64位浮点数

 

Buffer/Pointer

pointer

平台依赖(32或64位指针)

 

<T>[] (基本类型的数组)

pointer/array

32或64位指针(参数/返回值)

邻接内存(结构体成员)

 

String

char*

/0结束的数组 (native encoding or jna.encoding)

 

WString

wchar_t*

/0结束的数组(unicode)

 

String[]

char**

/0结束的数组的数组

 

WString[]

wchar_t**

/0结束的宽字符数组的数组

 

Structure

struct*/struct

指向结构体的指针(参数或返回值) (或者明确指定是结构体指针)结构体(结构体的成员) (或者明确指定是结构体)

 

Union

union

等同于结构体

 

Structure[]

struct[]

结构体的数组,邻接内存

 

Callback

<T> (*fp)()

Java函数指针或原生函数指针

 

NativeMapped

varies

依赖于定义

 

NativeLong

long

平台依赖(32或64位整数)

 

PointerType

pointer

和Pointer相同

 

 

通用入门案例

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform; /** Simple example of JNA interface mapping and usage. */
public class HelloWorld { // This is the standard, stable way of mapping, which supports extensive
// customization and mapping of Java to native types. public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
Native.loadLibrary((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]);
}
}
}

运行程序,如果没有带参数则只打印出“Hello, World”,如果带了参数,则会打印出所有的参数。

很简单,不需要写一行C代码,就可以直接在Java中调用外部动态链接库中的函数!

 

下面来解释下这个程序。

(1)需要定义一个接口,继承自Library StdCallLibrary

默认的是继承Library ,如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary,比如众所周知的kernel32库。比如上例中的接口定义:

public interface CLibrary extends Library {

}

(2)接口内部定义

接口内部需要一个公共静态常量:INSTANCE,通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部dll/so的函数。

该常量通过Native.loadLibrary()这个API函数获得,该函数有2个参数:

  • 第一个参数是动态链接库dll/so的名称,但不带.dll或.so这样的后缀,这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。搜索动态链接库路径的顺序是:先从当前类的当前文件夹找,如果没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,如果找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。比如上例中printf函数在Windows平台下所在的dll库名称是msvcrt,而在其它平台如Linux下的so库名称是c。

  • 第二个参数是本接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。该实例由JNA通过反射自动生成。

CLibrary INSTANCE = (CLibrary)
Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);

接口中只需要定义你要用到的函数或者公共变量,不需要的可以不定义,如上例只定义printf函数:

void printf(String format, Object... args);

注意参数和返回值的类型,应该和链接库中的函数类型保持一致。

(3)调用链接库中的函数

定义好接口后,就可以使用接口中的函数即相应dll/so中的函数了,前面说过调用方法就是通过接口中的实例进行调用,非常简单,如上例中:

CLibrary.INSTANCE.printf("Hello, World\n");
for (int i=0;i < args.length;i++) {
CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
}

这就是JNA使用的简单例子,可能有人认为这个例子太简单了,因为使用的是系统自带的动态链接库,应该还给出一个自己实现的库函数例子。其实我觉得这个完全没有必要,这也是JNA的方便之处,不像JNI使用用户自定义库时还得定义一大堆配置信息,对于JNA来说,使用用户自定义库与使用系统自带的库是完全一样的方法,不需要额外配置什么信息。比如我在Windows下建立一个动态库程序:

#include "stdafx.h"

extern "C"_declspec(dllexport) int add(int a, int b);

int add(int a, int b) {
return a + b;
}

然后编译成一个dll文件(比如CDLL.dll),放到当前目录下,然后编写JNA程序调用即可:

public class DllTest {

    public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)Native.loadLibrary("CDLL", CLibrary.class); int add(int a, int b);
} public static void main(String[] args) {
int sum = CLibrary.INSTANCE.add(3, 6); System.out.println(sum);
}
}

简单案例

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform; interface HelloInter extends Library {
int toupper(int ch); double pow(double x, double y); void printf(String format, Object... args);
} public class HelloWorld { public static void main(String[] args) {
HelloInter INSTANCE = (HelloInter) Native.loadLibrary(Platform.isWindows() ? "msvcrt" : "c", HelloInter.class);
INSTANCE.printf("Hello, Worldn");
String[] strs = new String[] { "芙蓉", "如花", "凤姐" };
for (int i = 0; i < strs.length; i++) {
INSTANCE.printf("人物 %d: %sn", i, strs[i]);
}
System.out.println("pow(2d,3d)==" + INSTANCE.pow(2d, 3d));
System.out.println("toupper('a')==" + (char) INSTANCE.toupper((int) 'a'));
} }

显示结果:

pow(2d,3d)==8.0
toupper('a')==A
Hello, Worldn人物 0: 芙蓉n人物 1: 如花n人物 2: 凤姐n

说明:

HelloInter接口中定义的3个函数全是C语言函数库中的函数,其定义格式如下:

int toupper(int ch)
double pow( double x, double y )
int printf(const char* format, ...)

C语言函数库中有很多个函数,但是我们只用到了这3个函数,所以其他的函数不需要声明在接口中。

JNA模拟结构体

例:使用 JNA调用使用 Struct的 C函数

假设我们现在有这样一个C 语言结构体

struct UserStruct{
long id;
wchar_t* name;
int age;
};

使用上述结构体的函数

#define MYLIBAPI extern "C" __declspec( dllexport )

MYLIBAPI void sayUser(UserStruct* pUserStruct);

对应的Java 程序中,在例1 的接口中添加下列代码:

public static class UserStruct extends Structure{
public NativeLong id;
public WString name;
public int age;
public static class ByReference extends UserStruct implements Structure.ByReference {}
public static class ByValue extends UserStruct implements Structure.ByValue {}
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[] { "id", "name", "age"});
} }
public void sayUser(UserStruct.ByReference struct);

Java中的代码

   UserStruct userStruct=new UserStruct ();
userStruct.id=new NativeLong(100);
userStruct.age=30;
userStruct.name=new WString("奥巴马");
TestDll1.INSTANCE.sayUser(userStruct);

Structure说明

现在,我们就在Java 中实现了对C 语言的结构体的模拟。这里,我们继承了Structure 类,用这个类来模拟C 语言的结构体。必须注意,Structure 子类中的公共字段的顺序,必须与C 语言中的结构的顺序一致。否则会报错!因为,Java 调用动态链接库中的C 函数,实际上就是一段内存作为函数的参数传递给C函数。动态链接库以为这个参数就是C 语言传过来的参数。同时,C 语言的结构体是一个严格的规范,它定义了内存的次序。因此,JNA 中模拟的结构体的变量顺序绝对不能错。

如果一个Struct 有2 个int 变量。Int a, int b如果JNA 中的次序和C 语言中的次序相反,那么不会报错,但是数据将会被传递到错误的字段中去。

Structure 类代表了一个原生结构体。当Structure 对象作为一个函数的参数或者返回值传递时,它代表结构体指针。当它被用在另一个结构体内部作为一个字段时,它代表结构体本身。

另外,Structure 类有两个内部接口Structure.ByReferenceStructure.ByValue。这两个接口仅仅是标记,如果一个类实现Structure.ByReference 接口,就表示这个类代表结构体指针。

如果一个类实现Structure.ByValue 接口,就表示这个类代表结构体本身。使用这两个接口的实现类,可以明确定义我们的Structure 实例表示的是结构体的指针还是结构体本身。上面的例子中,由于Structure 实例作为函数的参数使用,因此是结构体指针。所以这里直接使用了UserStruct userStruct=new UserStruct ();也可以使用UserStruct userStruct=new UserStruct.ByReference ();明确指出userStruct 对象是结构体指针而不是结构体本身。

JNA模拟复杂结构体C 语言最主要的数据类型就是结构体。结构体可以内部可以嵌套结构体,这使它可以拟任何类型的对象。JNA 也可以模拟这类复杂的结构体,结构体内部可以包含结构体对象的指针的数组

struct CompanyStruct{
long id;
wchar_t* name;
UserStruct users[100];
int count;
};

JNA 中可以这样模拟:

public static class CompanyStruct extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
public int count;
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[] { "id", "name",,"users" "count"});
}
}

这里,必须给users 字段赋值,否则不会分配100 个UserStruct 结构体的内存,这样JNA中的内存大小和原生代码中结构体的内存大小不一致,调用就会失败。

测试代码:

CompanyStruct2.ByReference companyStruct2=new CompanyStruct2.ByReference();
companyStruct2.id=new NativeLong(2);
companyStruct2.name=new WString("Yahoo");
companyStruct2.count=10;
UserStruct.ByReference pUserStruct=new UserStruct.ByReference();
pUserStruct.id=new NativeLong(90);
pUserStruct.age=99;
pUserStruct.name=new WString("杨致远");
// pUserStruct.write();
for(int i=0;i<companyStruct2.count;i++){
companyStruct2.users[i]=pUserStruct;
} TestDll1.INSTANCE.sayCompany2(companyStruct2);

执行测试代码,报错了。这是怎么回事?

考察JNI 技术,我们发现Java 调用原生函数时,会把传递给原生函数的Java 数据固定在内存中,这样原生函数才可以访问这些Java 数据。对于没有固定住的Java 对象,GC 可以删除它,也可以移动它在内存中的位置,以使堆上的内存连续。如果原生函数访问没有被固定住的Java 对象,就会导致调用失败。固定住哪些java 对象,是JVM 根据原生函数调用自动判断的。而上面的CompanyStruct2结构体中的一个字段是UserStruct 对象指针的数组,因此,JVM 在执行时只是固定住了CompanyStruct2 对象的内存,而没有固定住users 字段引用的UserStruct 数组。因此,造成了错误。我们需要把users 字段引用的UserStruct 数组的所有成员也全部固定住,禁止GC 移动或者删除。如果我们执行了pUserStruct.write();这段代码,那么就可以成功执行上述代码。Structure 类的write()方法会把结构体的所有字段固定住,使原生函数可以访问。

总结

使用JNA的过程中也不一定会一帆风顺,比如会抛出”非法内存访问”,这时候检查一下变量是否==null。还有内存对齐的问题,当从内存中获取图片信息进行保存的时候,如果内存对齐处理不好,就会抛出很严重的异常,导致JVM异常退出,JNA提供了四种内存对齐的方式,分别是:ALIGN_DEFAULT、ALIGN_NONE、ALIGN_GNUC和ALIGN_MSVC。ALIGN_DEFAULT采用平台默认的对齐方式(推荐);ALIGN_NONE是不采用对齐方式;ALIGN_GNUC为针对linux/gcc操作系统的对齐方式。ALIGN_MSVC为针对win32/msvc架构的内存对齐方式。

JNA也提供了一种保护机制.比如防止JNA出现异常不会导致JVM异常退出,默认是开启这个功能的,开启方式为System.setProperty(“jna.protected”,”true”); 记得要在JNA加载dll文件之前调用,然后try {...} catch(Throwable e)异常,不过你也不要期望过高,不要以为加上这个就万事大吉,出现”非法内存访问”的时候还是会束手无策。JNA也提供了一种保护机制.比如防止JNA 出现异常不会导致JVM异常退出,默认是开启这个功能的,开启方式为 System.setProperty(“jna.protected”,”true”); 记得要在JNA加载dll文件之前调用,然后try {...} catch(Throwable e)异常,不过你也不要期望过高,不要以为加上这个就万事大吉,出现”非法内存访问”的时候还是会束手无策。

Java JNA (三)—— 结构体使用及简单示例的更多相关文章

  1. Java的 「 “ 结构体 ”」 与 「 “ 自定义排序 ” 」

    Java里面的结构体可以靠class来实现,如果相对结构体进行排序,需要写一个接口,class 自定义的名字 implements Comparator<结构体(自己定义的class类的名字)& ...

  2. java基础(一):我对java的三个环境变量的简单理解和配置

    首先说说java的三个环境变量:java_home,classpath,path java_home:jdk的安装路径[你一层一层点开安装路径,直到当前目录有一个bin目录,然后在地址栏里面右键单击复 ...

  3. (三)结构体指针、sizeof

    (一)结构体指针定义 今天上班写了一段测试代码,结果在linux下编译出现段错误,刚开始一直找不到原因,后来找了度娘才搞懂了.我先贴出来第一次写的代码以及gcc编译器下报的错误: #include&l ...

  4. 【Redis】三、Redis安装及简单示例

    (四)Redis安装及使用   Redis的安装比较简单,仍然和大多数的Apache开源软件一样,只需要下载,解压,配置环境变量即可.具体安装过程参考:菜鸟教程Redis安装.   安装完成后,通过r ...

  5. java中request,application,session三个域及参数简单示例

    直接上代码: java代码: public class HelloAction implements Action { @Override public String execute() throws ...

  6. [iOS]C语言技术视频-13-指针变量练习三(结构体指针变量)

    下载地址: 链接: http://pan.baidu.com/s/1bnx2xm3 密码: t4mj

  7. 【Lucene】三个高亮显示模块的简单示例-Highlighter

    Lucene针对高亮显示功能提供了两种实现方式,分别是Highlighter和FastVectorHighlighter 这里的三个示例都是使用Highlighter: 示例代码: package c ...

  8. java基础之JDBC五:批处理简单示例

    /** * 批处理 * 批处理跟事务不同 只是把一批sql放到一起执行 2条sql是可以一条执行成功 一条执行失败 是不可逆的 */ public class Test { public static ...

  9. java基础之JDBC四:事务简单示例

    /** * 事务 */ public class Test { public static void main(String[] args) { Connection conn = null; Pre ...

随机推荐

  1. 【LeetCode】二叉查找树 binary search tree(共14题)

    链接:https://leetcode.com/tag/binary-search-tree/ [220]Contains Duplicate III (2019年4月20日) (好题) Given ...

  2. ps:图像格式的选择

    从上面点阵与矢量两者的对比中,似乎矢量格式有优势,那为什么不都使用矢量格式呢? 这是因为矢量图像是基于线段的.因此它不适合记录色彩较为复杂的图像.如下图, 如果使用点阵方式来记录,只要按照顺序扫描并记 ...

  3. sass @import 规则

    @import 根据文件名引入. 默认情况下,它会寻找 Sass 文件并直接引入, 但是,在少数几种情况下,它会被编译成 CSS 的 @import 规则: 如果文件的扩展名是 .css. 如果文件名 ...

  4. Sumdiv(约数和问题)

    题目地址 看到这题的题解,大佬都说是小学奥数,蔡得我不敢鸡声. 求 \(a^b\) 所有的约数之和 mod \(9901\) \((1<=a,b<=5*10^7)\) 题解 做这道题,我还 ...

  5. 【串线篇】spring boot外部配置加载顺序

    SpringBoot也可以从以下位置加载配置: 原则仍然是优先级从高到低:高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置 1.命令行参数 所有的配置都可以在命令行上进行指定 java -j ...

  6. Java缓冲流写出数据实例

    public class BufferedWriterDemo throws IOException { public static void main(String[] args) throws I ...

  7. linux运维、架构之路-网络基础

    一. 常用网络设备 1.交换机:实现多台主机之间互相通讯的需求 交换机实现互相通讯的要求: ①主机身份标识信息:mac地址,利用源mac和目标mac地址,进行身份信息识别 ②主机通过交换机识别目标主机 ...

  8. Centos中文语言乱码解决方法

    vim /etc/locale.conf 添加:LANG="zh_CN.UTF-8" 执行一下source /etc/locale.conf,使刚修改的文件生效

  9. 状态管理Vuex的使用总结

    1.Vuex.store 的基本使用 Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. Vu ...

  10. 数字类别生成onehot

    对应行的列#原始标签 my_label = np.array([3,4,2,4,6,1]) #类别数量 num_class = 6 #样本数量 num = my_label.shape[0] #生成o ...