最近项目需求,对接海康硬盘录像机,通过调用海康威视提供的Win环境下的dll文来实现功能。海康有提供Java的demo,不过为了避免以后需要调用其他dll的时候不会写,做个记录。

使用jna来加载调用dll文件

踩坑!!!!!

  • 开发环境Java的JDK版本位数(32/64)需要与dll相同,不然不行
  • 加载的时候不能加.dll后缀!
  • 程序打jar包后dll加载报错(这个坑我研究了一个礼拜都没搞明白,查各种资料,只给了两种解决方案、1.把dll文件放到项目外,路径在外部配置;2.把dll文件配置到环境变量或者放到java环境变量的bin目录里;搞的时间太久了,不能拖了,最后采用了把dll文件放到项目外。),今天跟老大沟通,他给我远程调试,配置,最后把dll文件放到项目的resources资源路径下,64位的新建目录win32-x86-64放进去,32位的新建目录win32-x86放进去,配置下路径就可以了,下文中上代码。
  • c++头文件中的char,在java中不能用String,得用byte[] xxx = new byte[头文件中的长度]
  • c++中的bool 直接在Java中设置boolean后,连续有三个boolean属性时,只有第一个的值是正确的,剩下的值都是错误的,导致后面的其他类型的属性值也全部错误,解决办法就是,Java中用byte代替boolean(0false,1true),c++bool占1字节。

导包

        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>5.5.0</version>
        </dependency>

配置加载dll路径(踩坑)

//加载dll接口
@Slf4j
public class IcwmApiDllPath {

    //定义的dll接口
    public static IcwmApi icwmApi ;

    static {

        //找项目内的dll文件路径
        String filePath = Thread.currentThread().getContextClassLoader().getResource("").getPath()
                .replaceFirst("/","").replaceAll("%20"," ").replace("/","\\") + "win32-x86-64\\icwm_api";
        //得到路径后 获取.jar的坐标
        int jarindex = filePath.indexOf(".jar");
        if(jarindex > 0) {
            //返回路径符\在此字符串中最后一次出现处的索引,从指定的.jar坐标开始反向搜索,如果此字符串中没有这样的字符,则返回 -1。
            int index = filePath.lastIndexOf("\\", jarindex);
            if(index > 0) {
                //拼接dll名称
                filePath = filePath.substring(0, index) + "\\icwm_api";
                //判断file:是否存在  然后去掉
                if (filePath.indexOf("file:") >= 0) {
                    filePath = filePath.substring(5);
                }
            }
        }
        //加载路径
        icwmApi = (IcwmApi) Native.load(filePath, IcwmApi.class);
        log.info(filePath);
    }

}

普通方法编写


C++

/**
 * 获取加载区信息
 * @param icwm   设备句柄
 * @param json   获取加载区信息字符串缓存
 * @param len    缓存大小
 * @return int 返回实际大小
 */
IA_EXPORT int get_load_loc_list(void* icwm, char* json, int len);

由于java跟c++的数据类型不同,所以jna就贴心的帮我们编译了类型转换,百度一堆jna类型对应

需要注意的是,c++返回的内容(指针)需要在参数中传进去。这个时候在java中就需要提前定义好并给定长度!,如果c++中是char*或者void*……那么在Java中的数据类型就是Pointer,下面是实现。

普通方法调用

Java

public interface IcwmApi extends Library{
   /**
    * 获取加载区信息
    * @param icwm 设备句柄
    * @param json 获取加载区信息字符串缓存 指针
    * @param len  缓存大小
    * @return 返回实际大小
    */
   int get_load_loc_list(Pointer icwm, Pointer json, int len);
}

回调函数方法编写

回调函数c++跟java就大不相同了,不多bb,直接上代码

C++

/**
 * 找到设备回调函数定义
 * @param context     传入的关联实体
 * @param sn     设备序号
 * @param ip     链接IP
 * @param port   链接端口
 */
typedef void (*find_devices_func)(void* context, const char* sn, const char* ip, int port);

/**
 * 尝试查找网内刻录设备
 * @param is     is  检测超时(秒)
 * @param fdf    找到设备回调函数
 * @param context     传入关联实体
 */
IA_EXPORT bool find_devices(int is, find_devices_func fdf, void* context = 0);

java定义c++的回调函数接口需要继承Callback接口,然后再方法里参数类型就是Callback

Java

public interface IcwmApi extends Library {

    /**
     * 找到设备回调函数定义
     */
    public static interface find_devices_func extends Callback {
        /**
         * @param context 传入的关联实体
         * @param sn      设备序号
         * @param ip      链接IP
         * @param port    链接端口
         */
        public void find_devices_func(Pointer context, String sn, String ip, int port);
    }

    /**
     * 尝试查找网内刻录设备
     * @param is      检测超时  秒
     * @param fdf     找到设备回调函数
     * @param context 传入关联实体
     * @return true找到   false未找到
     */
    boolean  find_devices(int is, Callback fdf, Pointer context);
}

回调函数实现

@Slf4j
@Service
public class IcwmLinkService {

    /* 扫描回调 */
    public class FindDevices implements IcwmApi.find_devices_func {
        /**
         * @param context 传入的关联实体
         * @param sn      设备序号
         * @param ip      链接IP
         * @param port    链接端口
         */
        @Override
        public void find_devices_func(Pointer context, String sn, String ip, int port) {
            System.out.println("扫描到设备序号:" + sn + ",IP:" + ip + ":" + port);
            try {
                if (context == Pointer.NULL) {

                    //我是通过webSocket发送到页面的
                    oneWebSocket.sendMessage("扫描到设备序号:" + sn + ",IP:" + ip + ":" + port);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 扫描设备
     * @return
     */
    public boolean findDevices() {
        IcwmApi.find_devices_func callbak = findDevices;
        boolean devices = IcwmApiDllPath.icwmApi.find_devices(3, callbak, Pointer.NULL);
        return devices;
    }

}

实体类

Java中的byte[] 与String转换可以用下文的工具类也可以直接new String(byte[]);

C++

/** 配置信息 */
typedef struct tag_IcwmConfig {
    int       iMaxRunTaskFail;        
    char      strBurnTempDir[512];    
    bool      bIsStopTaskFail;        
    Cdrom     cdroms[20];             //!< 实体集合
}IcwmConfig; //!< 配置信息

/** 光驱配置信息 */
typedef struct tag_Cdrom {
    char        strSerialNumber[64]; 
    uint8_t     ucIndex;         
    char        strName[64];     
}Cdrom; //!< 光驱信息

 java实体需要继承Structure接口,注解@Structure.FieldOrder中的参数需要与实体一一对应

Java

    /**
     * 配置信息
     */
    @Structure.FieldOrder({"iLogLevel","strBurnTempDir","bIsStopTaskFail","cdroms"})
    public static class IcwmConfig extends Structure {
        public int iLogLevel; 
        public byte[] strBurnTempDir = new byte[512]; 
        public byte bIsStopTaskFail; // 0 false  1 true
        public Cdrom[] cdroms = new Cdrom[20]; //实体集合
    }

    /**
     * 光驱配置信息
     */
    @Structure.FieldOrder({"strSerialNumber","ucInde","strName"})
    public static class Cdrom extends Structure {
        public byte[] strSerialNumber = new byte[64];
        public int ucInde;
        public byte[] strName = new byte[64]; 
    }

工具类

public class CommonUtils {
    /**
     * 去掉byte[]中填充的0 转为String
     *
     * @param buffer
     * @return
     */
    public static String byteToStr(byte[] buffer) {
        try {
            int length = 0;
            for (int i = 0; i < buffer.length; ++i) {
                if (buffer[i] == 0) {
                    length = i;
                    break;
                }
            }
            return new String(buffer, 0, length, "UTF-8");
        } catch (Exception e) {
            return "";
        }
    }
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注