目录

JNA技术难点

1、函数回调

2、结构体

3、指针


JNA技术难点

有过跨平台、跨语言开发的程序员都知道,跨平台、预研调用的难点,就是不同语言之间数据类型不一致造成的问题。绝大部分跨平台调用的失败都是这个问题造成的。关于这一点,不论何种语言、何种技术方案都无法解决这个问题。JNA也不列外。

上面说到接口中使用的函数必须与链接库中的函数原型保持一致,这是JNA甚至所有跨平台调用的难点,因为C/C++的类型与Java的类型是不一样的,你必须转换成java对应类型让它们保持一致,这就是类型映射(Type Mappings),JNA官方给出的默认类型映射表如下:

Java中映射的概念 jna类型映射_Java中映射的概念

其中类型映射的难点在于结构体、指针和函数回调。


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);
    }

运行结果:

Java中映射的概念 jna类型映射_JNA类型映射_02

 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");

后续想到什么继续补充吧,,,