目录
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.springframework.stereotype.Component;
/**
* 回调函数声明接口
*/
@Component
public 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.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@Component
public class DeviceSdkTests {
private static Logger logger = LoggerFactory.getLogger(DeviceSdkTests.class);
@Test
public void testDeviceSdk() {
// 初始化SDK
boolean 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() {
@Override
public 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
*/
@Override
protected 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");
后续想到什么继续补充吧,,,