前言
这两天了解了一下java调用dll动态库的方法,总的有三种:JNI、JNA、JNative,其中JNA调用DLL是最方便的。本次总结代码可以直接拿去用,不需要做太多的修改。
·JNI
JNI是 Java native interface的缩写,它提供了一定的API,实现了Java和其他语言之间的通信。从Java1.1之后JNI标准成为Java平台的一部分,它允许Java与其他语音进行通信,但是过程有点繁琐和复杂,不利于平时的敏捷开发。
·JNA
JNA是提供一些Java工具类用于在运行期间动态访问系统本地库(win dll),而不需要自己写JNative和JNI代码,只需要在一个接口实现目标native library,在运行之后 JNA会自己去映射相应的方法。
·JNative
JNative是一种能够使用Java语言调用DLL的技术,对JNI进行了封装。
Java使用 JNI来调用dll动态库的调用,工作量略大,一般情况下开发人员会选用JNA或JNative。
使用JNative调用DLL除了要引入jar包外还需要额外引入一个dll文件,而JNA只需要引入jar即可使用。
添加依赖
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>3.1.0</version>
</dependency>
SDK资料
编写代码
使用springBoot实现本次开发。
termb.dll API函数的动态联接库
sdtapi.dll 安全模块通讯函数
WltRs.dll 身份证相片解码库
加载dll,将dll文件放在resources文件夹下,网上有很多关于Dll放置位置的文章,本次采用的是放在项目中,方便读取DLL
关于获取DLL路径的方法,可以参考https://blog.csdn.net/Captain249/article/details/98172911
JNA实现library接口
```java
public interface TermbDLL extends Library {
// termb.dll API函数的动态联接库
// sdtapi.dll 安全模块通讯函数
// WltRs.dll 身份证相片解码库
TermbDLL termbDLL = (TermbDLL) Native.loadLibrary(TermbDLL.class.getResource("/Termb.dll").getPath().substring(1), TermbDLL.class);
/**
* 连接读卡器
*/
int CVR_InitComm(int Port);
/**
* 卡认证
*/
int CVR_Authenticate();
/**
* 读卡
*/
int CVR_Read_Content(int active);
/**
* 读卡操作 含指纹
*/
int CVR_Read_FPContent(int active);
/**
* 关闭连接
*/
int CVR_CloseComm();
/**
* 找卡
*/
int CVR_FindCard();
/**
* 选卡
*/
int CVR_SelectCard();
/**
* 获取姓名
*/
int GetPeopleName(byte[] name, IntByReference len);
/**
* 得到性别信息
*/
int GetPeopleSex(byte[] sex, IntByReference len);
/**
* 得到民族信息
*/
int GetPeopleNation(byte[] strTmp, IntByReference strLen);
/**
* 得到出生日期
*/
int GetPeopleBirthday(byte[] strTmp, IntByReference strLen);
/**
* 得到地址信息
*/
int GetPeopleAddress(byte[] strTmp, IntByReference strLen);
/**
* 得到身份证号信息
*
*/
int GetPeopleIDCode(byte[] strTmp, IntByReference strLen);
/**
* 得到发证机关信息
*/
int GetDepartment(byte[] strTmp, IntByReference strLen);
/**
* 得到有效开始日期
*/
int GetStartDate(byte[] strTmp, IntByReference strLen);
/**
* 得到有效截止日期
*/
int GetEndDate(byte[] strTmp, IntByReference strLen);
/**
* 得到安全模块号
*/
int CVR_GetSAMID(byte[] strTmp);
/**
* 得到指纹数据,不超过1024字节
*/
int GetFPDate(byte[] strTmp, IntByReference strLen);
/**
* 得到头像照片bmp数据,不超过38862字节
*/
int GetBMPData(byte[] strTmp, IntByReference strLen);
/**
* 得到头像照片base64编码数据,不超过38862*2字节
*/
int Getbase64BMPData(byte[] strTmp, IntByReference strLen);
/**
* 得到头像照片jpg数据,不超过38862字节
*/
int Getbase64JpgData(byte[] strTmp, IntByReference strLen);
}
``
具体调用的方法
@Service
public class CPReadIdCardInfoService {
//成功状态码
private static final int SUCCESS = 1;
//失败状态码
private static final int FAIL = 0;
private static final String CHARACTER_ENCODING = "gb2312";
public CPReadIdCardInfoModel readIdCardInfo() throws Exception {
TermbDLL instance = buildInstance();
try {
connAndCheckDevice(instance);
//读取身份证各信息
IntByReference len = new IntByReference();
//1、姓名
String name = getPeopleName(instance, len);
//2、性别
String sex = getPeopleSex(instance,len);
return readIdCardInfoModel;
} finally {
instance.CVR_CloseComm();
}
}
private String getPeopleName(TermbDLL instance, IntByReference len) {
try {
//长度更具华视文档设定
byte [] nameStrTmp = new byte[30];
//JNA调用DLL方法
instance.GetPeopleName(nameStrTmp,len);
return handleResult(nameStrTmp);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String getPeopleSex(TermbDLL instance, IntByReference len) {
try {
byte [] sexStrTmp = new byte[2];
instance.GetPeopleSex(sexStrTmp,len);
return handleResult(sexStrTmp);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String getPeopleInfo(TermbDLL instance, String methodName, IntByReference len) {
try {
byte [] strTmp = new byte[10];
Method method = instance.getClass().getMethod(methodName, byte[].class, IntByReference.class);
method.invoke(instance,strTmp,len);
return handleResult(strTmp);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String handleResult(byte[] nameByte) throws UnsupportedEncodingException {
//处理返回值防止乱码
return new String(nameByte, CHARACTER_ENCODING).trim();
}
private void connAndCheckDevice(TermbDLL instance) throws Exception {
//1、初始化连接
cvrInitComm(instance);
//2、读卡(同一个身份证,只要读取一次,下次读取优先从缓存中读取,并且CVR_Authenticate返回为2,卡认证失败,所以优先读卡一次)
int readStatus = instance.CVR_Read_Content(1); //读取卡
//2、卡认证,超出两分钟就是本次读卡失败
int authenticate = getAuthenticateStatus(instance, readStatus);
if (authenticate != SUCCESS)
throw new Exception("读卡失败,请重新放置卡片");
if (readStatus != SUCCESS)
readStatus = instance.CVR_Read_Content(1); //读取卡
if (readStatus != SUCCESS)
throw new Exception("证件信息读取失败,请重新放置证件");
}
/**
* 卡认证
* @param instance DLL
* @param readStatus 同一个身份证,只要读取一次,下次读取优先从缓存中读取,并且CVR_Authenticate返回为2,卡认证失败,所以优先读卡一次
*/
private int getAuthenticateStatus(TermbDLL instance, int readStatus) throws InterruptedException {
if (readStatus == SUCCESS)
return SUCCESS;
final long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(2L);
int authenticate = FAIL;
//循环读卡,失效时间为2分钟
while (authenticate != SUCCESS){
authenticate = instance.CVR_Authenticate();
if (deadline ==SUCCESS)continue;
if (deadline - System.nanoTime()<0L)break;
Thread.sleep(100);//短暂休眠
}
return authenticate;
}
private void cvrInitComm(TermbDLL instance) throws Exception {
int cvrInitComm = instance.CVR_InitComm(1001);
if (cvrInitComm != SUCCESS){
throw new Exception("读卡器连接失败,请检查设备状态");
}
}
private TermbDLL buildInstance() throws Exception {
URL dllResource = TermbDLL.class.getResource("/Termb.dll");
if(dllResource == null) { throw new Exception("请检查设备状态"); }
String dllPath = dllResource.getPath();
if (StringUtils.isBlank(dllPath)) { throw new Exception("请检查设备状态"); }
if (StringUtils.startsWithIgnoreCase(dllPath,"/")){
dllPath = dllPath.substring(1);
}
try {
return (TermbDLL) Native.loadLibrary(dllPath, TermbDLL.class);
} catch (Exception e) {
throw new Exception("请检查设备状态");
}
}
}
遇到的问题(坑)
问题一:华视的设备驱动,在windows电脑关闭之后,偶尔会出现需要重新安装;
- 暂时还没有比较好的解决方案,华视客服也是没有给出具体的原因。
问题二:同时多次发起申请,会造成设备阻塞
- 连接的时候优先使用缓存,看是否存在成功的连接
- 同一个身份证多次读取,在设备没有关闭的情况下,会优先获取缓存,因为卡认证消耗资源,所以优先读卡,如果成功说明有缓存,不需要认证;
- 卡认证需要一直循环认证,因为在请求接口的时候,设备上不一定放置了身份证,防止出现一只认证,设定一个超时时间;
问题三:DDL路径问题
- 在idea 开发阶段,顺利读取到身份证信息,但是在打成jar之后,使用Java -jar的方式运行,无法获取到jar中的DDL,路径无法访问,出现各种!;
- 将ddl读取到内存中,每次从内存中去读取DDL文件,但是需要每次都读取,浪费资源
- 将DDL放在一个绝对路径中,直接读取,这个时候出现一个小麻烦,绝对路径之后,在连接设备的时候一直返回-2,并且出现DDL路径不存在的问题,原来termb.dll会去调用另外的ddl