本隨筆較長,讀者可直接到最地下下載示例程序。
總所周知:C#是.NET Framework平台的相伴語言,用它本身的類庫和編譯器提供的方法是無法實現全局鈎子的。但實際上對於非托管代碼的調用在C#中是成立的,使用DllImport屬性可以引用非托管代碼類庫中的方法。鈎子函數存在於user32.dll中,函數原型如下:
HHOOK WINAPI SetWindowsHookEx(
__in int idHook,
__in HOOKPROC lpfn,
__in HINSTANCE hMod,
__in DWORD dwThreadId);
使用它可以向操作系統(Windows)注冊一個特定類型的消息攔截處理方法,例如我們可以注冊一個攔截全局鍵盤消息的鈎子,那么所有的鍵盤按下、抬起事件都可以被我們感知和處理(不排除有前端鈎子將消息丟棄的情況)。
我們在C#中可以如下聲明來引用這個函數:
[DllImport("user32.dll")]
public static extern int SetWindowsHookEx(
HookType idHook,
HookProc lpfn,
IntPtr hInstance,
int threadId
);
值得一提的是上面的HookType和HookProc是我自定義的類型,這無關緊要(因為程序運行時傳遞的是內存地址嘛),但必須符合一定規范。
函數的參數從上到下依次為:
idHook鈎子類型,此處用整形的枚舉表示
lpfn鈎子發揮作用時的回調函數
hInstance應用程序實例的模塊句柄(一般來說是你鈎子回調函數所在的應用程序實例模塊句柄)
threadId與安裝的鈎子子程相關聯的線程的標識符
鈎子的類型有以下幾種:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace HookINCS 6 { 7 /// <summary> 8 /// 設置的鈎子類型 9 /// </summary> 10 public enum HookType : int 11 { 12 /// <summary> 13 /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以監視菜單,滾動 14 ///條,消息框,對話框消息並且發現用戶使用ALT+TAB or ALT+ESC 組合鍵切換窗口。 15 ///WH_MSGFILTER Hook只能監視傳遞到菜單,滾動條,消息框的消息,以及傳遞到通 16 ///過安裝了Hook子過程的應用程序建立的對話框的消息。WH_SYSMSGFILTER Hook 17 ///監視所有應用程序消息。 18 /// 19 ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以在模式循環期間 20 ///過濾消息,這等價於在主消息循環中過濾消息。 21 /// 22 ///通過調用CallMsgFilter function可以直接的調用WH_MSGFILTER Hook。通過使用這 23 ///個函數,應用程序能夠在模式循環期間使用相同的代碼去過濾消息,如同在主消息循 24 ///環里一樣 25 /// </summary> 26 WH_MSGFILTER = -1, 27 /// <summary> 28 /// WH_JOURNALRECORD Hook用來監視和記錄輸入事件。典型的,可以使用這 29 ///個Hook記錄連續的鼠標和鍵盤事件,然后通過使用WH_JOURNALPLAYBACK Hook 30 ///來回放。WH_JOURNALRECORD Hook是全局Hook,它不能象線程特定Hook一樣 31 ///使用。WH_JOURNALRECORD是system-wide local hooks,它們不會被注射到任何行 32 ///程地址空間 33 /// </summary> 34 WH_JOURNALRECORD = 0, 35 /// <summary> 36 /// WH_JOURNALPLAYBACK Hook使應用程序可以插入消息到系統消息隊列。可 37 ///以使用這個Hook回放通過使用WH_JOURNALRECORD Hook記錄下來的連續的鼠 38 ///標和鍵盤事件。只要WH_JOURNALPLAYBACK Hook已經安裝,正常的鼠標和鍵盤 39 ///事件就是無效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象線程特定 40 ///Hook一樣使用。WH_JOURNALPLAYBACK Hook返回超時值,這個值告訴系統在處 41 ///理來自回放Hook當前消息之前需要等待多長時間(毫秒)。這就使Hook可以控制實 42 ///時事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它們不會被 43 ///注射到任何行程地址空間 44 /// </summary> 45 WH_JOURNALPLAYBACK = 1, 46 /// <summary> 47 /// 在應用程序中,WH_KEYBOARD Hook用來監視WM_KEYDOWN and 48 ///WM_KEYUP消息,這些消息通過GetMessage or PeekMessage function返回。可以使 49 ///用這個Hook來監視輸入到消息隊列中的鍵盤消息 50 /// </summary> 51 WH_KEYBOARD = 2, 52 /// <summary> 53 /// 應用程序使用WH_GETMESSAGE Hook來監視從GetMessage or PeekMessage函 54 ///數返回的消息。你可以使用WH_GETMESSAGE Hook去監視鼠標和鍵盤輸入,以及 55 ///其它發送到消息隊列中的消息 56 /// </summary> 57 WH_GETMESSAGE = 3, 58 /// <summary> 59 /// 監視發送到窗口過程的消息,系統在消息發送到接收窗口過程之前調用 60 /// </summary> 61 WH_CALLWNDPROC = 4, 62 /// <summary> 63 /// 在以下事件之前,系統都會調用WH_CBT Hook子過程,這些事件包括: 64 ///1. 激活,建立,銷毀,最小化,最大化,移動,改變尺寸等窗口事件; 65 ///2. 完成系統指令; 66 ///3. 來自系統消息隊列中的移動鼠標,鍵盤事件; 67 ///4. 設置輸入焦點事件; 68 ///5. 同步系統消息隊列事件。 69 ///Hook子過程的返回值確定系統是否允許或者防止這些操作中的一個 70 /// </summary> 71 WH_CBT = 5, 72 /// <summary> 73 /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以監視菜單,滾動 74 ///條,消息框,對話框消息並且發現用戶使用ALT+TAB or ALT+ESC 組合鍵切換窗口。 75 ///WH_MSGFILTER Hook只能監視傳遞到菜單,滾動條,消息框的消息,以及傳遞到通 76 ///過安裝了Hook子過程的應用程序建立的對話框的消息。WH_SYSMSGFILTER Hook 77 ///監視所有應用程序消息。 78 /// 79 ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我們可以在模式循環期間 80 ///過濾消息,這等價於在主消息循環中過濾消息。 81 /// 82 ///通過調用CallMsgFilter function可以直接的調用WH_MSGFILTER Hook。通過使用這 83 ///個函數,應用程序能夠在模式循環期間使用相同的代碼去過濾消息,如同在主消息循 84 ///環里一樣 85 /// </summary> 86 WH_SYSMSGFILTER = 6, 87 /// <summary> 88 /// WH_MOUSE Hook監視從GetMessage 或者 PeekMessage 函數返回的鼠標消息。 89 ///使用這個Hook監視輸入到消息隊列中的鼠標消息 90 /// </summary> 91 WH_MOUSE = 7, 92 /// <summary> 93 /// 當調用GetMessage 或 PeekMessage 來從消息隊列種查詢非鼠標、鍵盤消息時 94 /// </summary> 95 WH_HARDWARE = 8, 96 /// <summary> 97 /// 在系統調用系統中與其它Hook關聯的Hook子過程之前,系統會調用 98 ///WH_DEBUG Hook子過程。你可以使用這個Hook來決定是否允許系統調用與其它 99 ///Hook關聯的Hook子過程 100 /// </summary> 101 WH_DEBUG = 9, 102 /// <summary> 103 /// 外殼應用程序可以使用WH_SHELL Hook去接收重要的通知。當外殼應用程序是 104 ///激活的並且當頂層窗口建立或者銷毀時,系統調用WH_SHELL Hook子過程。 105 ///WH_SHELL 共有5鍾情況: 106 ///1. 只要有個top-level、unowned 窗口被產生、起作用、或是被摧毀; 107 ///2. 當Taskbar需要重畫某個按鈕; 108 ///3. 當系統需要顯示關於Taskbar的一個程序的最小化形式; 109 ///4. 當目前的鍵盤布局狀態改變; 110 ///5. 當使用者按Ctrl+Esc去執行Task Manager(或相同級別的程序)。 111 /// 112 ///按照慣例,外殼應用程序都不接收WH_SHELL消息。所以,在應用程序能夠接 113 ///收WH_SHELL消息之前,應用程序必須調用SystemParametersInfo function注冊它自 114 ///己 115 /// </summary> 116 WH_SHELL = 10, 117 /// <summary> 118 /// 當應用程序的前台線程處於空閑狀態時,可以使用WH_FOREGROUNDIDLE 119 ///Hook執行低優先級的任務。當應用程序的前台線程大概要變成空閑狀態時,系統就 120 ///會調用WH_FOREGROUNDIDLE Hook子過程 121 /// </summary> 122 WH_FOREGROUNDIDLE = 11, 123 /// <summary> 124 /// 監視發送到窗口過程的消息,系統在消息發送到接收窗口過程之后調用 125 /// </summary> 126 WH_CALLWNDPROCRET = 12, 127 /// <summary> 128 /// 監視輸入到線程消息隊列中的鍵盤消息 129 /// </summary> 130 WH_KEYBOARD_LL = 13, 131 /// <summary> 132 /// 監視輸入到線程消息隊列中的鼠標消息 133 /// </summary> 134 WH_MOUSE_LL = 14 135 } 136 }
我們一般會使用13攔截鍵盤消息,14攔截鼠標消息。
回調函數的聲明我們在C#里需要用到委托,聲明如下:
public delegate int HookProc(int nCode, int wParam, IntPtr lParam);
從上而下參數意義為:nCode鈎子鏈傳遞回來的參數,0表示此消息(被之前的消息鈎子)丟棄,非0表示此消息繼續有效
wParam消息參數
lParam消息參數
值得一提的是wParam和lParam在不同的消息類型中是不一樣的類型,不過wParam的類型大概可以用下面的枚舉表示:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace HookINCS 6 { 7 /// <summary> 8 /// 消息類型 9 /// 作為SendMessage和PostMessage的參數 10 /// </summary> 11 public enum MsgType : uint 12 { 13 WM_KEYFIRST = 0x0100, 14 15 //Msg參數常量值: 16 /// <summary> 17 /// 按下一個鍵 18 /// </summary> 19 WM_KEYDOWN = 0x0100, 20 /// <summary> 21 /// 釋放一個鍵 22 /// </summary> 23 WM_KEYUP = 0x0101, 24 /// <summary> 25 /// 按下某鍵,並已發出WM_KEYDOWN, WM_KEYUP消息 26 /// </summary> 27 WM_CHAR = 0x102, 28 /// <summary> 29 /// 當用translatemessage函數翻譯WM_KEYUP消息時發送此消息給擁有焦點的窗口 30 /// </summary> 31 WM_DEADCHAR = 0x103, 32 /// <summary> 33 /// 當用戶按住ALT鍵同時按下其它鍵時提交此消息給擁有焦點的窗口 34 /// </summary> 35 WM_SYSKEYDOWN = 0x104, 36 /// <summary> 37 /// 當用戶釋放一個鍵同時ALT 鍵還按着時提交此消息給擁有焦點的窗口 38 /// </summary> 39 WM_SYSKEYUP = 0x105, 40 /// <summary> 41 /// 當WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函數翻譯后提交此消息給擁有焦點的窗口 42 /// </summary> 43 WM_SYSCHAR = 0x106, 44 /// <summary> 45 /// 當WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函數翻譯后發送此消息給擁有焦點的窗口 46 /// </summary> 47 WM_SYSDEADCHAR = 0x107, 48 /// <summary> 49 /// 在一個對話框程序被顯示前發送此消息給它,通常用此消息初始化控件和執行其它任務 50 /// </summary> 51 WM_INITDIALOG = 0x110, 52 /// <summary> 53 /// 當用戶選擇一條菜單命令項或當某個控件發送一條消息給它的父窗口,一個快捷鍵被翻譯 54 /// </summary> 55 WM_COMMAND = 0x111, 56 /// <summary> 57 /// 當用戶選擇窗口菜單的一條命令或//當用戶選擇最大化或最小化時那個窗口會收到此消息 58 /// </summary> 59 WM_SYSCOMMAND = 0x112, 60 /// <summary> 61 /// 發生了定時器事件 62 /// </summary> 63 WM_TIMER = 0x113, 64 /// <summary> 65 /// 當一個窗口標准水平滾動條產生一個滾動事件時發送此消息給那個窗口,也發送給擁有它的控件 66 /// </summary> 67 WM_HSCROLL = 0x114, 68 /// <summary> 69 /// 當一個窗口標准垂直滾動條產生一個滾動事件時發送此消息給那個窗口也,發送給擁有它的控件 70 /// </summary> 71 WM_VSCROLL = 0x115, 72 /// <summary> 73 /// 當一個菜單將要被激活時發送此消息,它發生在用戶菜單條中的某項或按下某個菜單鍵,它允許程序在顯示前更改菜單 74 /// </summary> 75 WM_INITMENU = 0x116, 76 /// <summary> 77 /// 當一個下拉菜單或子菜單將要被激活時發送此消息,它允許程序在它顯示前更改菜單,而不要改變全部 78 /// </summary> 79 WM_INITMENUPOPUP = 0x117, 80 /// <summary> 81 /// 當用戶選擇一條菜單項時發送此消息給菜單的所有者(一般是窗口) 82 /// </summary> 83 WM_MENUSELECT = 0x11F, 84 /// <summary> 85 /// 當菜單已被激活用戶按下了某個鍵(不同於加速鍵),發送此消息給菜單的所有者 86 /// </summary> 87 WM_MENUCHAR = 0x120, 88 /// <summary> 89 /// 當一個模態對話框或菜單進入空載狀態時發送此消息給它的所有者,一個模態對話框或菜單進入空載狀態就是在處理完一條或幾條先前的消息后沒有消息它的列隊中等待 90 /// </summary> 91 WM_ENTERIDLE = 0x121, 92 /// <summary> 93 /// 在windows繪制消息框前發送此消息給消息框的所有者窗口,通過響應這條消息,所有者窗口可以通過使用給定的相關顯示設備的句柄來設置消息框的文本和背景顏色 94 /// </summary> 95 WM_CTLCOLORMSGBOX = 0x132, 96 /// <summary> 97 /// 當一個編輯型控件將要被繪制時發送此消息給它的父窗口通過響應這條消息,所有者窗口可以通過使用給定的相關顯示設備的句柄來設置編輯框的文本和背景顏色 98 /// </summary> 99 WM_CTLCOLOREDIT = 0x133, 100 101 /// <summary> 102 /// 當一個列表框控件將要被繪制前發送此消息給它的父窗口通過響應這條消息,所有者窗口可以通過使用給定的相關顯示設備的句柄來設置列表框的文本和背景顏色 103 /// </summary> 104 WM_CTLCOLORLISTBOX = 0x134, 105 /// <summary> 106 /// 當一個按鈕控件將要被繪制時發送此消息給它的父窗口通過響應這條消息,所有者窗口可以通過使用給定的相關顯示設備的句柄來設置按紐的文本和背景顏色 107 /// </summary> 108 WM_CTLCOLORBTN = 0x135, 109 /// <summary> 110 /// 當一個對話框控件將要被繪制前發送此消息給它的父窗口通過響應這條消息,所有者窗口可以通過使用給定的相關顯示設備的句柄來設置對話框的文本背景顏色 111 /// </summary> 112 WM_CTLCOLORDLG = 0x136, 113 /// <summary> 114 /// 當一個滾動條控件將要被繪制時發送此消息給它的父窗口通過響應這條消息,所有者窗口可以通過使用給定的相關顯示設備的句柄來設置滾動條的背景顏色 115 /// </summary> 116 WM_CTLCOLORSCROLLBAR = 0x137, 117 /// <summary> 118 /// 當一個靜態控件將要被繪制時發送此消息給它的父窗口通過響應這條消息,所有者窗口可以 通過使用給定的相關顯示設備的句柄來設置靜態控件的文本和背景顏色 119 /// </summary> 120 WM_CTLCOLORSTATIC = 0x138, 121 /// <summary> 122 /// 當鼠標輪子轉動時發送此消息個當前有焦點的控件 123 /// </summary> 124 WM_MOUSEWHEEL = 0x20A, 125 /// <summary> 126 /// 雙擊鼠標中鍵 127 /// </summary> 128 WM_MBUTTONDBLCLK = 0x209, 129 /// <summary> 130 /// 釋放鼠標中鍵 131 /// </summary> 132 WM_MBUTTONUP = 0x208, 133 /// <summary> 134 /// 移動鼠標時發生,同WM_MOUSEFIRST 135 /// </summary> 136 WM_MOUSEMOVE = 0x200, 137 /// <summary> 138 /// 按下鼠標左鍵 139 /// </summary> 140 WM_LBUTTONDOWN = 0x201, 141 /// <summary> 142 /// 釋放鼠標左鍵 143 /// </summary> 144 WM_LBUTTONUP = 0x202, 145 /// <summary> 146 /// 雙擊鼠標左鍵 147 /// </summary> 148 WM_LBUTTONDBLCLK = 0x203, 149 /// <summary> 150 /// 按下鼠標右鍵 151 /// </summary> 152 WM_RBUTTONDOWN = 0x204, 153 /// <summary> 154 /// 釋放鼠標右鍵 155 /// </summary> 156 WM_RBUTTONUP = 0x205, 157 /// <summary> 158 /// 雙擊鼠標右鍵 159 /// </summary> 160 WM_RBUTTONDBLCLK = 0x206, 161 /// <summary> 162 /// 按下鼠標中鍵 163 /// </summary> 164 WM_MBUTTONDOWN = 0x207, 165 166 //WM_USER = 0x0400; 167 //public static int MK_LBUTTON = 0x0001; 168 //public static int MK_RBUTTON = 0x0002; 169 //public static int MK_SHIFT = 0x0004; 170 //public static int MK_CONTROL = 0x0008; 171 //public static int MK_MBUTTON = 0x0010; 172 //public static int MK_XBUTTON1 = 0x0020; 173 //public static int MK_XBUTTON2 = 0x0040; 174 /// <summary> 175 /// 創建一個窗口 176 /// </summary> 177 WM_CREATE = 0x01, 178 /// <summary> 179 /// 當一個窗口被破壞時發送 180 /// </summary> 181 WM_DESTROY = 0x02, 182 /// <summary> 183 /// 移動一個窗口 184 /// </summary> 185 WM_MOVE = 0x03, 186 /// <summary> 187 /// 改變一個窗口的大小 188 /// </summary> 189 WM_SIZE = 0x05, 190 /// <summary> 191 /// 一個窗口被激活或失去激活狀態 192 /// </summary> 193 WM_ACTIVATE = 0x06, 194 /// <summary> 195 /// 一個窗口獲得焦點 196 /// </summary> 197 WM_SETFOCUS = 0x07, 198 /// <summary> 199 /// 一個窗口失去焦點 200 /// </summary> 201 WM_KILLFOCUS = 0x08, 202 /// <summary> 203 /// 一個窗口改變成Enable狀態 204 /// </summary> 205 WM_ENABLE = 0x0A, 206 /// <summary> 207 /// 設置窗口是否能重畫 208 /// </summary> 209 WM_SETREDRAW = 0x0B, 210 /// <summary> 211 /// 應用程序發送此消息來設置一個窗口的文本 212 /// </summary> 213 WM_SETTEXT = 0x0C, 214 /// <summary> 215 /// 應用程序發送此消息來復制對應窗口的文本到緩沖區 216 /// </summary> 217 WM_GETTEXT = 0x0D, 218 /// <summary> 219 /// 得到與一個窗口有關的文本的長度(不包含空字符) 220 /// </summary> 221 WM_GETTEXTLENGTH = 0x0E, 222 /// <summary> 223 /// 要求一個窗口重畫自己 224 /// </summary> 225 WM_PAINT = 0x0F, 226 /// <summary> 227 /// 當一個窗口或應用程序要關閉時發送一個信號 228 /// </summary> 229 WM_CLOSE = 0x10, 230 /// <summary> 231 /// 當用戶選擇結束對話框或程序自己調用ExitWindows函數 232 /// </summary> 233 WM_QUERYENDSESSION = 0x11, 234 /// <summary> 235 /// 用來結束程序運行 236 /// </summary> 237 WM_QUIT = 0x12, 238 /// <summary> 239 /// 當用戶窗口恢復以前的大小位置時,把此消息發送給某個圖標 240 /// </summary> 241 WM_QUERYOPEN = 0x13, 242 /// <summary> 243 /// 當窗口背景必須被擦除時(例在窗口改變大小時) 244 /// </summary> 245 WM_ERASEBKGND = 0x14, 246 /// <summary> 247 /// 當系統顏色改變時,發送此消息給所有頂級窗口 248 /// </summary> 249 WM_SYSCOLORCHANGE = 0x15, 250 /// <summary> 251 /// 當系統進程發出WM_QUERYENDSESSION消息后,此消息發送給應用程序,通知它對話是否結束 252 /// </summary> 253 WM_ENDSESSION = 0x16, 254 /// <summary> 255 /// 當隱藏或顯示窗口是發送此消息給這個窗口 256 /// </summary> 257 WM_SHOWWINDOW = 0x18, 258 /// <summary> 259 /// 發此消息給應用程序哪個窗口是激活的,哪個是非激活的 260 /// </summary> 261 WM_ACTIVATEAPP = 0x1C, 262 /// <summary> 263 /// 當系統的字體資源庫變化時發送此消息給所有頂級窗口 264 /// </summary> 265 WM_FONTCHANGE = 0x1D, 266 /// <summary> 267 /// 當系統的時間變化時發送此消息給所有頂級窗口 268 /// </summary> 269 WM_TIMECHANGE = 0x1E, 270 /// <summary> 271 /// 發送此消息來取消某種正在進行的摸態(操作) 272 /// </summary> 273 WM_CANCELMODE = 0x1F, 274 /// <summary> 275 /// 如果鼠標引起光標在某個窗口中移動且鼠標輸入沒有被捕獲時,就發消息給某個窗口 276 /// </summary> 277 WM_SETCURSOR = 0x20, 278 /// <summary> 279 /// 當光標在某個非激活的窗口中而用戶正按着鼠標的某個鍵發送此消息給//當前窗口 280 /// </summary> 281 WM_MOUSEACTIVATE = 0x21, 282 /// <summary> 283 /// 發送此消息給MDI子窗口//當用戶點擊此窗口的標題欄,或//當窗口被激活,移動,改變大小 284 /// </summary> 285 WM_CHILDACTIVATE = 0x22, 286 /// <summary> 287 /// 此消息由基於計算機的訓練程序發送,通過WH_JOURNALPALYBACK的hook程序分離出用戶輸入消息 288 /// </summary> 289 WM_QUEUESYNC = 0x23, 290 /// <summary> 291 /// 此消息發送給窗口當它將要改變大小或位置 292 /// </summary> 293 WM_GETMINMAXINFO = 0x24, 294 /// <summary> 295 /// 發送給最小化窗口當它圖標將要被重畫 296 /// </summary> 297 WM_PAINTICON = 0x26, 298 /// <summary> 299 /// 此消息發送給某個最小化窗口,僅//當它在畫圖標前它的背景必須被重畫 300 /// </summary> 301 WM_ICONERASEBKGND = 0x27, 302 /// <summary> 303 /// 發送此消息給一個對話框程序去更改焦點位置 304 /// </summary> 305 WM_NEXTDLGCTL = 0x28, 306 /// <summary> 307 /// 每當打印管理列隊增加或減少一條作業時發出此消息 308 /// </summary> 309 WM_SPOOLERSTATUS = 0x2A, 310 /// <summary> 311 /// 當button,combobox,listbox,menu的可視外觀改變時發送 312 /// </summary> 313 WM_DRAWITEM = 0x2B, 314 /// <summary> 315 /// 當button, combo box, list box, list view control, or menu item 被創建時 316 /// </summary> 317 WM_MEASUREITEM = 0x2C, 318 /// <summary> 319 /// 此消息有一個LBS_WANTKEYBOARDINPUT風格的發出給它的所有者來響應WM_KEYDOWN消息 320 /// </summary> 321 WM_VKEYTOITEM = 0x2E, 322 /// <summary> 323 /// 此消息由一個LBS_WANTKEYBOARDINPUT風格的列表框發送給他的所有者來響應WM_CHAR消息 324 /// </summary> 325 WM_CHARTOITEM = 0x2F, 326 /// <summary> 327 /// 當繪制文本時程序發送此消息得到控件要用的顏色 328 /// </summary> 329 WM_SETFONT = 0x30, 330 /// <summary> 331 /// 應用程序發送此消息得到當前控件繪制文本的字體 332 /// </summary> 333 WM_GETFONT = 0x31, 334 /// <summary> 335 /// 應用程序發送此消息讓一個窗口與一個熱鍵相關連 336 /// </summary> 337 WM_SETHOTKEY = 0x32, 338 /// <summary> 339 /// 應用程序發送此消息來判斷熱鍵與某個窗口是否有關聯 340 /// </summary> 341 WM_GETHOTKEY = 0x33, 342 /// <summary> 343 /// 此消息發送給最小化窗口,當此窗口將要被拖放而它的類中沒有定義圖標,應用程序能返回一個圖標或光標的句柄,當用戶拖放圖標時系統顯示這個圖標或光標 344 /// </summary> 345 WM_QUERYDRAGICON = 0x37, 346 /// <summary> 347 /// 發送此消息來判定combobox或listbox新增加的項的相對位置 348 /// </summary> 349 WM_COMPAREITEM = 0x39, 350 /// <summary> 351 /// 顯示內存已經很少了 352 /// </summary> 353 WM_COMPACTING = 0x41, 354 /// <summary> 355 /// 發送此消息給那個窗口的大小和位置將要被改變時,來調用setwindowpos函數或其它窗口管理函數 356 /// </summary> 357 WM_WINDOWPOSCHANGING = 0x46, 358 /// <summary> 359 /// 發送此消息給那個窗口的大小和位置已經被改變時,來調用setwindowpos函數或其它窗口管理函數 360 /// </summary> 361 WM_WINDOWPOSCHANGED = 0x47, 362 /// <summary> 363 /// 當系統將要進入暫停狀態時發送此消息 364 /// </summary> 365 WM_POWER = 0x48, 366 /// <summary> 367 /// 當一個應用程序傳遞數據給另一個應用程序時發送此消息 368 /// </summary> 369 WM_COPYDATA = 0x4A, 370 /// <summary> 371 /// 當某個用戶取消程序日志激活狀態,提交此消息給程序 372 /// </summary> 373 WM_CANCELJOURNA = 0x4B, 374 /// <summary> 375 /// 當某個控件的某個事件已經發生或這個控件需要得到一些信息時,發送此消息給它的父窗口 376 /// </summary> 377 WM_NOTIFY = 0x4E, 378 /// <summary> 379 /// 當用戶選擇某種輸入語言,或輸入語言的熱鍵改變 380 /// </summary> 381 WM_INPUTLANGCHANGEREQUEST = 0x50, 382 /// <summary> 383 /// 當平台現場已經被改變后發送此消息給受影響的最頂級窗口 384 /// </summary> 385 WM_INPUTLANGCHANGE = 0x51, 386 /// <summary> 387 /// 當程序已經初始化windows幫助例程時發送此消息給應用程序 388 /// </summary> 389 WM_TCARD = 0x52, 390 /// <summary> 391 /// 此消息顯示用戶按下了F1,如果某個菜單是激活的,就發送此消息個此窗口關聯的菜單,否則就發送給有焦點的窗口,如果//當前都沒有焦點,就把此消息發送給//當前激活的窗口 392 /// </summary> 393 WM_HELP = 0x53, 394 /// <summary> 395 /// 當用戶已經登入或退出后發送此消息給所有的窗口,//當用戶登入或退出時系統更新用戶的具體設置信息,在用戶更新設置時系統馬上發送此消息 396 /// </summary> 397 WM_USERCHANGED = 0x54, 398 /// <summary> 399 /// 公用控件,自定義控件和他們的父窗口通過此消息來判斷控件是使用ANSI還是UNICODE結構 400 /// </summary> 401 WM_NOTIFYFORMAT = 0x55, 402 /// <summary> 403 /// 當用戶某個窗口中點擊了一下右鍵就發送此消息給這個窗口 404 /// </summary> 405 WM_CONTEXTMENU = 0x7B, 406 /// <summary> 407 /// 當調用SETWINDOWLONG函數將要改變一個或多個 窗口的風格時發送此消息給那個窗口 408 /// </summary> 409 WM_STYLECHANGING = 0x7C, 410 /// <summary> 411 /// 當調用SETWINDOWLONG函數一個或多個 窗口的風格后發送此消息給那個窗口 412 /// </summary> 413 WM_STYLECHANGED = 0x7D, 414 /// <summary> 415 /// 當顯示器的分辨率改變后發送此消息給所有的窗口 416 /// </summary> 417 WM_DISPLAYCHANGE = 0x7E, 418 /// <summary> 419 /// 此消息發送給某個窗口來返回與某個窗口有關連的大圖標或小圖標的句柄 420 /// </summary> 421 WM_GETICON = 0x7F, 422 /// <summary> 423 /// 程序發送此消息讓一個新的大圖標或小圖標與某個窗口關聯 424 /// </summary> 425 WM_SETICON = 0x80, 426 /// <summary> 427 /// 當某個窗口第一次被創建時,此消息在WM_CREATE消息發送前發送 428 /// </summary> 429 WM_NCCREATE = 0x81, 430 /// <summary> 431 /// 此消息通知某個窗口,非客戶區正在銷毀 432 /// </summary> 433 WM_NCDESTROY = 0x82, 434 /// <summary> 435 /// 當某個窗口的客戶區域必須被核算時發送此消息 436 /// </summary> 437 WM_NCCALCSIZE = 0x83, 438 /// <summary> 439 /// 移動鼠標,按住或釋放鼠標時發生 440 /// </summary> 441 WM_NCHITTEST = 0x84, 442 /// <summary> 443 /// 程序發送此消息給某個窗口當它(窗口)的框架必須被繪制時 444 /// </summary> 445 WM_NCPAINT = 0x85, 446 /// <summary> 447 /// 此消息發送給某個窗口僅當它的非客戶區需要被改變來顯示是激活還是非激活狀態 448 /// </summary> 449 WM_NCACTIVATE = 0x86, 450 /// <summary> 451 /// 發送此消息給某個與對話框程序關聯的控件,widdows控制方位鍵和TAB鍵使輸入進入此控件通過應 452 /// </summary> 453 WM_GETDLGCODE = 0x87, 454 /// <summary> 455 /// 當光標在一個窗口的非客戶區內移動時發送此消息給這個窗口 非客戶區為:窗體的標題欄及窗 的邊框體 456 /// </summary> 457 WM_NCMOUSEMOVE = 0xA0, 458 /// <summary> 459 /// 當光標在一個窗口的非客戶區同時按下鼠標左鍵時提交此消息 460 /// </summary> 461 WM_NCLBUTTONDOWN = 0xA1, 462 /// <summary> 463 /// 當用戶釋放鼠標左鍵同時光標某個窗口在非客戶區十發送此消息 464 /// </summary> 465 WM_NCLBUTTONUP = 0xA2, 466 /// <summary> 467 /// 當用戶雙擊鼠標左鍵同時光標某個窗口在非客戶區十發送此消息 468 /// </summary> 469 WM_NCLBUTTONDBLCLK = 0xA3, 470 /// <summary> 471 /// 當用戶按下鼠標右鍵同時光標又在窗口的非客戶區時發送此消息 472 /// </summary> 473 WM_NCRBUTTONDOWN = 0xA4, 474 /// <summary> 475 /// 當用戶釋放鼠標右鍵同時光標又在窗口的非客戶區時發送此消息 476 /// </summary> 477 WM_NCRBUTTONUP = 0xA5, 478 /// <summary> 479 /// 當用戶雙擊鼠標右鍵同時光標某個窗口在非客戶區十發送此消息 480 /// </summary> 481 WM_NCRBUTTONDBLCLK = 0xA6, 482 /// <summary> 483 /// 當用戶按下鼠標中鍵同時光標又在窗口的非客戶區時發送此消息 484 /// </summary> 485 WM_NCMBUTTONDOWN = 0xA7, 486 /// <summary> 487 /// 當用戶釋放鼠標中鍵同時光標又在窗口的非客戶區時發送此消息 488 /// </summary> 489 WM_NCMBUTTONUP = 0xA8, 490 /// <summary> 491 /// 當用戶雙擊鼠標中鍵同時光標又在窗口的非客戶區時發送此消息 492 /// </summary> 493 WM_NCMBUTTONDBLCLK = 0xA9 494 } 495 }
而lParam一般被封裝為結構,因消息類型而異,如下的兩個結構分別是鼠標和鍵盤消息的lParam結構:
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Runtime.InteropServices; 5 6 namespace HookINCS 7 { 8 /// <summary> 9 /// 聲明鼠標鈎子的封送結構類型 10 /// </summary> 11 [StructLayout(LayoutKind.Sequential)] 12 public class MOUSEHOOKSTRUCT 13 { 14 15 /// <summary> 16 /// POINT結構對象,保存鼠標在屏幕上的x,y坐標 17 /// </summary> 18 public POINT pt; 19 /// <summary> 20 /// 接收到鼠標消息的窗口的句柄 21 /// </summary> 22 public IntPtr hWnd; 23 /// <summary> 24 /// hit-test值,詳細描述參見WM_NCHITTEST消息 25 /// </summary> 26 public int wHitTestCode; 27 /// <summary> 28 /// 指定與本消息聯系的額外消息 29 /// </summary> 30 public int dwExtraInfo; 31 } 32 }
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Runtime.InteropServices; 5 6 namespace HookINCS 7 { 8 /// <summary> 9 /// 鍵盤Hook結構函數 10 /// 即鈎子發揮作用時能夠得到的一些參數 11 /// </summary> 12 [StructLayout(LayoutKind.Sequential)] 13 public class KBDLLHOOKSTRUCT 14 { 15 /// <summary> 16 /// 虛擬按鍵碼(1--254) 17 /// </summary> 18 public int vkCode; 19 /// <summary> 20 /// 硬件按鍵掃描碼 21 /// </summary> 22 public int scanCode; 23 /// <summary> 24 /// 鍵按下:128 抬起:0 25 /// </summary> 26 public int flags; 27 /// <summary> 28 /// 消息時間戳間 29 /// </summary> 30 public int time; 31 /// <summary> 32 /// 額外信息 33 /// </summary> 34 public int dwExtraInfo; 35 } 36 }
當我們了解了以上信息時,我們就基本了解了鈎子函數的C#實現了,然后注意幾個問題就好:
1.鈎子對資源占用很多,不用時應及時取消掉,這個需要使用UnhookWindowsHookEx函數
2.處於禮貌,鈎子應返回下一個鈎子的處理結果,而不是單一地將當前鈎子的處理結果返回(使用CallNextHookEx調用下一個鈎子,由於鈎子是先設置后生效,所以應該如此來保證鈎子鏈的正常傳遞)
3.鈎子函數參數中的hInstance是只當前鈎子的回調函數在哪兒,一定要給出正確地址
4.因為使用了委托,應該保證委托的內存地址(對方法的引用)不會垃圾回收,否則在鈎子執行時會出現異常
大家可以下載我寫的示例程序,不過我的程序有以下的地方需要主要:
1.我將鈎子的實現屏蔽了,只對外開放了鍵盤和鼠標的消息攔截和處理(使用方式和C#的WinForm鼠標鍵盤事件一致),你可以開放出來其它的
2.我將鈎子設計為單例模式,你可以取消
3.由於對操作系統有一定侵入,殺毒軟件可能會報出有風險
4.代碼寫得很糟糕,湊合着看吧……
最后說一句:編程技術和語言的關系不大,語言的區別在於它們的編譯器和他們的使用者,使用C的人不能說一定比使用Java的高等,而技術也不一定體現在指針、矩陣、數據結構上,只要對計算機原理、編譯原理、操作系統原理等了解的人都明白。當然,大家有自己的喜歡的語言和慣用的編程手法固然是很好的。
PS:要找工作了,沒經驗沒學歷……
(最后編輯時間2012-12-28 22:33:47)