Java开发过程中,有时候会需要和C,C++等交互,这时候我们就想起了经典技术JNI,但是JNI的使用过程十分繁琐,而且容易出现各种问题,还得封装而且问题不好定位。假如我们有一个.so文件,如果使用JNI去调用,我们需要另外用C语音写一个.so的共享文件,并且得使用SUN规定的数据结构去替代C语言的数据结构,至此才能调用so文件里面公布的函数。作为JAVA的程序员这个过程是令人头疼的。

相比之下,使用JNA就简单多了,只需要依赖一个jar包,就像调用一个java方法一样简单。JNA全称Java Native Access,是一个建立在JNI技术之上的Java开源框架。JNA提供一组Java工具类用于在运行期动态访问系统本地库(native library:如Window的dll,Linux的so)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。

dll和so是C函数的集合和容器,这与Java中的接口概念吻合,所以JNA把dll文件和so文件看成一个个接口。在JNA中定义一个接口就是相当于了定义一个DLL/SO文件的描述文件,该接口代表了动态链接库中发布的所有函数。而且,对于程序不需要的函数,可以不在接口中声明。JNA provides Java programs easy access to native shared libraries without writing anything but Java code - no JNI or native code is required.

JNA定义的接口一般继承com.sun.jna.Library接口,如果dll文件中的函数是以stdcall方式输出函数,那么,该接口就应该继承com.sun.jna.win32.StdCallLibrary接口。Jna难点:编程语言之间的数据类型不一致。

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

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

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

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

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

该方法对应C语言的库函数

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

Java和C的数据类型对照表

(3)调用链接库中的函数定义好接口后,就可以使用接口中的函数即相应dll/so中的函数了,调用方法就是通过接口中的实例进行调用,如上例中:

这就是JNA使用系统自带的动态链接库的简单例子,不像JNI使用用户自定义库时还得定义一大堆配置信息,对于JNA来说,使用用户自定义库与使用系统自带的库是完全一样的方法,不需要额外配置什么信息。比如在Windows下建立一个动态库程序:

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

JNA模拟结构体

例:使用JNA调用使用了Struct的C函数假设我们现在有这样一个C语言结构体

使用上述结构体的函数

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

Java中的代码

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.ByReference 和Structure.ByValue。这两个接口仅仅是标记,如果一个类实现Structure.ByReference 接口,就表示这个类代表结构体指针。

如果一个类实现Structure.ByValue 接口,就表示这个类代表结构体本身。使用这两个接口的实现类,可以明确定义我们的Structure 实例表示的是结构体的指针还是结构体本身。

上面的例子中,由于Structure 实例作为函数的参数使用,因此是结构体指针。所以这里直接使用了UserStruct userStruct=new UserStruct();

也可以使用UserStruct userStruct=new UserStruct.ByReference();明确指出userStruct 对象是结构体指针而不是结构体本身。

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

JNA中可以这样模拟:

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

例:结构体内部可以包含结构体对象的指针的数组

JNA 中可以这样模拟:

测试代码:

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

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

案例一:获取本地时间(Get local time)

如果你在Java Native Access 首页 看过“JNA如何入门”,你就会知道一个很简单的关于调用Windows 平台下的API函数:GetSystemTime() 的JNA示例。这个不完整的例子只是展示了JNA的基本特点。(在例子的基础上,我做了一个更完整的基于Windows的例子来介绍JNA)我在Windows平台下完善了这个例子来介绍JNA。

第一例子基于Windows GetLocalTime() API函数返回本地当前的时间和日期。和GetSystemTime()不同的是,返回的时间/日期是协调通用时间(UTC)格式的,GetLocalTime()返回的时间/日期信息的格式是根据当前时区来表示。

在一个Java程序中使用JNA调用GetLocalTime,你需要知道这个函数所在的Windows平台下的动态链接库(DLL)的名称(和可能所在的地理区域)。我们发现GetLocalTime()和GetSystemTime在同一个DLL文件中:kernel32.dll。你还需要知道GetLocalTime()在C语言环境中的申明。申明如下Listing 1:

Listing 1. GetLocalTime在C语言中的申明

这个基于C语言的申明表明传到这个函数的参数数目和类型。在这个例子中,只有一个参数—一个指向Windows SYSTEMTIME结构体的指针。而且,每个结构体成员的类型是16bit长度的无符号整型。根据这些信息,你能够创建一个完全描述GetLocalTime()函数的接口,如Listing 2中所示:

Listing 2. Kernel32.java

Kernel32 接口(The Kernel32 interface)

因为JNA使用通过一个接口来访问某个库中的函数,Listing 2表示了一个描述GetLocalTime()的接口。根据约定,我把接口命名为Kernel32是因为GetLocalTime()在Windows的kernel32.dll库。

这个接口必须继承com.sun..jna.Library接口。因为Windows API函数遵循stdcall调用协议(stdcall calling convention),为Windows API申明的接口也必须继承com.sun.jna.win32. StdCallLibrary接口。因此这个接口共继承了Library 和 com.sun.jna.win32.StdCall两个接口。

在前面,你已经知道了GetLocalTime() 需要一个指向SYSTEMTIME结构体的指针作为它唯一的参数。因为Java不支持指针,JNA是通过申明一个com.sun.jna.Structure的子类来代替的。根据java文档中抽象类的概念,在参数环境中,Structure相当于C语言的struct*。

在SYSTEMTIME类中的字段和C结构体中的相对应的属性字段的顺序是一一对应的。保证字段顺序的一致性是非常重要的。例如,我发现交换wYear和wMonth会导致wYear和wMonth值互换。

每个字段在java中是short integer类型的。按照JNA首页上 “默认类型映射”章节给出的提示,这个short integer分配类型是正确。然而,我们应该知道一个重要的区别:Windows平台下的WORD类型等同于C语言环境中的16-bit的无符号的short integer,而java中short integer是16-bit有符号的short integer。

一个类型映射的问题

通过比较一个API 函数返回的整型值,你会发现Windows/C 语言的无符号整型和Java语言的有符号整型的JNA类型映射是有问题的。在比较的过程中,如果你不细心,那么错误的执行过程可能导致决定性情况。导致这种后果是因为忘记任何数值的符号位的确定是根据:在无符号整型的情况下会被解释为正号,而在有符号整型的进制中被理解为负号的。

通过Kernel32获取本地时间(Access the local time with Kernel32)

JNA首页上的GetSystemTime()示例已经表明必须使用预先申明的接口为本地库分配一个实例对象。你可以通过com.sun.jna.Native类中静态公用方法loadLibrary(String name, Class interfaceClass)来完成上述的目标。Listing 3 所示:

Listing 3. LocalTime.java

Listing 3 执行Kernel32 lib = (Kernel32) Native.loadLibrary (“kernel32”, Kernel32.class);来分配一个Kernel32实例对象并且装载kernel32.dll。因为kernel32.dll是Windows平台下标准的dll文件,所以不要指定访问这个库的路径。然而,如果找不到这个dll文件,loadLibrary()会抛出一个UnsatisfiedLinkError异常。

Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();创建了一个SYSTEMTIME结构体的示例。初始化后下面是lib.GetLocalTime (time);,这句话使用本地的时间/日期来给这个实例赋值。几个System.out.println()语句是输出这些值。

编译和运行这个应用(Compile and run the application)

这部分很容易。假设jna.jar、Kernel32.java和LocalTime.java是放在当前文件夹中,调用java –cp jna.jar;. LocalTime.java来编译这个应用的源代码。如果在Windows平台下,调用invoke java –cp jna.jar;. LocalTime 来运行这个应用。你可以得到类似与Listing 4的输出结果:

Listing 4. 从LocalTime.java生成的输出

Year is 2007Month is 12Day of Week is 3Day is 19Hour is 12Minute is 35Second is 13Milliseconds are 156

案例二:调用本地的使用JNA的调用本地方法的时候需要自定义数据结构,下面我们通过调用Windows提供的的锁定工作站方法来了解一下JNA。

1、首先查询Windows API知道锁定工作站的方法在user32.dll中定义,接下来定义一个接口来继承JNA的Library.java接口,用作声明DLL库文件,这里我们就把它命名为User32:

2、查询user32.dll提供的API得知锁定工作方法是LockWorkStation,返回类型是boolean型,在User32.java中新增相应的方法:

这样我们的User32.java这个类就定义好了。接下来我们写测试程序进行调用。

3、编写测试类比如LockWorkStation.java,首先通过JNA的Native类加载对应的dll:

然后就可以调用LockWorkStation方法了,完整代码如下:

复制代码这里说明一下loadLibrary方法中第一个参数是需要加载的dll文件名称,第二个参数的作用是让JNA使用这个类的加载器去加载DLL文件,加载顺序是,先从Users.class类的当前文件夹找,如果没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,如果找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。以TWAINDSM.dll将文件放到工程的根文件夹可以按照下面这个格式放:

附件: jnaexplorer.JPG

上面的User32定义的是dll库文件,有时会碰到比如HANDLE、POINT、WORD和MSG等数据类型,有些数据类型JNA中没有提供,需要自己定义,根据作用的不同,定义的时候继承的父类也不一样,比如HANDLE定义方法是:

HANDLE被定义为类型安全的指针。而POINT用作表示坐标,不需要这么复杂,定义方式为:

使用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)异常,不过你也不要期望过高,不要以为加上这个就万事大吉,出现”非法内存访问”的时候还是会束手无策。

java jna 数据结构_开源框架JNA的使用相关推荐

  1. Java面试宝典之开源框架!

    Java人才需求怎么样?Java开源框架面试有哪些?Java开发已然成为很多程序员都追求的编程语言,目前Java开发人才的需求非常大,待遇也是相当不错.无论是因为兴趣还是因为就业,学习Java编程都是 ...

  2. 开源框架JNA的使用

    Java开发过程中,有时候会需要和C,C++等交互,这时候我们就想起了经典技术JNI,但是JNI的使用过程十分繁琐,而且容易出现各种问题,还得封装而且问题不好定位.假如我们有一个.so文件,如果使用J ...

  3. Java 最著名的开源框架(第一部分)

    Spring Framework [J2EE框架] *Spring 是一个解决了许多在J2EE开发中常见的强大框架. *Spring 提供了管理业务对象的一致方法并且鼓励了注入对接口编程而不是对类编程 ...

  4. java hashtable 数据结构_数据结构--哈希表(Java)

    数据结构--哈希表(Java) 介绍 哈希表 底层是 数组加链表 或者是 数组加二叉树 ,一个数组里面有多个链表,通过散列函数来提高效率 代码 package cn.guizimo.hashtab; ...

  5. java简单数据结构_图解Java常用数据结构

    最近在整理数据结构方面的知识, 系统化看了下 Java 中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于 jdk8, 可能会有些特性与 jdk7 之前不相同, 例如 LinkedList ...

  6. java部署平台_开源Java自动化部署平台JDeploy

    JDeploy是Java + Shell实现的基于Linux系统的自动化.可视化的项目部署平台,能部署Java服务.Java Web项目,可以简化项目部署操作,无需繁琐的黑窗口SSH指令及Jenkin ...

  7. java string 数据结构_数据结构---Java---String、StringBuilder、StringBuffer

    1.概述 1.1 String:不可变字符串 public final class String implements java.io.Serializable, Comparable, CharSe ...

  8. java 堡垒机_开源堡垒机系统Teleport部署教程

    认识Teleport 在开源堡垒机领域, 很多人都知道jumpserver, 但是jumpserver安装相对较复杂, 新手容易出现各种坑. 在这里介绍一款简单易用的开源堡垒机系统: Teleport ...

  9. Java Web学习总结(28)——Java Web项目MVC开源框架SSH和SSM比较

    SSH和SSM定义 SSH 通常指的是 Struts2 做控制器(controller),spring 管理各层的组件,hibernate 负责持久化层. SSM 则指的是 SpringMVC 做控制 ...

最新文章

  1. 推荐6个HTML5编辑器
  2. 【大学课程】高数基础知识点
  3. Visual Studio Code里关于ESLint的错误消息
  4. Java交流|面试最后一问:你有什么问题想问我吗?
  5. python 数据处理----读取txt 一列数据写入excel 文件
  6. 为什么用scrum_为什么Scrum糟糕于数据科学
  7. RGB与YUV格式简介
  8. java.util.IdentityHashMap.entrySet()方法实例
  9. 有没有什么方法快速能找到导致软件崩溃的进程_手机软件闪退闪得怀疑人生?看我专治闪退二十年!...
  10. LVS--NAT模型
  11. dell linux raid 查看,Ubuntu Linux下Dell服务器使用硬Raid后查看磁盘信息方法
  12. 做网站有虚拟服务器,虚拟主机只能做网站吗
  13. 《英语语法新思维初级教程》学习笔记(五)形容词
  14. 什么东西可以提高睡眠质量、这五款助眠好物助你摆脱困扰
  15. 2月面经:真可惜...拿了小米的offer,字节却惨挂在三面
  16. pthon缺陷检测(机器视觉)
  17. 【UNR #1】火车管理
  18. TensorFlow 高性能数据输入管道设计指南
  19. 【UV打印机】电器之开关电源LRS-600
  20. 一部值得成年人反复观看的剧

热门文章

  1. simulink中固定大小矩阵和可变大小矩阵创建的几个陷阱
  2. 广播节目常用的背景音乐列表,可以试听
  3. ubuntu装机卡住转圈
  4. 2023年专利申请流程及费用,哪里可以申请
  5. 尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通 学习笔记
  6. 腾讯又一重磅服务停服!网友:陪了我5年了
  7. 洛谷P1494 [国家集训队]小Z的袜子 莫队
  8. (3)UVM验证平台搭建之介绍
  9. web服务器设置文档,web服务器设置
  10. 怎么选择聚合支付公司