最近项目需求,对接海康硬盘录像机,通过调用海康威视提供的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 "";
}
}
}