最近在做一个项目,其中涉及到身份证的读取问题,用的是新中二代身份证的读卡器,厂商有发送过来DLL,可是一调用,开始时读取信息很成功,但是,没几次程序就崩毁了,有时候直接就关闭,有时候就跳出错误提示:尝试读取或写入受保护的内存。这通常指示其他内存已损坏,出错的函数是Syn_ReadMsg,
原型:
[DllImport("SynIDCardAPI.dll", EntryPoint = "Syn_ReadMsg", CharSet = CharSet.Ansi)]
public static extern int Syn_ReadMsg(int iPortID, int iIfOpen, ref IDCardData pIDCardData);
被这个几乎要弄到吐血,网上搜答案看到很多同学都遇到过这样的问题。给我启发最大的是一个说New 一个Image,但是Syn_ReadMsg()里面没有new Image 的啊,百思不得其解。,然后继续找,最后有一个方案说是用Syn_ReadBaseMsg代替Syn_ReadMsg,我用这个方案,果然不会报错。
附上源码:这个是使用Syn_ReadBaseMsg来读取身份证信息,读取到的信息文字跟图片是分别存放在两个指针里的,如果要获取图片信息,得另行处理。如果只是仅仅想读取身份证的文字信息的话,可以使用下面的函数
int port;//端口号 string baut;//波特率 string passporInfo;//读身份证后返回的信息 /// <summary> /// 获取身份证信息 /// </summary> public string GetPassPortInfo() { passporInfo = null; #region 自动寻找读卡器 int i, nRet; uint[] iBaud = new uint[1]; i = Syn_FindReader(); //自动寻找读卡器 返回值:0未找到 其他 串口1~16 USB:1001~1016 if (i > 0) { port = i; if (port < 1000) { System.Threading.Thread.Sleep(200); nRet = Syn_GetCOMBaud(port, ref iBaud[0]);//该函数只用于SAM采用RS232串口的情形,如果采用USB接口则不支持该API,此处端口号必须为1-16,表示串口。 baut = Convert.ToString(iBaud[0]); } } else { //stmp = Convert.ToString(System.DateTime.Now) + " 没有找到身份证读卡器"; // MessageBox.Show(stmp); return; } #endregion IDCardData CardMsg = new IDCardData(); int Ret; byte[] pucIIN = new byte[4]; byte[] pucSN = new byte[8]; string errStr; if (Syn_OpenPort(port) == 0) { if (Syn_SetMaxRFByte(port, 80, 0) == 0) { Ret = Syn_StartFindIDCard(port, ref pucIIN[0], 0); //开始找卡 iIfOpen:0表示不在该函数内部打开和关闭串口,此时确保之前调用了Syn_OpenPort来打开端口 Ret = Syn_SelectIDCard(port, ref pucSN[0], 0);//选卡 string cardMsg = new string(' ', 256); //身份证基本信息返回长度为256 string imgMsg = new string(' ', 1024); //身份证图片信息返回长度为1024 IntPtr msg = Marshal.StringToHGlobalAnsi(cardMsg); //无符号字符指针,指向读到的文字信息。 uint mLen = 0; // 无符号整型数指针,指向读到的文字信息长度。 IntPtr img = Marshal.StringToHGlobalAnsi(imgMsg); //身份证图片 uint iLen = 0;//无符号整型数指针,指向读到的照片信息长度。 try { Ret = Syn_ReadBaseMsg(port, msg, ref mLen, img, ref iLen, 0); if (Ret == 0) { string card = Marshal.PtrToStringUni(msg); //MessageBox.Show(card); char[] cartb = card.ToCharArray(); CardMsg.Name = (new string(cartb, 0, 15)).Trim(); CardMsg.Sex = new string(cartb, 15, 1); CardMsg.Nation = new string(cartb, 16, 2); CardMsg.Born = new string(cartb, 18, 8); CardMsg.Address = (new string(cartb, 26, 35)).Trim(); CardMsg.IDCardNo = new string(cartb, 61, 18); CardMsg.GrantDept = (new string(cartb, 79, 15)).Trim(); CardMsg.UserLifeBegin = new string(cartb, 94, 8); CardMsg.UserLifeEnd = new string(cartb, 102, 8); } else { //MessageBox.Show("读取信息失败"); return; } } catch (Exception e) { errStr = e.Message; } finally { Marshal.FreeHGlobal(msg); Marshal.FreeHGlobal(img); } } else { return ; } } else { // MessageBox.Show("打开端口失败"); return ; } }虽然用Syn_ReadBaseMsg不会报错,但是没过多久新的问题又来了,因为后来又提出了新的需求,那就是需要获得身份证的图片信息,因为需要鉴定是不是本人。Syn_ReadBaseMsg获得的信息,文字跟图片是分开的,,返回的图片指针,得自己处理,好吧,我又跑去处理了,可是,可是,竟然给我出来一推错误(其实我也不知道为什么是错误,总之从哪个指针里读想转化为图片转化不了,还想过先将指针转化为二进制再由二进制转化为Image,还尝试过用OPenCV里面的将指针转化为Image的功能,还是不行),当我使用 Syn_GetBmp这个函数想要获得图片的时候,好吧,我无语了,又出现了“尝试读取或写入受保护的内存。这通常指示其他内存已损坏”这个错误提示。
折腾了半天,好吧,我再去看看Syn_ReadMsg这个函数,这个函数能够直接就获得图片,如果他不会崩溃或者跳出 尝试读取或写入受保护的内存。这通常指示其他内存已损坏这个错误,那么就很符合我的要求。可是为什么一直会出现尝试读取或写入受保护的内存。这通常指示其他内存已损坏这个错误呢,折腾了很久,估计是这样的原因:程序第一次读到了身份证的图片信息,便将其命名并放到指定文件夹(新中的命名方式有 四种)当第二次读取同一张身份证的时候,又会以同样的名称方式存放到同样的文件夹下,因为同名,所以第二次读到的图片需要覆盖第一次的图片,但是第一次读到的图片可能还没被释放,这时,错误就出现了。
怎么解决这个问题呢,我想了一个很笨的方法,每次读取到的图片放在不同的文件夹,这样就不用覆盖上一次读到的图片,我拖了一个timer让它自己读了一百次,没什么问题,估计后面再多读应该也没什么问题的吧。
直接附上源码:
/// <summary> /// 设置身份证的保存位置 /// </summary> /// <param name="photoPath"></param> /// <returns></returns> public bool SetPhotoInfo(string photoPath) { int path, name; byte[] cPath = new byte[255]; if (!System.IO.Directory.Exists(photoPath)) System.IO.Directory.CreateDirectory(photoPath);//如果目录不存在,创建目录 cPath = System.Text.Encoding.Default.GetBytes(photoPath); path = Syn_SetPhotoPath(2, ref cPath[0]);//设置照片文件指定的存储的路径 name = Syn_SetPhotoName(3); //设置照片文件的文件名:姓名_身份证号 if (path == 0 && name == 0) { return true; } else { return false; } } /// <summary> /// 获取身份证信息 /// </summary> /// <param name="photoPath">身份证图片保存位置</param> /// <param name="CardMsg">读取到的身份证信息</param> /// <returns></returns> public bool GetPassPortInfo(string photoPath, out IDCardData CardMsg) { CardMsg = new IDCardData(); bool flag = false; int port; int i; uint[] iBaud = new uint[1]; i = Syn_FindReader(); if (i > 0) { port = i; } else { return false; } if (!SetPhotoInfo(photoPath)) { return false; } int nRet; byte[] pucIIN = new byte[4]; byte[] pucSN = new byte[8]; if (Syn_OpenPort(port) == 0) { if (Syn_SetMaxRFByte(port, 80, 0) == 0) { nRet = Syn_StartFindIDCard(port, ref pucIIN[0], 0); nRet = Syn_SelectIDCard(port, ref pucSN[0], 0); nRet = Syn_ReadMsg(port, 0, ref CardMsg); if (nRet == 0) { flag = true; } } } return flag; }总结:这个问题折腾了我很久,其实现在想想原因还是明了的,另外我做测试的时候,一直都是拿一张身份证在反复地读(我比较懒,对于要重复跑的程序都是拖个时间控件让他自己跑),这样反复的读同一张身份证,那么一只获取到的是同样命名放在同样路径下的图片,那么就会出现有可能同一张图片要覆盖正在使用的图片,当然会报错(就像你想删除一个正在打开的文件。直接拿新中二代身份证读卡器配套的软件来读同一张身份证,读了十几次,那个软件也自己崩溃掉了,汗。),我猜想如果用不同的身份证做测试,命名不一样,估计Syn_ReadMsg这个函数就不会有错误了(话说我也没有那么多身份证拿来测试,这仅是我的猜想啊)。