学习之路三十八:Hook(钩子)的学习

好久没写文章了,还记得年前面试了一家公司,为了检测一下我的学习能力,给了我一个任务,做一个自动登录并自动操作菜单的程序。

花了几天的时间研究了Hook以及使用WindowsAPI操作程序的知识,现在记录一下,也算是一次温习。

一丶Hook

  在我看来Hook就是监测用户操作键盘(或虚拟键盘)以及鼠标的行为,对于Hook的理解我也不是很深入,也只是一点皮毛。

  1. 实现Hook的步骤

    ①安装钩子

    ②监测键盘和鼠标的操作,用来实现相应的逻辑

    ③卸载钩子

  2.安装钩子

    钩子分两种:键盘钩子和鼠标钩子,而每一种钩子又可以分为全局钩子或局部勾子。

    下面是安装钩子需要的Windows Message常量(网上找的)。

  1     public enum HookType : int
  2     {
  3         /// <summary>
  4         /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 
  5         ///条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 
  6         ///WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 
  7         ///过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook 
  8         ///监视所有应用程序消息。 
  9         /// 
 10         ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 
 11         ///过滤消息,这等价于在主消息循环中过滤消息。 
 12         ///    
 13         ///通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 
 14         ///个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 
 15         ///环里一样
 16         /// </summary>
 17         WH_MSGFILTER = -1,
 18         /// <summary>
 19         /// WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这 
 20         ///个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook 
 21         ///来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样 
 22         ///使用。WH_JOURNALRECORD是system-wide local hooks,它们不会被注射到任何行 
 23         ///程地址空间
 24         /// </summary>
 25         WH_JOURNALRECORD = 0,
 26         /// <summary>
 27         /// WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可 
 28         ///以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠 
 29         ///标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘 
 30         ///事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定 
 31         ///Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处 
 32         ///理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实 
 33         ///时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它们不会被 
 34         ///注射到任何行程地址空间
 35         /// </summary>
 36         WH_JOURNALPLAYBACK = 1,
 37         /// <summary>
 38         /// 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and  
 39         ///WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使 
 40         ///用这个Hook来监视输入到消息队列中的键盘消息
 41         /// </summary>
 42         WH_KEYBOARD = 2,
 43         /// <summary>
 44         /// 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函 
 45         ///数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及 
 46         ///其它发送到消息队列中的消息
 47         /// </summary>
 48         WH_GETMESSAGE = 3,
 49         /// <summary>
 50         /// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之前调用
 51         /// </summary>
 52         WH_CALLWNDPROC = 4,
 53         /// <summary>
 54         /// 在以下事件之前,系统都会调用WH_CBT Hook子过程,这些事件包括: 
 55         ///1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 
 56         ///2. 完成系统指令; 
 57         ///3. 来自系统消息队列中的移动鼠标,键盘事件; 
 58         ///4. 设置输入焦点事件; 
 59         ///5. 同步系统消息队列事件。
 60         ///Hook子过程的返回值确定系统是否允许或者防止这些操作中的一个
 61         /// </summary>
 62         WH_CBT = 5,
 63         /// <summary>
 64         /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 
 65         ///条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 
 66         ///WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 
 67         ///过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook 
 68         ///监视所有应用程序消息。 
 69         /// 
 70         ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 
 71         ///过滤消息,这等价于在主消息循环中过滤消息。 
 72         ///    
 73         ///通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 
 74         ///个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 
 75         ///环里一样
 76         /// </summary>
 77         WH_SYSMSGFILTER = 6,
 78         /// <summary>
 79         /// WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。 
 80         ///使用这个Hook监视输入到消息队列中的鼠标消息
 81         /// </summary>
 82         WH_MOUSE = 7,
 83         /// <summary>
 84         /// 当调用GetMessage 或 PeekMessage 来从消息队列种查询非鼠标、键盘消息时
 85         /// </summary>
 86         WH_HARDWARE = 8,
 87         /// <summary>
 88         /// 在系统调用系统中与其它Hook关联的Hook子过程之前,系统会调用 
 89         ///WH_DEBUG Hook子过程。你可以使用这个Hook来决定是否允许系统调用与其它 
 90         ///Hook关联的Hook子过程
 91         /// </summary>
 92         WH_DEBUG = 9,
 93         /// <summary>
 94         /// 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是 
 95         ///激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子过程。 
 96         ///WH_SHELL 共有5钟情况: 
 97         ///1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁; 
 98         ///2. 当Taskbar需要重画某个按钮; 
 99         ///3. 当系统需要显示关于Taskbar的一个程序的最小化形式; 
100         ///4. 当目前的键盘布局状态改变; 
101         ///5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。 
102         ///
103         ///按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接 
104         ///收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自 
105         ///106         /// </summary>
107         WH_SHELL = 10,
108         /// <summary>
109         /// 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE  
110         ///Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就 
111         ///会调用WH_FOREGROUNDIDLE Hook子过程
112         /// </summary>
113         WH_FOREGROUNDIDLE = 11,
114         /// <summary>
115         /// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之后调用
116         /// </summary>
117         WH_CALLWNDPROCRET = 12,
118         /// <summary>
119         /// 监视输入到线程消息队列中的键盘消息
120         /// </summary>
121         WH_KEYBOARD_LL = 13,
122         /// <summary>
123         /// 监视输入到线程消息队列中的鼠标消息
124         /// </summary>
125         WH_MOUSE_LL = 14
126     }
View Code

     而用的最多的也就是:WH_KEYBOARD,WH_MOUSE, WH_KEYBOARD_LL,WH_MOUSE_LL。

     WH_KEYBOARD和WH_MOUSE是全局钩子,而WH_KEYBOARD_LL和WH_MOUSE_LL是针对某个线程的。

     所以说安装全局还是局部钩子取决于传入的消息常量。

     安装钩子需要调用的API:

 1         /// <summary>
 2         /// 安装勾子
 3         /// </summary>
 4         /// <param name="idHook">钩子类型,此处用整形的枚举表示</param>
 5         /// <param name="hookCallBack">钩子发挥作用时的回调函数</param>
 6         /// <param name="moudleHandle">应用程序实例的模块句柄(一般来说是你钩子回调函数所在的应用程序实例模块句柄)</param>
 7         /// <param name="threadID">与安装的钩子子程相关联的线程的标识符
 8         /// <remarks>如果线程ID是0则针对系统级别的,否则是针对当前线程</remarks>
 9         /// </param>
10         /// <returns>返回钩子句柄</returns>
11         [DllImport("user32.dll")]
12         public static extern int SetWindowsHookEx(int idHook, HookProcCallBack hookCallBack, IntPtr moudleHandle, int threadID);
13 
14         public delegate int HookProcCallBack(int nCode, int wParam, IntPtr lParam);

     ☆:上面方法的第二个需要是个委托参数,必须把它设置为静态变量,因为监测钩子相当于一个定时器一直在跑,如果委托变量不是静态的话,会被GC给回收掉的。

  3.监测键盘和鼠标行为

    键盘操作分为:keyDown,keyPress,keyUp;鼠标操作分为:rightClick,leftClick,doubleClick,wheel,move。

    所以为了要监测上面的所有行为,需要使用事件来实现。

  4.卸载钩子

    主要还是调用API就可以了。

1         /// <summary>
2         /// 卸载勾子
3         /// </summary>
4         /// <param name="handle">要取消的钩子的句柄</param>
5         /// <returns>卸载钩子是否成功</returns>
6         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
7         public static extern bool UnhookWindowsHookEx(int handle); 

  5.HookManager

    发现写不下去,不知道该讲什么了,很多细节上都没有讲到,比如每个参数的含义,怎么调用等等,算是给出个思路,我也是下载了很多源码摸索过来的。

    给出主要实现代码:

  1 using System;
  2 using System.ComponentModel;
  3 using System.Diagnostics;
  4 using System.Runtime.InteropServices;
  5 using System.Windows.Forms;
  6 
  7 using SharpCommon.Windows;
  8 
  9 /*
 10  
 11  * 2014-1-28 完善第一个版本
 12  * 
 13  *  1.关于KeyPress的解释
 14  *      在控件有焦点的情况下按下键时发生。
 15  *      键事件按下列顺序发生: 
 16             KeyDown
 17             KeyPress
 18             KeyUp
 19 
 20             非字符键不会引发 KeyPress 事件;但非字符键却可以引发 KeyDown 和 KeyUp 事件。
 21             使用 KeyChar 属性在运行时对键击进行取样,并且使用或修改公共键击的子集。
 22             若要仅在窗体级别处理键盘事件而不允许其他控件接收键盘事件,
 23  *         请将窗体的 KeyPress 事件处理方法中的 KeyPressEventArgs.Handled 属性设置为 true。
 24  *         
 25  *      摘自MSDN上的说明
 26  *      KeyPressEventArgs 指定在用户按键时撰写的字符。例如,当用户按 Shift + K 时,KeyChar 属性返回一个大写字母 K。
 27          当用户按下任意键时,发生 KeyPress 事件。与 KeyPress 事件紧密相关的两个事件为 KeyUp 和 KeyDown。
 28  *      当用户按下某个键时,KeyDown 事件先于每个 KeyPress 事件发生;当用户释放某个键时发生 KeyUp 事件。
 29  *      当用户按住某个键时,每次字符重复时,KeyDown 和 KeyPress 事件也都重复发生。一个 KeyUp 事件在释放按键时生成。
 30 
 31          KeyPressEventArgs 随着 KeyPress 事件的每次发生而被传递。
 32  *      KeyEventArgs 随着 KeyDown 和 KeyUp 事件的每次发生而被传递。
 33  *      KeyEventArgs 指定是否有任一个组合键(Ctrl、Shift 或 Alt)在另一个键按下的同时也曾按下。
 34  *      此修饰符信息也可以通过 Control 类的 ModifierKeys 属性获得。
 35 
 36          将 Handled 设置为 true,以取消 KeyPress 事件。这可防止控件处理按键。
 37 
 38          注意注意: 
 39          有些控件将会在 KeyDown 上处理某些击键。
 40  *      例如,RichTextBox 在调用 KeyPress 前处理 Enter 键。
 41  *      在这种情况下,您无法取消 KeyPress 事件,而是必须从 KeyDown 取消击键。
 42  *      
 43  * 
 44  *  2014-1-28 1:00 PM
 45  *      1. 完成了对组合键的监测代码,通过获取KeyState来判断是否按了组合键
 46  
 47  */
 48 
 49 namespace SharpCommon.Hook
 50 {
 51     public sealed class HookManager
 52     {
 53         #region Event And Field
 54         public event CustomKeyEventHandler KeyUp;
 55         public event CustomKeyEventHandler KeyDown;
 56         public event CustomKeyEventHandler KeyPress;
 57 
 58         public event MouseEventHandler MouseMove;
 59         public event MouseEventHandler MouseWheel;
 60         public event MouseEventHandler LeftMouseClickUp;
 61         public event MouseEventHandler RightMouseClickUp;
 62         public event MouseEventHandler LeftMouseClickDown;
 63         public event MouseEventHandler RightMouseClickDown;
 64         public event MouseEventHandler LeftMouseDoubleClick;
 65         public event MouseEventHandler RightMouseDoubleClick;
 66 
 67         private static int _mouseHookHandle;
 68         private static int _keyboardHookHandlel;
 69 
 70         private static HookProcCallBack _mouseHookCallBack;
 71         private static HookProcCallBack _keyboardHookCallBack;
 72 
 73         private static readonly HookManager _instance = new HookManager();
 74 
 75         private static readonly int _currentThreadID = AppDomain.GetCurrentThreadId();
 76         private static readonly IntPtr _currentMoudleHandle = WindowsAPI.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
 77         
 78         #endregion
 79 
 80         #region Instance
 81 
 82         private HookManager()
 83         { }
 84 
 85         public static HookManager Instance
 86         {
 87             get { return _instance; }
 88         } 
 89 
 90         #endregion
 91 
 92         #region Install Hook
 93 
 94         /// <summary>
 95         /// Install the hook.
 96         /// </summary>
 97         /// <param name="installType">Select the hook install type.</param>
 98         public void InstallHook(HookInstallType installType = HookInstallType.MouseAndKeyBoard)
 99         {
100             switch (installType)
101             {
102                 case HookInstallType.Mouse:
103                     this.InstallMouseHook();
104                     break;
105                 case HookInstallType.KeyBoard:
106                     this.InstallKeyBoardHook();
107                     break;
108                 case HookInstallType.MouseAndKeyBoard:
109                     this.InstallMouseHook();
110                     this.InstallKeyBoardHook();
111                     break;
112             }
113         } 
114 
115         #endregion
116 
117         #region Mouse Hook Monitor
118 
119         /// <summary>
120         /// Install the mouse hook.
121         /// Default install mouse global hook - [14];
122         /// </summary>
123         /// <param name="hookType">Select mouse hook type.</param>
124         public void InstallMouseHook(HookType hookType = HookType.WH_MOUSE_LL)
125         {
126             if (_mouseHookHandle == default(int))
127             {
128                 _mouseHookCallBack = new HookProcCallBack(this.MouseHookCallBack);
129                 if (hookType == HookType.WH_MOUSE)
130                 {
131                     _mouseHookHandle = HookAPI.SetWindowsHookEx((int)hookType, _mouseHookCallBack, IntPtr.Zero, _currentThreadID);
132                 }
133                 else
134                 {
135                     _mouseHookHandle = HookAPI.SetWindowsHookEx((int)hookType, _mouseHookCallBack, _currentMoudleHandle, 0);
136                 }
137                 this.CheckHandleIsZero(_mouseHookHandle);
138             }
139         }
140 
141         private int MouseHookCallBack(int nCode, int wParam, IntPtr lParam)
142         {
143             MouseButtons mouseOperation = MouseButtons.None;
144             Point mousePoint = (Point)Marshal.PtrToStructure(lParam, typeof(Point));
145 
146             switch (wParam)
147             {
148                 case (int)WindowsMessage.WM_LBUTTONDOWN:
149                     mouseOperation = MouseButtons.Left;
150                     this.InvokeMouseEvent(this.LeftMouseClickDown, mouseOperation, mousePoint);
151                     break;
152                 case (int)WindowsMessage.WM_LBUTTONUP:
153                     mouseOperation = MouseButtons.Left;
154                     this.InvokeMouseEvent(this.LeftMouseClickUp, mouseOperation, mousePoint);
155                     break;
156                 case (int)WindowsMessage.WM_LBUTTONDBLCLK:
157                     mouseOperation = MouseButtons.Left;
158                     this.InvokeMouseEvent(this.LeftMouseDoubleClick, mouseOperation, mousePoint);
159                     break;
160                 case (int)WindowsMessage.WM_RBUTTONDOWN:
161                     mouseOperation = MouseButtons.Right;
162                     this.InvokeMouseEvent(this.RightMouseClickDown, mouseOperation, mousePoint);
163                     break;
164                 case (int)WindowsMessage.WM_RBUTTONUP:
165                     mouseOperation = MouseButtons.Right;
166                     this.InvokeMouseEvent(this.RightMouseClickUp, mouseOperation, mousePoint);
167                     break;
168                 case (int)WindowsMessage.WM_RBUTTONDBLCLK:
169                     mouseOperation = MouseButtons.Right;
170                     this.InvokeMouseEvent(this.RightMouseDoubleClick, mouseOperation, mousePoint);
171                     break;
172                 case (int)WindowsMessage.WM_MOUSEMOVE:
173                     this.InvokeMouseEvent(this.MouseMove, mouseOperation, mousePoint);
174                     break;
175                 case (int)WindowsMessage.WM_MOUSEWHEEL:
176                     this.InvokeMouseEvent(this.MouseWheel, mouseOperation, mousePoint);
177                     break;
178             }
179 
180             return HookAPI.CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
181         }
182 
183         private void InvokeMouseEvent(MouseEventHandler mouseEvent, MouseButtons mouseButton, Point point)
184         {
185             if (mouseEvent != null)
186             {
187                 MouseEventArgs mouseArgs = new MouseEventArgs(mouseButton, 0, point.X, point.Y, 0);
188                 mouseEvent(this, mouseArgs);
189             }
190         } 
191 
192         #endregion
193 
194         #region KeyBoaed Hook Monitor
195 
196         /// <summary>
197         /// Install the keyboard hook.
198         /// Default install keyboard global hook - [13].
199         /// </summary>
200         /// <param name="hookType">Select keyboard hook type.</param>
201         public void InstallKeyBoardHook(HookType hookType = HookType.WH_KEYBOARD_LL)
202         {
203             if (_keyboardHookHandlel == default(int))
204             {
205                 _keyboardHookCallBack = new HookProcCallBack(this.KeyBoradHookCallBack);
206                 if (hookType == HookType.WH_KEYBOARD)
207                 {
208                     _keyboardHookHandlel = HookAPI.SetWindowsHookEx((int)hookType, _keyboardHookCallBack, IntPtr.Zero, _currentThreadID);
209                 }
210                 else
211                 {
212                     _keyboardHookHandlel = HookAPI.SetWindowsHookEx((int)hookType, _keyboardHookCallBack, _currentMoudleHandle, 0);
213                 }
214                 this.CheckHandleIsZero(_keyboardHookHandlel);
215             }
216         }
217 
218         private int KeyBoradHookCallBack(int nCode, int wParam, IntPtr lParam)
219         {
220             if (nCode >= 0)
221             {
222                 CustomKeyBoard keyInfo = (CustomKeyBoard)Marshal.PtrToStructure(lParam, typeof(CustomKeyBoard));
223 
224                 if (this.KeyDown != null
225                     && (wParam == (int)WindowsMessage.WM_KEYDOWN || wParam == (int)WindowsMessage.WM_SYSKEYDOWN))
226                 {
227                     this.InvokeKeyBoardEvent(this.KeyDown, (Keys)keyInfo.VirtualKeyCode);
228                 }
229 
230                 if (this.KeyPress != null && wParam == (int)WindowsMessage.WM_KEYDOWN)
231                 {
232                     this.InvokeKeyBoardEvent(this.KeyPress, (Keys)keyInfo.VirtualKeyCode);
233                 }
234 
235                 if (this.KeyUp != null
236                     && (wParam == (int)WindowsMessage.WM_KEYUP || wParam == (int)WindowsMessage.WM_SYSKEYUP))
237                 {
238                     this.InvokeKeyBoardEvent(this.KeyUp, (Keys)keyInfo.VirtualKeyCode);
239                 }
240             }
241 
242             return HookAPI.CallNextHookEx(_keyboardHookHandlel, nCode, wParam, lParam);
243         }
244 
245         private void InvokeKeyBoardEvent(CustomKeyEventHandler keyEvent, Keys keyData)
246         {
247             CustomKeyEventArgs customKeyArgs = new CustomKeyEventArgs(keyData);
248             keyEvent(this, customKeyArgs);
249         } 
250 
251         #endregion
252 
253         #region Common
254 
255         private void CheckHandleIsZero(int handle)
256         {
257             if (handle == 0)
258             {
259                 int errorID = Marshal.GetLastWin32Error();
260                 throw new Win32Exception(errorID);
261             }
262         }
263 
264         public void UninstallHook()
265         {
266             if (_mouseHookHandle != default(int))
267             {
268                 if (HookAPI.UnhookWindowsHookEx(_mouseHookHandle))
269                 {
270                     _mouseHookHandle = default(int);
271                 }
272             }
273             if (_keyboardHookHandlel != default(int))
274             {
275                 if (HookAPI.UnhookWindowsHookEx(_keyboardHookHandlel))
276                 {
277                     _keyboardHookHandlel = default(int);
278                 }
279             }
280         } 
281 
282         #endregion
283     }
284 }
View Code

  

  全部代码:下载

 

好了就这么多了,已同步至:个人文章目录索引

posted @ 2014-02-22 16:13  TimYang  阅读(4309)  评论(1编辑  收藏  举报