目录
JNA技术难点
1、函数回调
2、结构体
3、指针
JNA技术难点
有过跨平台、跨语言开发的程序员都知道,跨平台、预研调用的难点,就是不同语言之间数据类型不一致造成的问题。绝大部分跨平台调用的失败都是这个问题造成的。关于这一点,不论何种语言、何种技术方案都无法解决这个问题。JNA也不列外。
上面说到接口中使用的函数必须与链接库中的函数原型保持一致,这是JNA甚至所有跨平台调用的难点,因为C/C++的类型与Java的类型是不一样的,你必须转换成java对应类型让它们保持一致,这就是类型映射(Type Mappings),JNA官方给出的默认类型映射表如下:
其中类型映射的难点在于结构体、指针和函数回调。
1、函数回调
c++代码长这样:
// 实时监视接口VIXHZ_EXPORT long _VixHz_StartRealPlay(long lLoginID, long lChannelNO, int type, HWND hWnd, CallbackFuncRealDataEx func, unsigned long dwUserParm);// 回调函数typedef bool (CALLBACK * CallbackFuncRealDataEx)( unsigned long lRealHandle, unsigned long dwDataType, unsigned char *pBuffer, unsigned long dwBufSize, int width, int height, int ra, unsigned long dwUser);
转java,在CLibrary接口中函数回调函数声明为RealDataCallbackListener接口:
/*** 用于用户请求实时视频** @param lLoginID 登录ID* @param lChannelNO 通道号* @param type 码流类型,1-主码流,2-辅码流* @param hWnd 窗口句柄 传空指针即可* @param realDataResult 实时数据回调函数* @param dwUserParm 用户数据参数* @return 请求成功返回此次会话session,错误返回错误码(<=0) {@link NetworkErrorEnum}*/NativeLong VixHz_StartRealPlay(NativeLong lLoginID, NativeLong lChannelNO, Integer type, Pointer hWnd, RealDataCallbackListener realDataResult, String dwUserParm);
RealDataCallbackListener接口:
package com.focus.vision.device.sdk.callback;import com.sun.jna.Callback;import com.sun.jna.NativeLong ;import com.sun.jna.Pointer;import org.ponent;/*** 回调函数声明接口*/@Componentpublic interface RealDataCallbackListener extends Callback {/*** 获取实时视频数据回调** @param lRealHandle 播放句柄* @param dwDataType 无实际意义* @param pBuffer 实时数据* @param dwBufSize* @param dwUser 用户数据* @return*/boolean getRealData(NativeLong lRealHandle, NativeLong dwDataType, Pointer pBuffer, NativeLong dwBufSize, String dwUser);}
再在需要调用CLibrary.INSTANCE方法的类中,声明getVixHz_StartRealPlay:
/*** 用户请求实时视频 回调函数** @param lLoginID 登录ID* @param lChannelNO 通道号* @param type 码流类型,1-主码流,2-辅码流* @param hWnd 窗口句柄 传空指针即可* @param realDataResult 实时数据回调函数* @param dwUserParm 用户数据参数* @return*/public static boolean getVixHz_StartRealPlay(NativeLong lLoginID, NativeLong lChannelNO, Integer type, Pointer hWnd, RealDataCallbackListener realDataResult, String dwUserParm) {NativeLong startRealPlay = CLibrary.INSTANCE.VixHz_StartRealPlay(lLoginID, lChannelNO, type, hWnd, realDataResult, dwUserParm);if (startRealPlay.intValue() > 0) {return true;}return false;}
测试实时预览,函数回调是否能够调用成功:
ackage com.focus.vision.device.sdk;import com.focus.vision.device.sdk.callback.RealDataCallbackListener;import com.focus.vision.device.sdk.callback.TalkDataCallbackListener;import com.focus.vision.device.sdk.enums.AlarmCallbackEnum;import com.focus.vision.device.sdk.enums.PTZConfigTypeEnum;import com.focus.vision.device.sdk.enums.PTZParamConfigTypeEnum;import com.focus.vision.device.sdk.enums.RecordTypeEnum;import com.focus.vision.device.sdk.structure.QueryStructure;import com.sun.jna.NativeLong;import com.sun.jna.Pointer;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.test.context.SpringBootTest;import org.ponent;import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)@SpringBootTest@Componentpublic class DeviceSdkTests {private static Logger logger = LoggerFactory.getLogger(DeviceSdkTests.class);@Testpublic void testDeviceSdk() {// 初始化SDKboolean initSDKResult = CLibrary.INSTANCE.VixHz_InitSDK();if (initSDKResult) {logger.info("SDK初始化成功...");// 用户根据自己的需要设置SDK消息回调CLibrary.INSTANCE.VixHz_SetMessageCallback(AlarmCallbackEnum.CALLBACK_DEVC_DISCONNECT.getKey(), null, "0");// 测试登录设备,返回登录句柄NativeLong loginResult = testLoginDevice();if (loginResult.intValue() == -1) {return;}// 测试打开和关闭实时预览testRealPlay(loginResult);// 用户登出设备CLibrary.INSTANCE.VixHz_Logout(loginResult);}// 退出释放SDK资源CLibrary.INSTANCE.VixHz_UnInitSDK();}/*** 测试登录设备,返回登录句柄** @return*/private NativeLong testLoginDevice() {NativeLong loginResult = CLibrary.INSTANCE.VixHz_LoginDevice("172.18.30.16", 80, "admin", "123456", "{}");if (loginResult.intValue() <= 90000) {logger.error("设备注册失败...loginResult= {}", loginResult);return new NativeLong(-1);}logger.info("设备注册成功...loginResult = {}", loginResult);return loginResult;}/*** 测试打开和关闭实时预览*/private void testRealPlay(NativeLong loginResult) {boolean startRealPlay = getVixHz_StartRealPlay(loginResult, new NativeLong(0), 1, Pointer.NULL, new RealDataCallbackListener() {@Overridepublic boolean getRealData(NativeLong lRealHandle, NativeLong dwDataType, Pointer pBuffer, NativeLong dwBufSize, String dwUser) {return true;}}, "");if (!startRealPlay) {logger.error("实时预览失败...startRealPlay = {}", startRealPlay);return;}logger.info("实时预览成功...startRealPlay = {}", startRealPlay);// 关闭实时预览NativeLong stopRealPlayResult = CLibrary.INSTANCE.VixHz_StopRealPlay(loginResult, new NativeLong(100));if (stopRealPlayResult.intValue() <= 0) {logger.error("关闭实时预览失败...stopRealPlayResult = {}", stopRealPlayResult);}logger.info("关闭实时预览成功...stopRealPlayResult = {}", stopRealPlayResult);}
运行结果:
OK,函数回调成功啦~~
2、结构体
c++代码:
// c++接口不完整,作为一个java模仿着写了个结构体,如有问题,请不吝赐教struct Vix_QueryInfoParam{void *bebpic;void *begtime;void *endtime;int recordtype;int source;void *channelno;int protocolType;};
java代码,编写结构体:
package com.focus.vision.device.sdk.structure;import com.focus.vision.device.sdk.enums.RecordTypeEnum;import com.sun.jna.Structure;import java.util.Arrays;import java.util.List;/*** 查询信息结构体*/public class QueryStructure extends Structure {/*** 是否为图片查询,0-否*/public Pointer bebpic;/*** 开始时间*/public Pointer begtime;/*** 结束时间*/public Pointer endtime;/*** 录像类型 {@link RecordTypeEnum}*/public Integer recordtype;/*** 录像源(只能为2,表设备录像)*/public Integer source;/*** 通道号*/public Pointer channelno;/*** 协议类型(只能为1,表cfi协议)*/public Integer protocolType;/*** 内部类实现指针类型接口*/public static class ByReference extends QueryStructure implements Structure.ByReference{}/*** 内部类实现值类型接口*/public static class ByValue extends QueryStructure implements Structure.ByValue{}/*** 定义取值次序,需要与c++中对齐,不然报NoSuchFieldError** @return*/@Overrideprotected List<String> getFieldOrder() {return Arrays.asList(new String[] {"bebpic", "begtime", "endtime", "recordtype", "source", "channelno", "protocolType"});}}
注意:结构体定义取值次序,需要与c++中对齐,不然报NoSuchFieldError错误!
重点(此处总结也不理解什么时候用ByReference,什么时候用ByValue,,,请大佬赐教):
1.只要涉及到结构体的传递,必须使用ByReference或者ByValue中的一种
2.指针和引用的传递使用ByReference
3.拷贝参数传递使用ByValue
java代码,声明含结构体函数:
/*** 用于用户查询视频录像,将要查询的录像信息结构体封装成xml格式的string,在回调函数中再将其解析为录像信息结构体** @param queryInfo 查询信息结构体* @param pRecQueryCallback 录像查询回调函数 * @param dwUser 用户数据 TODO 类型待确认 NativeLong = 0* @return*/NativeLong VixHz_QueryRecrodFile(QueryStructure.ByReference queryInfo, Pointer pRecQueryCallback, String dwUser);
java代码,调用结构体函数:
/*** 测试录像查询、下载和停止、回放和停止----录像处理参数程序 */private void testRecordPlay(NativeLong loginResult) {// 查询 TODO 类型不符QueryStructure.ByReference queryStructure = new QueryStructure.ByReference();queryStructure.bebpic = "0";queryStructure.begtime = "1434816000";queryStructure.endtime = "1434902399";queryStructure.channelno = "0";queryStructure.recordtype = RecordTypeEnum.RECORD_ALARM_EXTERNAL.getKey();queryStructure.source = 2;queryStructure.ProtocolType = 1;NativeLong queryRecrodFileResult = CLibrary.INSTANCE.VixHz_QueryRecrodFile(queryStructure, Pointer.NULL, "");if (queryRecrodFileResult.intValue() <= 0) {logger.error("录像查询失败...queryRecrodFileResult = {}", queryRecrodFileResult);return;}logger.info("录像查询成功...queryRecrodFileResult = {}", queryRecrodFileResult);}
实际开发中使用ByReference一直报参数类型转换异常,最后使用ByValue接口调用成功。。。
其实我们可以看到,JNA使用的主要难点在于结构体定义和传递,只要弄清楚如何对付结构体,剩下的事情也就水到渠成了。说了这么多,其实就想告诉大家,在做跨语言调用时,尽量还是要封装一下函数、结构体,让数据传递时更为简单。
3、指针
3.1 void *var ==>Pointer
c++中此类型即可转化为Pointer,此处举一个之前的栗子吧:
CLibrary接口中声明:
int GetCcrInfo(Pointer str, Pointer out,Pointer mfr);int OpenCcr(Pointer str);int CloseCcr();
以上调用:
static CLibrary INSTANCE = (CLibrary) Native.loadLibrary("CCR_SDKx64.dll", CLibrary.class);public static CCrInfoBean getCCR() {CCrInfoBean cCrInfoBean = new CCrInfoBean();Pointer pMac = new Memory(20);Pointer pMaxChannel = new Memory(20);Pointer pstr = new Memory(255);Pointer pmfr = new Memory(4);try {INSTANCE.OpenCcr(pstr);// 读取ccr信息INSTANCE.GetCcrInfo(pMac, pMaxChannel,pmfr);INSTANCE.CloseCcr();if (checkMac(pMac.getString(0))) { // mac地址合法cCrInfoBean.setMaxChannel(pMaxChannel.getInt(0));cCrInfoBean.setMac(pMac.getString(0));int nManufacture = pmfr.getInt(0);logger.info("Read ccr manufacture Success!"+ nManufacture);cCrInfoBean.setManufactureList(getManufactureList(nManufacture));logger.info("Read ccr Success!"+ JSON.toJSONString(cCrInfoBean));} else {cCrInfoBean.setMaxChannel(pMaxChannel.getInt(0));Set<ManufactureTypeEnum> manufactureTypeEnums = new HashSet();manufactureTypeEnums.add(ManufactureTypeEnum.IPC2_0);cCrInfoBean.setManufactureList(manufactureTypeEnums);cCrInfoBean.setMaxChannel(300);//默认给300路logger.info("Read ccr mac not match!"+ JSON.toJSONString(cCrInfoBean));}} catch (Exception e) {Set<ManufactureTypeEnum> manufactureTypeEnums = new HashSet();manufactureTypeEnums.add(ManufactureTypeEnum.IPC2_0);cCrInfoBean.setManufactureList(manufactureTypeEnums);cCrInfoBean.setMaxChannel(300);//默认给300路logger.error("Read ccr error! return a default CCR"+ JSON.toJSONString(cCrInfoBean));}return cCrInfoBean;}
这是一直在用的栗子,可直接食。
3.2 char**是二重指针,也就是指向指针变量的指针
char**==》PointerByReference
PointerByReference如何转String?
// 获取指针 Pointer value = strOutInfo.getValue();PointerByReference strLicense = new PointerByReference();String license = strLicense.getValue().getString(0, "UTF-8");
后续想到什么继续补充吧,,,
JNA终结
JNA实战