C#通过键盘钩子获取数据

前言

在做关于使用USB读卡器、扫码枪等项目的开发时,有可能需要通过键盘钩子去获取刷卡器、扫码枪扫描到的数据,当然,如果设备是串口、或者非系统独占设备的USB,可以采用串口通信、HID设备读写去获取数据。
本文是记录了C#如何通过键盘钩子去获取刷卡器读取上来的数据。

一、项目背景

需要获取刷卡器读到的卡号,然后根据卡号从客户的WebService中获取数据。所以第一步工作是如何获取卡号。

二、弯路

一开始拿到读卡器时老板告诉我,这个刷卡器时USB通信的,所以一开始我花了两三天时间去了解C#如何打开USB设备。已经通过CreateFile方法获取到了设备路径,但始终没有访问权限,所以后面的ReadFile一直无法使用。最后在一篇文章中得知,我拿到的刷卡器是系统独占设备,是无法访问的,所以只能换其他方式,最终各种搜索,决定用键盘钩子去抓取数卡器读到的数据。

三、键盘钩子工作原理

在刷卡器读到卡号时,实际上是键盘输入事件,比如我读到的卡号时123456,实际上就相当于我的键盘输入了7次,字符是“1 2 3 4 5 6 \r”,所以我只要监控键盘的输入,将“1 2 3 4 5 6 \r”字符串抓到就可以了。
但是如何区别是刷卡器读到的数据还是键盘打出来的字符串呢?这个就需要通过字符增加的时间间隔去区分,刷卡器刷卡得到数据的时间间隔是ms级别的,用键盘打出这些字符是不会这么快的,所以通过字符输入的间隔时间能够成功过滤键盘事件。

四、代码

1. 安装钩子

开始监控键盘事件。需要使用Windows API函数GetModuleHandle获取当前运行的应用程序句柄,Process.GetCurrentProcess().MainModule.ModuleName就是获取当前运行模块的名称。获取句柄后通过SetWindowsHookEx函数安装钩子,或者说开始监控键盘输入。
SetWindowsHookEx

  1. 第一个参数是钩子使用范围是全局的还是局部的、是否包括鼠标移动等等,具体可参考注释中的MSDN,我用的13是只监控键盘输入事件,不包括鼠标移动;
  2. 第二个参数是一个事件HookProc,用来获取键盘事件输入的字符,下一步会介绍具体实现方法;
  3. 第三个参数是当前运行模块的句柄;
  4. 第四个参数是:挂钩过程将与之关联的线程的标识符。为0是全局钩子,只要钩子程序开启,就开始监控键盘输入事件,如果不为0,表示监视指定的线程。
 delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);HookProc hookproc;//钩子过程的委托在设置钩子之后由GC收集,垃圾收集委托后,系统在尝试调用回调时崩溃,所以必须放在外面保证其必须处于活动状态,直到保证永远不会被调用public bool Start(){if (hKeyboardHook == 0){hookproc = new HookProc(KeyboardHookProc);IntPtr modulePtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);hKeyboardHook = SetWindowsHookEx(13, hookproc, modulePtr, 0);   }return (hKeyboardHook != 0);}
        /// <summary>/// 获取一个应用程序或动态链接库的模块句柄/// </summary>/// <param name="name"></param>/// <returns></returns>[DllImport("kernel32.dll")]public static extern IntPtr GetModuleHandle(string name);
 /// <summary>/// 安装钩子,参数设置详见https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-setwindowshookexa//// </summary>/// <param name="idHook"></param>/// <param name="lpfn"></param>/// <param name="hInstance"></param>/// <param name="threadId"></param>/// <returns></returns>[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

2. KeyboardHookProc事件

这个函数是用来解析并收集键盘输入的字符,在1中安装钩子后,第一次键盘输入事件会执行一次KeyboardHookProc,收集一次字符后继续下一个钩子CallNextHookEx,第二次键盘输入会继续执行该函数,直到所有字符都收集后组成一个字符串供后续使用。

 public struct KeyBoards
{public int VirtKey;      //虚拟码public int ScanCode;     //扫描码public string KeyName;   //键名public uint AscII;       //AscIIpublic char Chr;         //字符public bool IsValid;     //事件发生是否来自设备public string KeyBoard;   //条码信息public DateTime Time;    //扫描时间
}
private struct EventMsg
{public int message;public int paramL;public int paramH;public int Time;public int hwnd;
}public int ts { get; set; }//扫码间隔时间
public delegate void KeyBoardDelegate(KeyBoards KeyBoard);
public event KeyBoardDelegate KeyBoardEvent;
delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
event HookProc hookproc;//钩子过程的委托在设置钩子之后由GC收集,垃圾收集委托后,系统在尝试调用回调时崩溃,所以必须放在外面保证其必须处于活动状态,直到保证永远不会被调用
KeyBoards KeyBoard = new KeyBoards();
int hKeyboardHook = 0;
public string strKeyBoard = "";
public string strbarcoding { get; set; }
public int length;
public int KeyBoardNum = 0;//刷卡读到的第一个字符时,DateTime.Now.Subtract(KeyBoard.Time).TotalMilliseconds一定会大于300ms,如果不处理会过滤第一个字符,所以第一个要被保留private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{KeyBoardNum++;bool notChar = false;KeyBoard.IsValid = true;if (nCode == 0){EventMsg msg = (EventMsg)Marshal.PtrToStructure(lParam, typeof(EventMsg));if (wParam == 0x100){KeyBoard.VirtKey = msg.message & 0xff;  //虚拟码KeyBoard.ScanCode = msg.paramL & 0xff;  //扫描码,有可能是鼠标键盘按键,有可能是HID设备发送过来的字符StringBuilder strKeyName = new StringBuilder(255);if (GetKeyNameText(KeyBoard.ScanCode * 65536, strKeyName, 255) > 0)//将接收到的扫描码转化成实际字符{KeyBoard.KeyName = strKeyName.ToString().Trim(new char[] { ' ', '\0' });}else{KeyBoard.KeyName = "";}byte[] kbArray = new byte[256];uint uKey = 0;GetKeyboardState(kbArray);if (ToAscii(KeyBoard.VirtKey, KeyBoard.ScanCode, kbArray, ref uKey, 0)){KeyBoard.AscII = uKey;KeyBoard.Chr = Convert.ToChar(uKey);//真正获取到的字符,要保存}else{notChar = true;   //转到ascii字符失败,这不是一个正常字符,要去掉}if (DateTime.Now.Subtract(KeyBoard.Time).TotalMilliseconds > ts)  //40ms可以过键盘输入{KeyBoard.IsValid = false;if ((KeyBoardNum - 1) % 11 == 0){KeyBoardNum = 1;KeyBoard.IsValid = true;}if (notChar == false)strKeyBoard = KeyBoard.Chr.ToString();elsestrKeyBoard = "";}else{KeyBoard.IsValid = true;if (notChar == false){// strKeyBoard += KeyBoard.Chr.ToString();}KeyBoard.KeyBoard = strKeyBoard;strbarcoding = strKeyBoard;}KeyBoard.Time = DateTime.Now;if (KeyBoardEvent != null && KeyBoard.IsValid) KeyBoardEvent(KeyBoard);    //触发KeyBoardEvent事件,将数据收集}}return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}/// <summary>/// 继续下一个钩子/// </summary>/// <param name="idHook"></param>/// <param name="nCode"></param>/// <param name="wParam"></param>/// <param name="lParam"></param>/// <returns></returns>[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]private static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);/// <summary>/// 检取表示键名的字符串,(键盘输入后,将键盘翻译成真正字符)/// </summary>/// <param name="lParam">指定被处理的键盘消息</param>/// <param name="lpBuffer">指向接受键名的缓冲区的指针</param>/// <param name="nSize">指定键名的最大字符长度,包括空结束符</param>/// <returns></returns>[DllImport("user32", EntryPoint = "GetKeyNameText")]private static extern int GetKeyNameText(int lParam, StringBuilder lpBuffer, int nSize);/// <summary>/// 获取虚拟键状态,将虚拟键的状态拷贝到缓冲区 /// </summary>/// <param name="pbKeyState"></param>/// <returns></returns>[DllImport("user32", EntryPoint = "GetKeyboardState")]private static extern int GetKeyboardState(byte[] pbKeyState);/// <summary>/// 将虚拟码和键盘状态翻译为相应的字符和字符串/// </summary>/// <param name="VirtualKey"></param>/// <param name="ScanCode"></param>/// <param name="lpKeyState"></param>/// <param name="lpChar"></param>/// <param name="uFlags"></param>/// <returns></returns>[DllImport("user32", EntryPoint = "ToAscii")]private static extern bool ToAscii(int VirtualKey, int ScanCode, byte[] lpKeyState, ref uint lpChar, int uFlags);/// <summary>/// 获取一个应用程序或动态链接库的模块句柄/// </summary>/// <param name="name"></param>/// <returns></returns>[DllImport("kernel32.dll")]public static extern IntPtr GetModuleHandle(string name);

KeyBoardEvent收集按键事件获取的字符,所以它的处理是需要将这些字符加起来成为一个字符串,就是我们刷卡后最终得到的卡号。

       List<string> codes = new List<string>();/// <summary>/// 刷卡后,触发该事件,codes为刷卡机数据收集器/// </summary>/// <param name="barcode"></param>void KeyBoardCollect(KeyBoardHook.KeyBoards barcode){codes.Add(barcode.Chr.ToString());}

注册KeyBoardEvent

KeyBoardEvent += KeyBoardCollect;

3. 卸载钩子

当不再使用钩子时即可卸载钩子。

public bool Stop()
{if (hKeyboardHook != 0){bool result = UnhookWindowsHookEx(hKeyboardHook);hKeyboardHook = 0;         //将hKeyboardHook 置为0return result;}else{return false;}
}/// <summary>/// 卸载钩子/// </summary>/// <param name="idHook"></param>/// <returns></returns>[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]private static extern bool UnhookWindowsHookEx(int idHook);

总结

钩子是应用程序获取Windows消息的一个常用技术,在实际使用时还是要根据项目设置需要的钩子,全局、局部、监视特殊按键与否、鼠标事件等等。在本项目中的使用中,除了需要注意API函数的使用外,还需要注意如何正确获取字符并组成字符串。

C#通过键盘钩子获取数据相关推荐

  1. 使用扫码枪(二维码,条码)使用键盘钩子获取扫码数据

    1.扫描枪获取数据原理基本相当于键盘数据,获取扫描枪扫描出来的数据,一般分为两种实现方式. a)文本框输入获取焦点,扫描后自动显示在文本框内. b)使用键盘钩子,勾取扫描枪虚拟按键,根据按键频率进行手 ...

  2. 我的全局键盘钩子(KeyBoardHook)

    1,钩子操作类 public class KeyPressSender{[StructLayout(LayoutKind.Sequential)]public class KeyBoardHookSt ...

  3. python 键盘输入int_Python编程 Python如何获取数据

    计算机的基本功能就是接受输入的数据,处理后再输出结果.print( )函数的功能是输出数据,那么Python如何获取输入的数据呢?在使用图形界面之前,Python主要从键盘获取数据,而从键盘获取数据方 ...

  4. java从键盘获取数据_java实现从键盘获取数据的方法

    java实现从键盘获取数据的方法 发布时间:2020-06-25 15:42:06 来源:亿速云 阅读:83 作者:Leah 这期内容当中小编将会给大家带来有关java实现从键盘获取数据的方法,文章内 ...

  5. C++ 监视用户输入的数据(键盘钩子)代码及详解

    有关利用C++设置键盘钩子的代码,基本上都是基于一个窗口程序,其实控制台窗口也能够实现,我们不需要太多的修改即可实现. 在SetWindowsHookEx()函数中的第一个参数我们要设置成 WH_KE ...

  6. 键盘钩子,游戏外挂基础

    网上有很多外挂制作的教程,大多是讲针对大型网络游戏的,主要包含一些抓包.反汇编.C++的知识综合.事实也如此,常见的外挂都是使用VC++写的,从来没有过C#或者其他.NET语言编写的外挂. 作为微软. ...

  7. 自己写一个键盘钩子程序来监视键盘输入

    "钩子是Windows的消息监视点,应用程序可以在这里安装一个监视子程序,这样就可以在系统中的消息流到达目的窗口过程前监控它们" 上面就是WIN32API手册中对钩子的描述.大概就 ...

  8. 钩子原理及实例:实现键盘钩子截获密码

    钩子原理及实例:利用鼠标键盘钩子截获密码 钩子原理 钩子能截获系统并得理发送给其它应用程序的消息,能完成一般程序无法完成的功能.Windows系统是建立在事件驱动的机制上的,也就是整个系统都是通过消息 ...

  9. [C++]键盘钩子程序

    实现适时监视键盘,并将按键信息保存在TXT文件中的程序 Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的.而钩子是Windows系统中非常重要的系统接口,用它 ...

最新文章

  1. postgres sql 多表联合查询_从零学会SQL-多表查询
  2. python文件输入和输出
  3. jetty 切换log4j日志接口
  4. 【java读书笔记】——java的异常处理
  5. Laravel -- Excel 导入(import) (v2.1.0)
  6. struts读常量顺序
  7. 一个学习Python的好链接
  8. 计算机应用基础实例,计算机应用基础案例教程(Windows 7+Office 2010)
  9. ListView优化的
  10. BitCoin p2p通信过程
  11. 这是一份 AI 界最强年终总结
  12. HR最不认同的5大跳槽理由排行榜
  13. 7-2 Binomial Queue
  14. 实现Typora多端同步
  15. 媳妇儿喜欢玩某音中的动漫特效,那我就用python做一个图片转化软件。
  16. IT 外包中的甲方乙方,德国人,美国人,印度人和日本人印象杂谈
  17. 【转】知识图谱上推荐推理的模仿学习框架
  18. $Self~Problem~C~:~Samsara$
  19. 02-pandas数据分析库
  20. 37本国产SCI期刊推荐!涵盖9大领域,建议收藏!①

热门文章

  1. java测试单个方法 @Test
  2. 网站永久换域名的处理过程
  3. APP开发所需时间,看完这些你就懂了
  4. snap7-c++/MFC开发笔记
  5. 除权除息日为:2015年5月21日 股票一览
  6. linux qq远程桌面连接,还在用QQ远程桌面?这款软件还能用手机控制电脑!
  7. 【IOS】IOS开发问题解决方法索引(三)
  8. 高光谱图像分类--HybridSN: Exploring 3-D–2-DCNN Feature Hierarchy for Hyperspectral Image Classification
  9. 白牛:一半是磨练,一半是成长
  10. jieba 同义词_中文分词库FNLP与jieba的安装与使用