JNA二次开发华视身份证阅读器
前言
这三天了解了一下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资料
二次开发sdk文件下载地址
编写代码
使用springBoot实现本次开发。
termb.dll API函数的动态连接库
sdtapi.dll 安全模块通讯函数
WltRs.dll 身份证照片解码库
加载dll,将dll文件置于resources文件夹下,网上有很多关于Dll放置位置的文章,本次采用的是置于项目中,方便读取DLL
关于获取DLL路径的方式,可以参考
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路径问题