本随笔较长,读者可直接到最地下下载示例程序。
总所周知: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)