java中通过JNA调⽤dll
---恢复内容开始---
1. JNA简单介绍
先说JNI(Java Native Interface)吧,有过不同语⾔间通信经历的⼀般都知道,它允许Java代码和其他语⾔(尤其C/C++)写的代码进⾏交互,只要遵守调⽤约定即可。⾸先看下JNI调⽤C/C++的过程,注意写程序时⾃下⽽上,调⽤时⾃上⽽下。
可见步骤⾮常的多,很⿇烦,使⽤JNI调⽤.dll/.so共享库都能体会到这个痛苦的过程。如果已有⼀个编译好的.dll/.so⽂件,如果使⽤JNI技术调⽤,我们⾸先需要使⽤C语⾔另外写⼀个.dll/.so共享库,使⽤SUN规定的数据结构替代C语⾔的数据结构,调⽤已有的 dll/so中公布的函数。然后再在Java中载⼊这个库dll/so,最后编写Java native函数作为链接库中函数的代理。经过这些繁琐的步骤才能在Java中调⽤本地代码。因此,很少有Java程序员愿意编写调⽤dll/.so库中原⽣函数的java程序。这也使Java语⾔在客户端上乏善可陈,可以说JNI是 Java的⼀⼤弱点!
那么JNA是什么呢?
JNA(Java Native Access)是⼀个开源的Java框架,是Sun公司推出的⼀种调⽤本地⽅法的技术,是建⽴在经典的JNI基础之上的⼀个框架。之所以说它是JNI的替代者,是因为JNA⼤⼤简化了调⽤本地⽅法的过程,使⽤很⽅便,基本上不需要脱离Java环境就可以完成。
如果要和上图做个⽐较,那么JNA调⽤C/C++的过程⼤致如下:
可以看到步骤减少了很多,最重要的是我们不需要重写我们的动态链接库⽂件,⽽是有直接调⽤的API,⼤⼤简化了我们的⼯作量。
JNA只需要我们写Java代码⽽不⽤写JNI或本地代码。功能相对于Windows的Platform/Invoke和Python的ctypes。
2. JNA技术原理
JNA使⽤⼀个⼩型的JNI库插桩程序来动态调⽤本地代码。开发者使⽤Java接⼝描述⽬标本地库的功能和结构,这使得它很容易利⽤本机平台的功能,⽽不会产⽣多平台配置和⽣成JNI代码的⾼开销。这样的性能、准确性和易⽤性显然受到很⼤的重视。
此外,JNA包括⼀个已与许多本地函数映射的平台库,以及⼀组简化本地访问的公⽤接⼝。
注意:
JNA是建⽴在JNI技术基础之上的⼀个Java类库,它使您可以⽅便地使⽤java直接访问动态链接库中的函数。
原来使⽤JNI,你必须⼿⼯⽤C写⼀个动态链接库,在C语⾔中映射Java的数据类型。
JNA中,它提供了⼀个动态的C语⾔编写的转发器,可以⾃动实现Java和C的数据类型映射,你不再需要编写C动态链接库。
也许这也意味着,使⽤JNA技术⽐使⽤JNI技术调⽤动态链接库会有些微的性能损失。但总体影响不⼤,因为JNA也避免了JNI的⼀些平台配置的开销。
3. JNA简单使⽤
JNA的项⽬已迁移⾄,⽬前最新版本是4.1.0,已有打包好的jar⽂件可供下载。
JNA把⼀个.dll/.so⽂件看做是⼀个Java接⼝,下⾯以⼀个简单的实例来说明怎么使⽤。
当然要从最经典的HelloWorld开始,我们调⽤C的printf函数打印出“HelloWorld”(官⽅的例⼦)
新建java project
然后finish
新建⽂件HelloWorld.java
在项⽬下创建lib⽂件夹,将jna.jar放⼊其中
在项⽬Properties->java Build Path->Add External JARs 中添加jna.jar
然后OK
编辑HelloWorld.java,并运⾏,结果如下:
运⾏程序,如果没有带参数则只打印出“Hello, World Hello jna!”,如果带了参数,则会打印出所有的参数。
下⾯来解释下这个程序。
(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, 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下建⽴⼀个动态库程序:
⽤vs创建DLL⼯程:
⽂件->新建->项⽬->visual c++->win32->win32控制台应⽤程序(win32项⽬也可以)
填写项⽬名称MyDLL->确定->下⼀步->DLL(附加选项对空项⽬打钩)->完成。
到这⾥DLL⼯程就创建完毕了,下⾯新建两个⽂件MyDLL.cpp和MyDLL.h。
python转java代码MyDLL.cpp内容如下:
然后Bulid -->Bulid MyDLL,dll⽂件就在debug⽂件夹下⽣成了编译成⼀个dll⽂件(⽐如Mydll.dll),将Mydll.dll放到⼯程的bin⽬录下,然后编写JNA程序调⽤即可:
然后修改代码并运⾏如下:
得到输出。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论