`

用JNA开发身份证阅读程序

    博客分类:
  • Java
阅读更多

     JNA(Java Native Access)是建立在JNI基础上的开源Java框架。
    项目网址:
https://github.com/twall/jna
    使用JNI调用dll是比较麻烦的,如已有一个dll文件,还需要使用C语言另外编写一个dll(根据由java代码生成的C/C++ 头文件编写 ),使用者需要比较了解C/C++。

    使用JNA, 需再编写适配用的dll,只 编写一个 Java 接口和一些代码,作为dll的代理,就可在Java程序中调用dll,JNA自动实现Java和C的数据类型映射。

准备工作

     下载JAR包 JNA当前版本为 3.5.1,包含两个jar包:jna.jar、platform.jar,一般使用只需jna.jar就可以了,platform.jar提供了许多工具类。

     JNA运行时会到path环境变量指定的路径下寻找dll库,用户也可以设置java 系统参数-Djna.library.path=...

 

JNA官网的一个简单例子

     使用C运行时库msvcrt.dll中的printf函数打印字符,

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

/** Simple example of JNA interface mapping and usage. */
public class HelloWorld {

    // This is the standard, stable way of mapping, which supports extensive
    // customization and mapping of Java to native types.
    public interface CLibrary extends Library {

CLibrary INSTANCE = (CLibrary)Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);

void printf(String format, Object... args);

    }

   public static void main(String[] args) {
     CLibrary.INSTANCE.printf("Hello, World\n");
     for (int i=0;i < args.length;i++) {
       CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
     }
   }
}

 

使用JNA开发身份证阅读器

     用到的身份证阅读器基本类库:sdtapi.dll、WltRs.dll。以下为示例代码,其中列出了sdtapi.dll、WltRs.dll中提供的方法(方法名可以通过vc dumpbin等工具查看),介绍了阅读身份证的基本流程及接口的使用方法。

---IDCardReader---

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.HashMap;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;

public class IDCardReader {

/**
* sdtapi接口
*
* 声明sdtapi.dll中的导出方法
*
* 端口类api: SDT_OpenPort,SDT_ClosePort,SDT_GetCOMBaud,SDT_SetCOMBaud
* SAM(身份证安全控制模块security account manager)类aip: SDT_GetSAMStatus,SDT_GetSAMID,SDT_GetSAMIDToStr,SDT_ResetSAM
* 身份证卡类api: SDT_StartFindIDCard,SDT_SelectIDCard,SDT_ReadBaseMsg,SDT_ReadBaseMsgToFile,SDT_ReadNewAppMsg
*
* 在普通开发中仅需使用部分端口类、身份证卡类api即可满足需要
*
* @author sunjc
*
*/
public interface Sdtapi extends Library {
Sdtapi INSTANCE = (Sdtapi) Native.loadLibrary("sdtapi", Sdtapi.class);

/************************端口类API *************************/

/**
* 打开串口/USB口
*
* @param iPort 端口号
* 1-16(十进制)为串口,1001-1016(十进制)为USB口
* @return
*/
int SDT_OpenPort(int iPort);

/**
* 关闭串口/USB口
*
* @param iPort 端口号
* @return
*/
int SDT_ClosePort(int iPort);

/**
* 查看串口波特率
*
* @param iPort 串口号 (1-16)
* @param pucBaudRate 串口波特率
* @return
*/
int SDT_GetCOMBaud(int iPort, IntByReference pucBaudRate);

/**
* 设置串口波特率
*
* @param iPort 串口号 (1-16)
* @param puiCurrBaud 当前串口波特率
* @param puiSetBaud 要设置的串口波特率
* 波特率只能为以下项115200,57600,38400,19200,9600
* @return
*/
int SDT_SetCOMBaud(int iPort, int puiCurrBaud, int puiSetBaud);

/************************SAM类API *************************/

/**
* 对SAM_V 进行状态检测
*
* @param iPort 端口号
* @param iIfOpen
* @return
*/
int SDT_GetSAMStatus(int iPort, int iIfOpen);

/**
* 读取SAM_V 的编号(十六进制)
*
* @param iPort 端口号
* @param pucSAMID SAM_V编号
* @param iIfOpen
* @return
*/
int SDT_GetSAMID(int iPort, CharBuffer pucSAMID, int iIfOpen);

/**
* 读取SAM_V 的编号(字符串格式)
*
* @param iPort 端口号
* @param pucSAMID SAM_V编号
* @param iIfOpen
* @return
*/
int SDT_GetSAMIDToStr(int iPort, CharBuffer pucSAMID, int iIfOpen);

/**
* 对SAM_V 复位
*
* @param iPort 端口号
* @param iIfOpen
* @return
*/
int SDT_ResetSAM(int iPort, int iIfOpen);

/************************身份证卡类API *************************/

/**
* 找卡
*
* @param iPort 端口号
* @param pucManaInfo 证/卡芯片治理号(4字节)
* @param iIfOpen
* 0表示不在该函数内部打开和关闭串口,此时确保之前调用了SDT_OpenPort来打开端口,并且在不需要与端口通信时,调用SDT_ClosePort关闭端口;
* 非0表示在API函数内部包含了打开端口和关闭端口函数,之前不需要调用SDT_OpenPort,也不用再调用SDT_ClosePort
* @return
*/
int SDT_StartFindIDCard(int iPort, IntByReference pucManaInfo, int iIfOpen);

/**
* 选卡
*
* @param iPort 端口号
* @param pucManaMsg 证/卡芯片序列号(8字节)
* @param iIfOpen
* @return
*/
int SDT_SelectIDCard(int iPort, IntByReference pucManaMsg, int iIfOpen);

/**
* 读取固定信息
*
* @param iPort 端口号
* @param pucCHMsg 文字信息
* @param puiCHMsgLen
* @param pucPHMsg 照片信息
* @param puiPHMsgLen
* @param iIfOpen
* @return
*/
int SDT_ReadBaseMsg(int iPort, CharBuffer pucCHMsg,
IntByReference puiCHMsgLen, ByteBuffer pucPHMsg,
IntByReference puiPHMsgLen, int iIfOpen);

/**
* 读取固定信息,将信息写到文件
*
* @param iPort 端口号
* @param chMsgFile 文本信息文件名
* @param puiCHMsgLen
* @param wltFile 照片信息文件名(扩展名为wlt)
* @param puiPHMsgLen
* @param iIfOpen
* @return
*/
int SDT_ReadBaseMsgToFile(int iPort, String chMsgFile,
IntByReference puiCHMsgLen, String wltFile,
IntByReference puiPHMsgLen, int iIfOpen);

/**
* 读取追加信息
*
* @param iPort 端口号
* @param pucAppMsg 追加信息
* @param puiAppMsgLen
* @param iIfOpen
* @return
*/
int SDT_ReadNewAppMsg(int iPort, CharBuffer pucAppMsg,
IntByReference puiAppMsgLen, int iIfOpen);

/**
* 设置射频适配器最大通信字节数
*
* @param iPort
* @param ucByte
* @param iIfOpen
* @return
*/
int SDT_SetMaxRFByte(int iPort, String ucByte, int iIfOpen);
}

public interface Wltrs extends Library {
Wltrs INSTANCE = (Wltrs) Native.loadLibrary("WltRs", Wltrs.class);

/**
* 将wlt文件解码成bmp文件
*
* @param wltFile wlt文件名
* @param intf 阅读设备通讯接口类型(1—RS-232C,2—USB)
* @return
* 1 相片解码解码正确
* 0 调用sdtapi.dll错误
* -1 相片解码错误
* -2 wlt文件后缀错误
* -3 wlt文件打开错误
* -4 wlt文件格式错误
* -5 软件未授权
* -6 设备连接错误
*
* wlt文件的扩展名要固定为”.wlt”,如:xp.wlt,相片解码成xp.bmp
* 本方法要与sdtapi.dll关联使用,并确认通讯端口处于关闭状态;
*/
int GetBmp(String wltFile, int intf);
}

private static IDCardReader instance;

private int port;

private IDCardReader() {

}

public static IDCardReader getInstance() {
if (instance == null) {
instance = new IDCardReader();
}

return instance;
}

/**
* 以指定波特率打开串口
* @param port
* @param baudRate
* @return
*/
public boolean openPort(int port, int baudRate) {
int resultCode = -1;

// 取得串口当前波特率
IntByReference currBaudRate = new IntByReference();
resultCode = Sdtapi.INSTANCE.SDT_GetCOMBaud(port, currBaudRate);

if (resultCode == 144) {
// 设置串口波特率,如设置不成功将使用原波特率
resultCode = Sdtapi.INSTANCE.SDT_SetCOMBaud(port, currBaudRate.getValue(), baudRate);
}

// 打开串口
resultCode = Sdtapi.INSTANCE.SDT_OpenPort(port);

if (resultCode != 144) {
return false;
}

this.port = port;
return true;
}

/**
* 读卡
* @return
*/
public IDCardInfo readCard() {
int rtn = -1;

// 找卡
rtn = Sdtapi.INSTANCE.SDT_StartFindIDCard(port, new IntByReference(), 0);

if (rtn != 159) {
return null;
}

// 选卡
rtn = Sdtapi.INSTANCE.SDT_SelectIDCard(port, new IntByReference(), 0);

if (rtn != 144) {
return null;
}

// 读卡
CharBuffer cHMsg = CharBuffer.allocate(256); // 文字信息
ByteBuffer pHMsg = ByteBuffer.allocate(1024); // 照片信息
rtn = Sdtapi.INSTANCE.SDT_ReadBaseMsg(port, cHMsg, new IntByReference(), pHMsg, new IntByReference(), 0);

IDCardInfo idCardInfo = new IDCardInfo();
parseCHMsg(cHMsg.toString(), idCardInfo);
return idCardInfo;
}

/**
* 关闭端口
*/
public void closePort() {
Sdtapi.INSTANCE.SDT_ClosePort(port);
}

public static void main(String[] args) throws InterruptedException {
// 打开端口
IDCardReader idCardReader = IDCardReader.getInstance();
idCardReader.openPort(4, 115200); // 默认波特率为115200,一般不需要修改,速度最快

// 读卡
long starttime = System.currentTimeMillis();
System.out.println(idCardReader.readCard());
long endtime = System.currentTimeMillis();
System.out.println("time:" + (endtime - starttime));

// 关闭端口
idCardReader.closePort();
}

public static void test() {
// 取串口波特率
IntByReference pucBaudRate = new IntByReference();
System.out.println("GetCOMBaud:"+ Sdtapi.INSTANCE.SDT_GetCOMBaud(4, pucBaudRate));
System.out.println("pucBaudRate:" + pucBaudRate.getValue());

// 设置串口波特率
System.out.println("SetCOMBaud:" + Sdtapi.INSTANCE.SDT_SetCOMBaud(4, pucBaudRate.getValue(), 9600));

// 打开端口
System.out.println("OpenPort:" + Sdtapi.INSTANCE.SDT_OpenPort(4));

// 找卡
IntByReference pucManaInfo = new IntByReference();
System.out.println("StartFindIDCard:" + Sdtapi.INSTANCE.SDT_StartFindIDCard(4, pucManaInfo, 0));
System.out.println("pucManaInfo:" + pucManaInfo.getValue());

// 选卡
IntByReference pucManaMsg = new IntByReference();
System.out.println("SelectIDCard:" + Sdtapi.INSTANCE.SDT_SelectIDCard(4, pucManaMsg, 0));
System.out.println("pucManaMsg:" + pucManaMsg.getValue());

// 读卡
CharBuffer pucCHMsg = CharBuffer.allocate(256);
ByteBuffer pucPHMsg = ByteBuffer.allocate(1024);
IntByReference puiCHMsgLen = new IntByReference();
IntByReference puiPHMsgLen = new IntByReference();
System.out.println("ReadBaseMsg:" + Sdtapi.INSTANCE.SDT_ReadBaseMsg(4, pucCHMsg, puiCHMsgLen,
pucPHMsg, puiPHMsgLen, 0));
System.out.println("pucCHMsg:" + pucCHMsg + ",puiCHMsgLen:" + puiCHMsgLen.getValue());
System.out.println("身份证号:" + pucCHMsg.toString().substring(61,79));
System.out.println("pucPHMsg:" + pucPHMsg + ",puiPHMsgLen:" + puiPHMsgLen.getValue());

// 读卡并写文件
// System.out.println("ReadBaseMsgToFile:" + Sdtapi.INSTANCE.SDT_ReadBaseMsgToFile(4, "xx.txt", puiCHMsgLen,
// "xx.wlt", puiPHMsgLen, 1));

// 生成照片文件
writeFile("xx.wlt", pucPHMsg.array());
System.out.println("GetBmp:" + Wltrs.INSTANCE.GetBmp("xx.wlt", 2));

// 读取追加信息
// CharBuffer pucAppMsg = CharBuffer.allocate(40);
// IntByReference puiAppMsgLen = new IntByReference();
// System.out.println("ReadNewAppMsg:"+ Sdtapi.INSTANCE.SDT_ReadNewAppMsg(4, pucAppMsg, puiAppMsgLen, 1));
// System.out.println("pucAppMsg:" + pucAppMsg + ",puiAppMsgLen:" + puiAppMsgLen.getValue());

// 关闭端口
Sdtapi.INSTANCE.SDT_ClosePort(4);

}

private static void writeFile(String filename,byte[] content) {
File wltFile = new File(filename);
try {
FileOutputStream fo = new FileOutputStream(wltFile);
fo.write(content);
fo.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

/**
* 解析文字信息
* @param chMsg
* @param idCardInfo
*/
private void parseCHMsg(String chMsg, IDCardInfo idCardInfo) {
idCardInfo.setName(chMsg.substring(0, 15).trim());
idCardInfo.setGender(genderCode.get(chMsg.substring(15, 16)));
idCardInfo.setNation(nationCode.get(chMsg.substring(16, 18)));
idCardInfo.setBirthday(chMsg.substring(18, 26));
idCardInfo.setAddress(chMsg.substring(26, 61).trim());
idCardInfo.setCardNo(chMsg.substring(61, 79).trim());
idCardInfo.setGrantDept(chMsg.substring(79, 94).trim());
idCardInfo.setUsefulLifeBegin(chMsg.substring(94, 102));
idCardInfo.setUsefulLifeEnd(chMsg.substring(102, 110));
}

private HashMap<String, String> nationCode = new HashMap<String, String>();
{
nationCode.put("01", "汉族");
nationCode.put("02", "蒙古族");
nationCode.put("03", "回族");
nationCode.put("04", "藏族");
nationCode.put("05", "维吾尔族");
nationCode.put("06", "苗族");
nationCode.put("07", "彝族");
nationCode.put("08", "壮族");
nationCode.put("09", "布依族");
nationCode.put("10", "朝鲜族");
nationCode.put("11", "满族");
nationCode.put("12", "侗族");
nationCode.put("13", "瑶族");
nationCode.put("14", "白族");
nationCode.put("15", "土家族");
nationCode.put("16", "哈尼族");
nationCode.put("17", "哈萨克族");
nationCode.put("18", "傣族");
nationCode.put("19", "黎族");
nationCode.put("20", "傈僳族");
nationCode.put("21", "佤族");
nationCode.put("22", "畲族");
nationCode.put("23", "高山族");
nationCode.put("24", "拉祜族");
nationCode.put("25", "水族");
nationCode.put("26", "东乡族");
nationCode.put("27", "纳西族");
nationCode.put("28", "景颇族");
nationCode.put("29", "柯尔克孜族");
nationCode.put("30", "土族");
nationCode.put("31", "达翰尔族");
nationCode.put("32", "仫佬族");
nationCode.put("33", "羌族");
nationCode.put("34", "布朗族");
nationCode.put("35", "撒拉族");
nationCode.put("36", "毛南族");
nationCode.put("37", "仡佬族");
nationCode.put("38", "锡伯族");
nationCode.put("39", "阿昌族");
nationCode.put("40", "普米族");
nationCode.put("41", "塔吉克族");
nationCode.put("42", "怒族");
nationCode.put("43", "乌孜别克族");
nationCode.put("44", "俄罗斯族");
nationCode.put("45", "鄂温克族");
nationCode.put("46", "德昂族");
nationCode.put("47", "保安族");
nationCode.put("48", "裕固族");
nationCode.put("49", "京族");
nationCode.put("50", "塔塔尔族");
nationCode.put("51", "独龙族");
nationCode.put("52", "鄂伦春族");
nationCode.put("53", "赫哲族");
nationCode.put("54", "门巴族");
nationCode.put("55", "珞巴族");
nationCode.put("56", "基诺族");
nationCode.put("57", "其它");
nationCode.put("98", "外国人入籍");
}

private HashMap<String, String> genderCode = new HashMap<String, String>();
{
genderCode.put("0", "未知");
genderCode.put("1", "男");
genderCode.put("2", "女");
genderCode.put("9", "未说明");
}
}

/**
* 身份证信息
*
* @author sunjc
*
*/
public class IDCardInfo {
private String name;
private String gender;
private String nation;
private String birthday;
private String address;
private String cardNo;
private String grantDept;
private String usefulLifeBegin;
private String usefulLifeEnd;
private String newAddres;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public String getNation() {
return nation;
}

public void setNation(String nation) {
this.nation = nation;
}

public String getBirthday() {
return birthday;
}

public void setBirthday(String birthday) {
this.birthday = birthday;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

public String getCardNo() {
return cardNo;
}

public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}

public String getGrantDept() {
return grantDept;
}

public void setGrantDept(String grantDept) {
this.grantDept = grantDept;
}

public String getUsefulLifeBegin() {
return usefulLifeBegin;
}

public void setUsefulLifeBegin(String usefulLifeBegin) {
this.usefulLifeBegin = usefulLifeBegin;
}

public String getUsefulLifeEnd() {
return usefulLifeEnd;
}

public void setUsefulLifeEnd(String usefulLifeEnd) {
this.usefulLifeEnd = usefulLifeEnd;
}

public String getNewAddres() {
return newAddres;
}

public void setNewAddres(String newAddres) {
this.newAddres = newAddres;
}

@Override
public String toString() {
String cardInfo = name + "|" + gender + "|" + nation + "|" + birthday
+ "|" + address + "|" + cardNo + "|" + grantDept + "|"
+ usefulLifeBegin + "|" + usefulLifeEnd + "|" + newAddres;
return cardInfo;
}
}

分享到:
评论

相关推荐

    Java身份证阅读器实例(华旭)

    在Java开发中,有时我们需要与硬件设备如身份证阅读器进行交互,以便获取并处理身份证上的信息。本实例是关于如何使用Java与华旭品牌的身份证阅读器进行通信的实践案例。华旭身份证阅读器是一款常见的设备,它能快速...

    kotlin-身份证阅读器二次开发及相关驱动文件.zip

    总的来说,使用Kotlin进行身份证阅读器的二次开发涉及到的知识点包括:Kotlin语言基础、JNI或JNA的使用、原生库的调用、实体类的设计、异常处理、异步编程以及测试策略。理解并掌握这些知识点,将有助于你成功开发出...

    java使用jna连接华视CVR-100UC读卡器

    由于公司需要使用华视读卡器进行身份证读取,博主去华视官网找相关文档,但是官方只提供了Android相关的开发包,但是博主需要使用java桌面应用,完全不懂Android的有木有。好在官方提供了相关的dll文件已经文档,...

    CVR-100U二次开发,vb例子+源码

    通过二次开发,开发者可以将身份证读卡功能集成到自己的应用程序中,实现更加便捷和个性化的服务。 VB(Visual Basic)是微软公司推出的一种基于事件驱动的编程语言,以其易学易用的特点深受程序员喜爱。在本案例中...

    精伦IDR210产品通用二次开发包V3及实例.7z

    通用二次开发包V3.zip BC示例程序.zip C#(.net)示例程序.zip Delphi示例程序.zip java_jna.zip JAVA二次开发包(需和通用开发包一起使用).zip...可定制开发身份证阅读器B/S端,C/S端软件 部分项目已在github开源

    精伦IDR210通用二次开发包V4.0.0.7及实例.7z

    20160401_火狐及谷歌浏览器插件V2.0.1.1.zip BC.zip, C#.zip, Delphi示例程序.zip, Java_jna.zip, PowerBuiler.zip, VB.zip, VC.zip ...可定制开发身份证阅读器B/S端,C/S端软件 部分项目已在github开源

    cvr硬件驱动的开发

    4. **Java层接口**:使用JNI或JNA创建Java层的接口,使得Java应用程序可以调用驱动程序的功能。 5. **测试和调试**:编写测试用例,确保驱动程序能正常工作,同时对可能出现的问题进行调试。 6. **优化和维护**:...

    Java读取注册表所需的jar包和dll文件(32位和64位)

    通常,开发者会使用JNA(Java Native Access)库,它允许Java程序直接调用操作系统API,包括读写注册表。JNA库包括了处理不同平台间接口差异的能力,因此它支持32位和64位的系统。 1. **JNA库的使用**: - 首先,...

    java开发包说明.zip_Dll白卡开发_fewerlmd

    对于其他操作系统,例如MacOS或Linux,可能需要寻找不同的实现方式,如使用JNA(Java Native Access)或其他跨平台的硬件访问库。 7. **安全考虑**:直接在Web页面中进行白卡操作可能涉及安全风险,如数据泄露或...

    JAVA获取本地CPU编号 项目加源码

    JNA允许Java程序直接调用C语言级别的系统函数,如在Linux上使用`/proc/cpuinfo`文件,或者在Windows上使用`Kernel32`库的`GetSystemFirmwareTable`函数来获取CPU信息。 3. 利用第三方库: 还有一些Java库,如`...

    java ocr 图形识别

    8. **集成开发环境(IDE)支持**:开发Java OCR应用时,可以使用像Eclipse、IntelliJ IDEA或NetBeans这样的IDE,它们提供了丰富的代码编辑、调试和构建功能。 9. **性能优化**:由于OCR涉及大量的图像处理和计算,...

Global site tag (gtag.js) - Google Analytics