记录一下自己的JNA调研成果,需求是公司同事用C++写了一个红外测温SDK,编译成so文件后提供给客户使用。客户需要一个Linux环境用Java调用so库的一个demo,刚好就我一个懂点Java,所有有了这次调研。
因为JNA相关资料实在太少,而且我一没用过Linux,二没搞过虚拟机,所以在研发过程中踩了太多坑,每向前迈一步都要克服很多困难,所以想记录下来,也许能给其他需要的人借鉴一下,少走一点弯路。Linux虚拟机Java开发环境我就不介绍了,这方面资料还是挺多的,主要说一下JNA的使用。
JNA介绍和技术原理
JNA全称Java Native Access,是一个建立在经典的JNI技术之上的Java开源框架。
JNA提供工具用于调用c/c++动态函数库(如Window的dll以及linux的so)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标函数库的函数与结构,JNA将自动实现Java接口方法到函数的映射。
JNA使用一个小型的JNI库插桩程序来动态调用本地代码。开发者使用Java接口描述目标本地库的功能和结构,这使得它很容易利用本机平台的功能,而不会产生多平台配置和生成JNI代码的高开销。这样的性能、准确性和易用性显然受到很大的重视。
此外,JNA包括一个已与许多本地函数映射的平台库,以及一组简化本地访问的公用接口。
注意:
JNA是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数。
原来使用JNI,你必须手工用C写一个动态链接库,在C语言中映射Java的数据类型。
JNA中,它提供了一个动态的C语言编写的转发器,可以自动实现Java和C的数据类型映射,你不再需要编写C动态链接库。
也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。但总体影响不大,因为JNA也避免了JNI的一些平台配置的开销。
JNA数据类型映射表&模拟指针
在JNA中模拟指针,最常用到的就是Pointer类和PointerByReference类。Pointer类代表指向任何东西的指针,PointerByReference类表示指向指针的指针。Pointer类更加通用,事实上PointerByReference类内部也持有Pointer类的实例。
使用示例
环境说明:
jna-version:4.0.0(我用的是4.0.0版本)
jdk-version:1.8
java开发使用idea
JNA下载地址:https://github.com/java-native-access/jna.git
1、JNA简单调用
实现步骤
在Java类中创建一个接口CLibrary继承Library,
在CLibrary中加载so文件,
创建一个本地方法(对应so中的提供的native方法),
在main函数中用so实例(INSTANCE)调用本地创建的方法。
注意:下面是So库提供的可调用的方法.h头文件代码,JNA就是实现Java对这些函数的调用,后面的例子都是实现对这些方法的调用。
Java代码
(Linux中加载libSCT_SDK_CHANNEL_X64.so,要去掉前面的lib和.so后缀)这里so文件SCT_SDK_CHANNEL_X64依赖SCT_SDK_MT_X64,所以要先加载SCT_SDK_MT_X64
CLibrary中创建了SCT_ChannelInit()方法,根据头文件,返回int类型,后面就可以用INSTANCE1去调用本地方法映射的so库函数了。Main方法中调用CLibrary创建的SCT_ChannelInit()方法,打印输出结果。
运行,返回1调用成功
2、JNA返回char指针
实现步骤
同上
从.h头文件我们可以看到,要调用的C++函数,返回值是char*(字符类型指针),参考映射表,我们可以用字符串接收返回数据。
Java代码
在CLibrary中创建SCT_ChannelVersion();
Main函数中调用本地方法
运行,返回结果
3、JNA返回void指针
实现步骤
同上
从.h头文件看到返回的是HChannel类型,它是C++自定义的void指针
参考映射表,我们可以用Pointer(通用,所有指针都可以用Pointer接收)
Java代码
首先定义一个Pointer并申请内存,然后在CLibrary中创建SCT_ChannelCreate();
Main中接收指针,pChannel会在后面的例子中用到
4、JNA基本传参
实现步骤
同上
从.h头文件中可以看到需要参数hChannel,返回int类型
Java代码
同样在CLibrary中创建本地函数SCT_ChannelClose(Pointer hChannel);
Main函数中传入hChannel参数调用
运行,打印输出,返回1,调用成功
5、JNA模拟结构体传参
实现步骤
同上
从.h头文件中看到需要参数hChannel和openParams,OpenParams是一个结构体指针,我们要在Java中模拟结构体openParams,返回结果int类型
先查看一下C++中结构体OpenParams
Java代码
模拟OpenParams结构体,创建OpenParams继承Structure,设置成员变量,和C++结构体保持一致,参考映射表,char对应byte,int对应int,CapabilitySet也是结构体,属于结构体嵌套,CapabilitySet结构体模拟参考OpenParams,(JPEG_SIZE是枚举类型)C++中枚举类型和Java中不同,Java直接用int接收就可以。
创建两个内部类ByReference,ByValue继承OpenParams,分别实现接口Structure.ByReference、Structure.ByValue
覆盖getFieldOrder()方法,按顺序添加成员变量,一定要和C++结构体保持一致。
结构体传参分为值传递、引用传递(指针传递)
值传递使用ByValue
引用传递使用ByReference
在CLibrary中创建SCT_ChannelOpen(Pointer hChannel, OpenParams.ByRefrence openParams);
在main函数中初始化结构体
然后调用,OPEN_SUCCESS=1,调用成功
6、JNA模拟结构体数据传递
实现步骤
同上
从.h头文件中看到需要传入三个参数,hChannel指针,packetType枚举类型,param void指针,先看看C++中Packet Type,例如其中RTR_GetJpgFrame获取的数据param对应是一个FrameInfo结构体指针,所以要读取数据,我们要模拟结构体FrameInfo进行数据传递
看看C++中FrameInfo结构体,一共四个成员变量,char指针指向的是图片jpg的二进制地址,len表示二进制数据长度,windth,height宽高
Java代码
枚举类型,也可以直接传int值
模拟创建FrameInfo结构体继承Structure,设置成员变量参考映射表,创建两个内部类ByReference、ByValue,char指针我们可以用Pointer表示,注意需要给Pointer申请内存
先声明一个imgbyte用来接收二进制数据
在CLibrary中创建SCT_ChannelRead(Pointer hChannel, int packetType,Frame Info.ByReference frameInfo);
在main函数中初始化FrameInfo结构体并调用SCT_ChannelRead()方法,如果调用成功,读取_pData,保存成图片
运行,返回结果1,成功保存图片readjpg
7、JNA模拟结构体回调方法
调用步骤
同上
从.h头文件可以看到需要hChannel指针,optionType枚举类型,param void指针三个参数
先查看OptionType,例如OT_SetFunGetMtData类型,param指向的是一个回调函数FunGetMtData();
查看FunGetMtData回调方法,我们需要在Java中实现该回调
查看C++结构体MtImgInfo,我们现在就读取这个结构体数据
Java代码
枚举类型参考上面
现在模拟创建MtImgInfo结构体继承Structure,设置成员变量参考映射表,创建两个内部类ByReference、ByValue,获取数据,要想获取回调方法FunGetMtImgInfo()中其他数据,可以用同样的方法
在CLibrary中创建本地方法SCT_ChannelSetOpt(Pointer hChannel, int optionType,CallBack callback);形参callback创建请看下一步
在CLibrary中定义一个回调接口CallBack继承Callback,并在接口内创建一个FunGetMtData方法对应C++中的回调方法
在CLibrary创建FunGeMtData实现接口CallBack并实现FunGetMtData()方法
在main函数中调用本地方法SCT_ChannelSetOpt();
运行查看回调结果,成功输出回调结果
写了这么多,差不多各种类型的例子都有了,如果有哪里不对的,或者有问题需要的探讨的,可以留言。