C#使用LibUsbDotNet实现USB读取刷卡机数据
剧情概要,刷卡机没有SDK和驱动,刷卡机连接电脑用的是USB。(以后没有开发文档的刷卡机还是绕开)
首先要安装驱动,解决电脑识别USB并接收它的输入,此时的USB接收刷卡机输入的数据,和键盘键入字符是一个道理
最后,生成一个钩子获取USB输入的数据,处理业务。
1. 下载并安装 LibUsbDotNet
2. 插入USB连接读卡机,安装 LibUsbDotNet 软件
继续
继续
继续
这里是安装和卸载
快捷键 windows+X 选择 设备管理器,我们不知道插入的USB是哪一个设备,可以多插拔几次,就知道是哪一个设备,也可以在事件的时间上区分。
当然电脑品牌不一样可能存在差异:
联想笔记本配置:
戴尔笔记本配置:
知道是哪一个设备,然后继续libusb 驱动安装,注意是输入设备
3.编写 C# 代码 ,首先需要引入Nuget包 LibUsbDotNet
using System; using System.Threading; using JsonDataOpeartion; using LibUsbDotNet; using LibUsbDotNet.Main; using Sunny.UI; namespace UpperMaterial.Common { class LibUSB : IDisposable { private UsbDevice usbDevice; private bool isDisposed; ScanerHook scanerHook; public LibUSB() { isDisposed = false; } //这里的PID 和 VID 是我们在设备管理器 看到的 硬件参数 VID_C216和PID_0709,硬件参数是16进制,需要转换成10进制。 public void OpenUSB(int vid, int pid) { UsbDeviceFinder usbFinder = new UsbDeviceFinder(vid, pid); usbDevice = UsbDevice.OpenUsbDevice(usbFinder); if (usbDevice == null) { // 多次尝试连接USB设备 int count = 0; while (count < 4 && usbDevice == null) { Thread.Sleep(100); usbDevice = UsbDevice.OpenUsbDevice(usbFinder); count++; } } if (usbDevice == null) { Console.WriteLine("打开USB设备失败"); } else { //监听usb输入,和键盘输入一个道理 if (null == scanerHook) { scanerHook = new ScanerHook(); } // 注册事件处理程序 scanerHook.ScanerEvent += Listener_ScanerEvent; // 启动键盘钩子 if (scanerHook.Start()) { HistoryHelper.SaveSystemLog("USB键盘钩子已启动,等待扫描器输入..."); } else { HistoryHelper.SaveErrorLog("启动USB键盘钩子失败"); } } } private void Listener_ScanerEvent(ScanerHook.ScanerCodes codes) { string result = codes.Result; ProcessData(result); } public void Close() { if (IsOpen()) { IUsbDevice wholeUsbDevice = usbDevice as IUsbDevice; if (wholeUsbDevice != null) { wholeUsbDevice.ReleaseInterface(0); } usbDevice.Close(); usbDevice = null; } UsbDevice.Exit(); } public bool IsOpen() { return usbDevice != null && usbDevice.IsOpen; } private void ProcessData(string hexReverseCode) { if (null == hexReverseCode || hexReverseCode == "") { return; } try { //ABA 反码解析 // 在这里处理读取到的数据 93F12417 // 解决手抖多次刷卡,如果 hexReverseCode 超过8位,只取前8位 // 提取 16 进制反码 hexReverseCode = hexReverseCode.Substring(0, 8); HistoryHelper.SaveSystemLog($"读取到刷卡卡数据: {hexReverseCode}"); // 将 16 进制反码转换为 16 进制正码 string hexNormalCode = ReverseHex(hexReverseCode); // 将 16 进制正码转换为 10 进制正码 long decimalNormalCode = Convert.ToInt64(hexNormalCode, 16); string actualCardNumber = decimalNormalCode.ToString("D10"); // 保留 10 位 // 输出实际卡号 0388297107 HistoryHelper.SaveSystemLog("解析刷卡卡号: " + actualCardNumber); //UIMessageBox.Show("解析刷卡卡号: " + actualCardNumber); bool result = false; OpcCode opcCode = OpcCode.none; //根据当前的操作指令 和员工编号查询权限 if (PublicValue.CurrentOpcCode != OpcCode.none) { opcCode = PublicValue.CurrentOpcCode; } //先刷卡再操作,刷卡前不知道操作什么,因为报警服务前会有提示排除, //未登录则执行登录,登录过则执行查询修改权限 else { if (!PublicValue.LoginState) { opcCode = OpcCode.login; } else { opcCode = OpcCode.alter; } } result = RequestHelper.JsonSendData(PublicValue.EQname, actualCardNumber.ToString(), opcCode); //如果是刷卡登录,则需要插入登录记录数据 if (result && PublicValue.CurrentOpcCode == OpcCode.login) { RequestHelper.InsertLoginInfo(); PublicValue.LoginJurisdiction = false; } PublicValue.CurrentOpcCode = OpcCode.none; } catch (Exception ex) { string msg = "读卡器处理业务出现异常:" + ex.Message; HistoryHelper.SaveErrorLog(msg); UIMessageBox.ShowError(msg); } } /// <summary> /// 反转 16 进制字符串 /// </summary> /// <param name="hexString"></param> /// <returns></returns> private static string ReverseHex(string hexString) { // 将字符串转换为字节数组 byte[] bytes = new byte[hexString.Length / 2]; for (int i = 0; i < bytes.Length; i++) { bytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16); } // 反转字节数组 Array.Reverse(bytes); // 将字节数组转换回 16 进制字符串 string reversedHex = BitConverter.ToString(bytes).Replace("-", "").ToLower(); return reversedHex; } public void Dispose() { if (!isDisposed) { Close(); isDisposed = true; } if (null != scanerHook) { // 停止键盘钩子 scanerHook.Stop(); } } } }
4.编写钩子,读取USB输入数据
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Diagnostics; namespace UpperMaterial.Common { public class ScanerHook { public delegate void ScanerDelegate(ScanerCodes codes); public event ScanerDelegate ScanerEvent; delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); private int hKeyboardHook = 0; private ScanerCodes codes = new ScanerCodes(); private HookProc hookproc; [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] private static extern bool UnhookWindowsHookEx(int idHook); [DllImport("user32", EntryPoint = "GetKeyNameText")] private static extern int GetKeyNameText(int IParam, StringBuilder lpBuffer, int nSize); [DllImport("user32", EntryPoint = "GetKeyboardState")] private static extern int GetKeyboardState(byte[] pbKeyState); [DllImport("user32", EntryPoint = "ToAscii")] private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeySate, ref uint lpChar, int uFlags); [DllImport("kernel32.dll")] public static extern IntPtr GetModuleHandle(string name); public ScanerHook() { } public bool Start() { if (hKeyboardHook == 0) { hookproc = new HookProc(KeyboardHookProc); //GetModuleHandle 函数 替代 Marshal.GetHINSTANCE //防止在 framework4.0中 注册钩子不成功 IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName); //WH_KEYBOARD_LL=13 //全局钩子 WH_KEYBOARD_LL // hKeyboardHook = SetWindowsHookEx(13, hookproc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0); hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0); } return (hKeyboardHook != 0); } public bool Stop() { if (hKeyboardHook != 0) { return UnhookWindowsHookEx(hKeyboardHook); } return true; } private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) { EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg)); codes.Add(msg); if (ScanerEvent != null && msg.message == 13 && msg.paramH > 0 && !string.IsNullOrEmpty(codes.Result)) { ScanerEvent(codes); } return 0; } public class ScanerCodes { private int ts = 300; // 指定输入间隔为300毫秒以内时为连续输入 private List<List<EventMsg>> _keys = new List<List<EventMsg>>(); private List<int> _keydown = new List<int>(); // 保存组合键状态 private List<string> _result = new List<string>(); // 返回结果集 private DateTime _last = DateTime.Now; private byte[] _state = new byte[256]; private string _key = string.Empty; private string _cur = string.Empty; public EventMsg Event { get { if (_keys.Count == 0) { return new EventMsg(); } else { return _keys[_keys.Count - 1][_keys[_keys.Count - 1].Count - 1]; } } } public List<int> KeyDowns { get { return _keydown; } } public DateTime LastInput { get { return _last; } } public byte[] KeyboardState { get { return _state; } } public int KeyDownCount { get { return _keydown.Count; } } public string Result { get { if (_result.Count > 0) { return _result[_result.Count - 1].Trim(); } else { return null; } } } public string CurrentKey { get { return _key; } } public string CurrentChar { get { return _cur; } } public bool IsShift { get { return _keydown.Contains(160); } } public void Add(EventMsg msg) { #region 记录按键信息 // 首次按下按键 if (_keys.Count == 0) { _keys.Add(new List<EventMsg>()); _keys[0].Add(msg); _result.Add(string.Empty); } // 未释放其他按键时按下按键 else if (_keydown.Count > 0) { _keys[_keys.Count - 1].Add(msg); } // 单位时间内按下按键 else if (((TimeSpan)(DateTime.Now - _last)).TotalMilliseconds < ts) { _keys[_keys.Count - 1].Add(msg); } // 从新记录输入内容 else { _keys.Add(new List<EventMsg>()); _keys[_keys.Count - 1].Add(msg); _result.Add(string.Empty); } #endregion _last = DateTime.Now; #region 获取键盘状态 // 记录正在按下的按键 if (msg.paramH == 0 && !_keydown.Contains(msg.message)) { _keydown.Add(msg.message); } // 清除已松开的按键 if (msg.paramH > 0 && _keydown.Contains(msg.message)) { _keydown.Remove(msg.message); } #endregion #region 计算按键信息 int v = msg.message & 0xff; int c = msg.paramL & 0xff; StringBuilder strKeyName = new StringBuilder(500); if (GetKeyNameText(c * 65536, strKeyName, 255) > 0) { _key = strKeyName.ToString().Trim(new char[] { ' ', '\0' }); GetKeyboardState(_state); if (_key.Length == 1 && msg.paramH == 0) { // 根据键盘状态和shift缓存判断输出字符 _cur = ShiftChar(_key, IsShift, _state).ToString(); _result[_result.Count - 1] += _cur; } else { _cur = string.Empty; } } #endregion } private char ShiftChar(string k, bool isShiftDown, byte[] state) { bool capslock = state[0x14] == 1; bool numlock = state[0x90] == 1; bool scrolllock = state[0x91] == 1; bool shiftdown = state[0xa0] == 1; char chr = (capslock ? k.ToUpper() : k.ToLower()).ToCharArray()[0]; if (isShiftDown) { if (chr >= 'a' && chr <= 'z') { chr = (char)((int)chr - 32); } else if (chr >= 'A' && chr <= 'Z') { chr = (char)((int)chr + 32); } else { string s = "`1234567890-=[];',./"; string u = "~!@#$%^&*()_+{}:\"<>?"; if (s.IndexOf(chr) >= 0) { return (u.ToCharArray())[s.IndexOf(chr)]; } } } return chr; } } public struct EventMsg { public int message; public int paramL; public int paramH; public int Time; public int hwnd; } } }