搞Java的同学应该都清楚,Java与C++交互方式是通过DLL,使用JNI技术,也就是现在Android上最常用的方式,但其实现在有更高层次的封装了,那就是JNA方式。虽然JNA使用很简单,但是发布却比较麻烦,特别是参考网上的文章基本很少有正确的方法,本文将详述正确的方法。
JNA的使用
JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。JNA框架解决了JNI需要额外编写动态库代码与Java交互的工作,可以直接使用其他语言提供的动态库,开发人员只要在一个Java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射,大大降低了Java调用本体共享库的开发难度。
使用JNA在引入JNA的JAR之后,只需要定义接口,类似如下
public interface Clibrary extends Library { |
上面是对接口的定义,其中加载的那行的库名称为 foo
,在Linux平台上为 libfoo.so
在windows平台为 foo.dll
,熟悉两个平台的开发的都应该比较清楚 。然后后面是具体的动态库导出函数的接口定义,注意一些数据类型的转换,比如C中的 const char[]
应该使用Java的 byte[]
类型,不能使用Java的 char[]
类型。
调用方的代码也很简单,类似
public void test() { |
JNA的封装
一般我们希望在动态库的基础上封装Java接口,毕竟直接调用动态库接口不是太方便,包括一些错误处理什么的等等,都可以转换成Java的方式。然后最终将封装库额外加动态库一起封装成一个JAR对外提供。
这里面涉及到两方面的问题,第一是打包动态库到JAR中,这个倒不是很难,网络上有大量的文章可参考,此处就不再赘述了。
另外就是如何才能让JNA加载到动态库,我们知道JNA加载的方式有两种,第一种就是类似上面的直接给传递一个库名称,JNA自动在 classpath
查找,如果将动态库拷贝到运行环境的 classpath
上就能加载。另一种就是加载绝对路径的动态库。
但是由于打包之后动态库实际上存在JAR包之中,并不存在于本地的文件系统上,因此只需要想办法将动态库释放出来,然后使用第二种方式加载即可。释放其实可以通过 getResourceAsStream
读取到文件,而 Clibrary.class.getResourceAsStream
就是从当前JAR读取文件,路径 /
表示JAR包根目录,参照真实路径进行修改,具体可以通过压缩软件把JAR打开查看目录结构。一般可以将文件释放到 temp 路径去,然后返回释放后的文件的绝对路径给JNA加载,我采用了类似下面的代码实现功能
private synchronized static String extractLibrary(String libName) { |
有上面这样的代码逻辑后,JNA加载动态库的代码相应变为
Clibrary INSTANTCE = (Clibrary)Native.load(extractLibrary("foo"), Clibrary.class); |
其他一些问题
上面实现了基本逻辑后,基本满足了对外提供功能的需要。但是Java是跨平台的语言,上面的做法只能满足一个平台,这其中的主要原因是我们提供的动态库,如果我们提供了Win32平台的dll,这个JAR就能在Win32系统上的Java上运行,类似的Java运行时对32位和64位还有要求,因此如果要满足大部分的需求,比较好的办法是将支持的平台的所有动态库打包到一起,在上面的释放代码处进行判断具体的平台来释放需要的文件,而且文件最终要路径唯一,就可以适应各种平台。