用JNA開發身份證閱讀程序

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;
}
}

 

發佈了159 篇原創文章 · 獲贊 55 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章