學習之路三十八:Hook(鉤子)的學習

很久沒寫文章了,還記得年前面試了一家公司,爲了檢測一下個人學習能力,給了我一個任務,作一個自動登陸並自動操做菜單的程序。html

花了幾天的時間研究了Hook以及使用WindowsAPI操做程序的知識,如今記錄一下,也算是一次溫習。面試

一丶Hookide

  在我看來Hook就是監測用戶操做鍵盤(或虛擬鍵盤)以及鼠標的行爲,對於Hook的理解我也不是很深刻,也只是一點皮毛。函數

  1. 實現Hook的步驟佈局

    ①安裝鉤子post

    ②監測鍵盤和鼠標的操做,用來實現相應的邏輯學習

    ③卸載鉤子ui

  2.安裝鉤子this

    鉤子分兩種:鍵盤鉤子和鼠標鉤子,而每一種鉤子又能夠分爲全局鉤子或局部勾子。url

    下面是安裝鉤子須要的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

  

  所有代碼:下載

 

好了就這麼多了,已同步至:我的文章目錄索引

相關文章
相關標籤/搜索