C#编程技巧之钩子函数的使用——SetWindowsHookEx

文章来源:http://www.cnblogs.com/Johness/archive/2012/12/28/2837977.html


总所周知: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与安装的钩子子程相关联的线程的标识符


  钩子的类型有以下几种:

using System;
using System.Collections.Generic;
using System.Text;

namespace HookINCS
{
    /// 
    /// 设置的钩子类型
    /// 
    public enum HookType : int
    {
        /// 
        /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 
        ///条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 
        ///WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 
        ///过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook 
        ///监视所有应用程序消息。 
        /// 
        ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 
        ///过滤消息,这等价于在主消息循环中过滤消息。 
        ///    
        ///通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 
        ///个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 
        ///环里一样
        /// 
        WH_MSGFILTER = -1,
        /// 
        /// WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这 
        ///个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook 
        ///来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样 
        ///使用。WH_JOURNALRECORD是system-wide local hooks,它们不会被注射到任何行 
        ///程地址空间
        /// 
        WH_JOURNALRECORD = 0,
        /// 
        /// WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可 
        ///以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠 
        ///标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘 
        ///事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定 
        ///Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处 
        ///理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实 
        ///时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它们不会被 
        ///注射到任何行程地址空间
        /// 
        WH_JOURNALPLAYBACK = 1,
        /// 
        /// 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and  
        ///WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使 
        ///用这个Hook来监视输入到消息队列中的键盘消息
        /// 
        WH_KEYBOARD = 2,
        /// 
        /// 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函 
        ///数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及 
        ///其它发送到消息队列中的消息
        /// 
        WH_GETMESSAGE = 3,
        /// 
        /// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之前调用
        /// 
        WH_CALLWNDPROC = 4,
        /// 
        /// 在以下事件之前,系统都会调用WH_CBT Hook子过程,这些事件包括: 
        ///1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 
        ///2. 完成系统指令; 
        ///3. 来自系统消息队列中的移动鼠标,键盘事件; 
        ///4. 设置输入焦点事件; 
        ///5. 同步系统消息队列事件。
        ///Hook子过程的返回值确定系统是否允许或者防止这些操作中的一个
        /// 
        WH_CBT = 5,
        /// 
        /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 
        ///条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 
        ///WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 
        ///过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook 
        ///监视所有应用程序消息。 
        /// 
        ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 
        ///过滤消息,这等价于在主消息循环中过滤消息。 
        ///    
        ///通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 
        ///个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 
        ///环里一样
        /// 
        WH_SYSMSGFILTER = 6,
        /// 
        /// WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。 
        ///使用这个Hook监视输入到消息队列中的鼠标消息
        /// 
        WH_MOUSE = 7,
        /// 
        /// 当调用GetMessage 或 PeekMessage 来从消息队列种查询非鼠标、键盘消息时
        /// 
        WH_HARDWARE = 8,
        /// 
        /// 在系统调用系统中与其它Hook关联的Hook子过程之前,系统会调用 
        ///WH_DEBUG Hook子过程。你可以使用这个Hook来决定是否允许系统调用与其它 
        ///Hook关联的Hook子过程
        /// 
        WH_DEBUG = 9,
        /// 
        /// 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是 
        ///激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子过程。 
        ///WH_SHELL 共有5钟情况: 
        ///1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁; 
        ///2. 当Taskbar需要重画某个按钮; 
        ///3. 当系统需要显示关于Taskbar的一个程序的最小化形式; 
        ///4. 当目前的键盘布局状态改变; 
        ///5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。 
        ///
        ///按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接 
        ///收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自 
        ///己
        /// 
        WH_SHELL = 10,
        /// 
        /// 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE  
        ///Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就 
        ///会调用WH_FOREGROUNDIDLE Hook子过程
        /// 
        WH_FOREGROUNDIDLE = 11,
        /// 
        /// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之后调用
        /// 
        WH_CALLWNDPROCRET = 12,
        /// 
        /// 监视输入到线程消息队列中的键盘消息
        /// 
        WH_KEYBOARD_LL = 13,
        /// 
        /// 监视输入到线程消息队列中的鼠标消息
        /// 
        WH_MOUSE_LL = 14
    }
}

我们一般会使用13拦截键盘消息,14拦截鼠标消息。


  回调函数的声明我们在C#里需要用到委托,声明如下:
    public delegate int HookProc(int nCode, int wParam, IntPtr lParam);


  从上而下参数意义为:nCode钩子链传递回来的参数,0表示此消息(被之前的消息钩子)丢弃,非0表示此消息继续有效


            wParam消息参数


            lParam消息参数


  值得一提的是wParam和lParam在不同的消息类型中是不一样的类型,不过wParam的类型大概可以用下面的枚举表示:

using System;
using System.Collections.Generic;
using System.Text;

namespace HookINCS
{
    /// 
    /// 消息类型
    /// 作为SendMessage和PostMessage的参数
    /// 
    public enum MsgType : uint
    {
        WM_KEYFIRST = 0x0100,

        //Msg参数常量值:
        /// 
        /// 按下一个键
        /// 
        WM_KEYDOWN = 0x0100,
        /// 
        /// 释放一个键
        /// 
        WM_KEYUP = 0x0101,
        /// 
        /// 按下某键,并已发出WM_KEYDOWN, WM_KEYUP消息
        /// 
        WM_CHAR = 0x102,
        /// 
        /// 当用translatemessage函数翻译WM_KEYUP消息时发送此消息给拥有焦点的窗口
        /// 
        WM_DEADCHAR = 0x103,
        /// 
        /// 当用户按住ALT键同时按下其它键时提交此消息给拥有焦点的窗口
        /// 
        WM_SYSKEYDOWN = 0x104,
        /// 
        /// 当用户释放一个键同时ALT 键还按着时提交此消息给拥有焦点的窗口
        /// 
        WM_SYSKEYUP = 0x105,
        /// 
        /// 当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后提交此消息给拥有焦点的窗口
        /// 
        WM_SYSCHAR = 0x106,
        /// 
        /// 当WM_SYSKEYDOWN消息被TRANSLATEMESSAGE函数翻译后发送此消息给拥有焦点的窗口
        /// 
        WM_SYSDEADCHAR = 0x107,
        /// 
        /// 在一个对话框程序被显示前发送此消息给它,通常用此消息初始化控件和执行其它任务
        /// 
        WM_INITDIALOG = 0x110,
        /// 
        /// 当用户选择一条菜单命令项或当某个控件发送一条消息给它的父窗口,一个快捷键被翻译
        /// 
        WM_COMMAND = 0x111,
        /// 
        /// 当用户选择窗口菜单的一条命令或//当用户选择最大化或最小化时那个窗口会收到此消息
        /// 
        WM_SYSCOMMAND = 0x112,
        /// 
        /// 发生了定时器事件
        /// 
        WM_TIMER = 0x113,
        /// 
        /// 当一个窗口标准水平滚动条产生一个滚动事件时发送此消息给那个窗口,也发送给拥有它的控件
        /// 
        WM_HSCROLL = 0x114,
        /// 
        /// 当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口也,发送给拥有它的控件
        /// 
        WM_VSCROLL = 0x115,
        /// 
        /// 当一个菜单将要被激活时发送此消息,它发生在用户菜单条中的某项或按下某个菜单键,它允许程序在显示前更改菜单
        /// 
        WM_INITMENU = 0x116,
        /// 
        /// 当一个下拉菜单或子菜单将要被激活时发送此消息,它允许程序在它显示前更改菜单,而不要改变全部
        /// 
        WM_INITMENUPOPUP = 0x117,
        /// 
        /// 当用户选择一条菜单项时发送此消息给菜单的所有者(一般是窗口)
        /// 
        WM_MENUSELECT = 0x11F,
        /// 
        /// 当菜单已被激活用户按下了某个键(不同于加速键),发送此消息给菜单的所有者
        /// 
        WM_MENUCHAR = 0x120,
        /// 
        /// 当一个模态对话框或菜单进入空载状态时发送此消息给它的所有者,一个模态对话框或菜单进入空载状态就是在处理完一条或几条先前的消息后没有消息它的列队中等待
        /// 
        WM_ENTERIDLE = 0x121,
        /// 
        /// 在windows绘制消息框前发送此消息给消息框的所有者窗口,通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置消息框的文本和背景颜色
        /// 
        WM_CTLCOLORMSGBOX = 0x132,
        /// 
        /// 当一个编辑型控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置编辑框的文本和背景颜色
        /// 
        WM_CTLCOLOREDIT = 0x133,

        /// 
        /// 当一个列表框控件将要被绘制前发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置列表框的文本和背景颜色
        /// 
        WM_CTLCOLORLISTBOX = 0x134,
        /// 
        /// 当一个按钮控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置按纽的文本和背景颜色
        /// 
        WM_CTLCOLORBTN = 0x135,
        /// 
        /// 当一个对话框控件将要被绘制前发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置对话框的文本背景颜色
        /// 
        WM_CTLCOLORDLG = 0x136,
        /// 
        /// 当一个滚动条控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以通过使用给定的相关显示设备的句柄来设置滚动条的背景颜色
        /// 
        WM_CTLCOLORSCROLLBAR = 0x137,
        /// 
        /// 当一个静态控件将要被绘制时发送此消息给它的父窗口通过响应这条消息,所有者窗口可以 通过使用给定的相关显示设备的句柄来设置静态控件的文本和背景颜色
        /// 
        WM_CTLCOLORSTATIC = 0x138,
        /// 
        /// 当鼠标轮子转动时发送此消息个当前有焦点的控件
        /// 
        WM_MOUSEWHEEL = 0x20A,
        /// 
        /// 双击鼠标中键
        /// 
        WM_MBUTTONDBLCLK = 0x209,
        /// 
        /// 释放鼠标中键
        /// 
        WM_MBUTTONUP = 0x208,
        /// 
        /// 移动鼠标时发生,同WM_MOUSEFIRST
        /// 
        WM_MOUSEMOVE = 0x200,
        /// 
        /// 按下鼠标左键
        /// 
        WM_LBUTTONDOWN = 0x201,
        /// 
        /// 释放鼠标左键
        /// 
        WM_LBUTTONUP = 0x202,
        /// 
        /// 双击鼠标左键
        /// 
        WM_LBUTTONDBLCLK = 0x203,
        /// 
        /// 按下鼠标右键
        /// 
        WM_RBUTTONDOWN = 0x204,
        /// 
        /// 释放鼠标右键
        /// 
        WM_RBUTTONUP = 0x205,
        /// 
        /// 双击鼠标右键
        /// 
        WM_RBUTTONDBLCLK = 0x206,
        /// 
        /// 按下鼠标中键
        /// 
        WM_MBUTTONDOWN = 0x207,

        //WM_USER = 0x0400;
        //public static int MK_LBUTTON = 0x0001;
        //public static int MK_RBUTTON = 0x0002;
        //public static int MK_SHIFT = 0x0004;
        //public static int MK_CONTROL = 0x0008;
        //public static int MK_MBUTTON = 0x0010;
        //public static int MK_XBUTTON1 = 0x0020;
        //public static int MK_XBUTTON2 = 0x0040;
        /// 
        /// 创建一个窗口
        /// 
        WM_CREATE = 0x01,
        /// 
        /// 当一个窗口被破坏时发送
        /// 
        WM_DESTROY = 0x02,
        /// 
        /// 移动一个窗口
        /// 
        WM_MOVE = 0x03,
        /// 
        /// 改变一个窗口的大小
        /// 
        WM_SIZE = 0x05,
        /// 
        /// 一个窗口被激活或失去激活状态
        /// 
        WM_ACTIVATE = 0x06,
        /// 
        /// 一个窗口获得焦点
        /// 
        WM_SETFOCUS = 0x07,
        /// 
        /// 一个窗口失去焦点
        /// 
        WM_KILLFOCUS = 0x08,
        /// 
        /// 一个窗口改变成Enable状态
        /// 
        WM_ENABLE = 0x0A,
        /// 
        /// 设置窗口是否能重画
        /// 
        WM_SETREDRAW = 0x0B,
        /// 
        /// 应用程序发送此消息来设置一个窗口的文本
        /// 
        WM_SETTEXT = 0x0C,
        /// 
        /// 应用程序发送此消息来复制对应窗口的文本到缓冲区
        /// 
        WM_GETTEXT = 0x0D,
        /// 
        /// 得到与一个窗口有关的文本的长度(不包含空字符)
        /// 
        WM_GETTEXTLENGTH = 0x0E,
        /// 
        /// 要求一个窗口重画自己
        /// 
        WM_PAINT = 0x0F,
        /// 
        /// 当一个窗口或应用程序要关闭时发送一个信号
        /// 
        WM_CLOSE = 0x10,
        /// 
        /// 当用户选择结束对话框或程序自己调用ExitWindows函数
        /// 
        WM_QUERYENDSESSION = 0x11,
        /// 
        /// 用来结束程序运行
        /// 
        WM_QUIT = 0x12,
        /// 
        /// 当用户窗口恢复以前的大小位置时,把此消息发送给某个图标
        /// 
        WM_QUERYOPEN = 0x13,
        /// 
        /// 当窗口背景必须被擦除时(例在窗口改变大小时)
        /// 
        WM_ERASEBKGND = 0x14,
        /// 
        /// 当系统颜色改变时,发送此消息给所有顶级窗口
        /// 
        WM_SYSCOLORCHANGE = 0x15,
        /// 
        /// 当系统进程发出WM_QUERYENDSESSION消息后,此消息发送给应用程序,通知它对话是否结束
        /// 
        WM_ENDSESSION = 0x16,
        /// 
        /// 当隐藏或显示窗口是发送此消息给这个窗口
        /// 
        WM_SHOWWINDOW = 0x18,
        /// 
        /// 发此消息给应用程序哪个窗口是激活的,哪个是非激活的
        /// 
        WM_ACTIVATEAPP = 0x1C,
        /// 
        /// 当系统的字体资源库变化时发送此消息给所有顶级窗口
        /// 
        WM_FONTCHANGE = 0x1D,
        /// 
        /// 当系统的时间变化时发送此消息给所有顶级窗口
        /// 
        WM_TIMECHANGE = 0x1E,
        /// 
        /// 发送此消息来取消某种正在进行的摸态(操作)
        /// 
        WM_CANCELMODE = 0x1F,
        /// 
        /// 如果鼠标引起光标在某个窗口中移动且鼠标输入没有被捕获时,就发消息给某个窗口
        /// 
        WM_SETCURSOR = 0x20,
        /// 
        /// 当光标在某个非激活的窗口中而用户正按着鼠标的某个键发送此消息给//当前窗口
        /// 
        WM_MOUSEACTIVATE = 0x21,
        /// 
        /// 发送此消息给MDI子窗口//当用户点击此窗口的标题栏,或//当窗口被激活,移动,改变大小
        /// 
        WM_CHILDACTIVATE = 0x22,
        /// 
        /// 此消息由基于计算机的训练程序发送,通过WH_JOURNALPALYBACK的hook程序分离出用户输入消息
        /// 
        WM_QUEUESYNC = 0x23,
        /// 
        /// 此消息发送给窗口当它将要改变大小或位置
        /// 
        WM_GETMINMAXINFO = 0x24,
        /// 
        /// 发送给最小化窗口当它图标将要被重画
        /// 
        WM_PAINTICON = 0x26,
        /// 
        /// 此消息发送给某个最小化窗口,仅//当它在画图标前它的背景必须被重画
        /// 
        WM_ICONERASEBKGND = 0x27,
        /// 
        /// 发送此消息给一个对话框程序去更改焦点位置
        /// 
        WM_NEXTDLGCTL = 0x28,
        /// 
        /// 每当打印管理列队增加或减少一条作业时发出此消息 
        /// 
        WM_SPOOLERSTATUS = 0x2A,
        /// 
        /// 当button,combobox,listbox,menu的可视外观改变时发送
        /// 
        WM_DRAWITEM = 0x2B,
        /// 
        /// 当button, combo box, list box, list view control, or menu item 被创建时
        /// 
        WM_MEASUREITEM = 0x2C,
        /// 
        /// 此消息有一个LBS_WANTKEYBOARDINPUT风格的发出给它的所有者来响应WM_KEYDOWN消息 
        /// 
        WM_VKEYTOITEM = 0x2E,
        /// 
        /// 此消息由一个LBS_WANTKEYBOARDINPUT风格的列表框发送给他的所有者来响应WM_CHAR消息 
        /// 
        WM_CHARTOITEM = 0x2F,
        /// 
        /// 当绘制文本时程序发送此消息得到控件要用的颜色
        /// 
        WM_SETFONT = 0x30,
        /// 
        /// 应用程序发送此消息得到当前控件绘制文本的字体
        /// 
        WM_GETFONT = 0x31,
        /// 
        /// 应用程序发送此消息让一个窗口与一个热键相关连 
        /// 
        WM_SETHOTKEY = 0x32,
        /// 
        /// 应用程序发送此消息来判断热键与某个窗口是否有关联
        /// 
        WM_GETHOTKEY = 0x33,
        /// 
        /// 此消息发送给最小化窗口,当此窗口将要被拖放而它的类中没有定义图标,应用程序能返回一个图标或光标的句柄,当用户拖放图标时系统显示这个图标或光标
        /// 
        WM_QUERYDRAGICON = 0x37,
        /// 
        /// 发送此消息来判定combobox或listbox新增加的项的相对位置
        /// 
        WM_COMPAREITEM = 0x39,
        /// 
        /// 显示内存已经很少了
        /// 
        WM_COMPACTING = 0x41,
        /// 
        /// 发送此消息给那个窗口的大小和位置将要被改变时,来调用setwindowpos函数或其它窗口管理函数
        /// 
        WM_WINDOWPOSCHANGING = 0x46,
        /// 
        /// 发送此消息给那个窗口的大小和位置已经被改变时,来调用setwindowpos函数或其它窗口管理函数
        /// 
        WM_WINDOWPOSCHANGED = 0x47,
        /// 
        /// 当系统将要进入暂停状态时发送此消息
        /// 
        WM_POWER = 0x48,
        /// 
        /// 当一个应用程序传递数据给另一个应用程序时发送此消息
        /// 
        WM_COPYDATA = 0x4A,
        /// 
        /// 当某个用户取消程序日志激活状态,提交此消息给程序
        /// 
        WM_CANCELJOURNA = 0x4B,
        /// 
        /// 当某个控件的某个事件已经发生或这个控件需要得到一些信息时,发送此消息给它的父窗口 
        /// 
        WM_NOTIFY = 0x4E,
        /// 
        /// 当用户选择某种输入语言,或输入语言的热键改变
        /// 
        WM_INPUTLANGCHANGEREQUEST = 0x50,
        /// 
        /// 当平台现场已经被改变后发送此消息给受影响的最顶级窗口
        /// 
        WM_INPUTLANGCHANGE = 0x51,
        /// 
        /// 当程序已经初始化windows帮助例程时发送此消息给应用程序
        /// 
        WM_TCARD = 0x52,
        /// 
        /// 此消息显示用户按下了F1,如果某个菜单是激活的,就发送此消息个此窗口关联的菜单,否则就发送给有焦点的窗口,如果//当前都没有焦点,就把此消息发送给//当前激活的窗口
        /// 
        WM_HELP = 0x53,
        /// 
        /// 当用户已经登入或退出后发送此消息给所有的窗口,//当用户登入或退出时系统更新用户的具体设置信息,在用户更新设置时系统马上发送此消息
        /// 
        WM_USERCHANGED = 0x54,
        /// 
        /// 公用控件,自定义控件和他们的父窗口通过此消息来判断控件是使用ANSI还是UNICODE结构
        /// 
        WM_NOTIFYFORMAT = 0x55,
        /// 
        /// 当用户某个窗口中点击了一下右键就发送此消息给这个窗口
        /// 
        WM_CONTEXTMENU = 0x7B,
        /// 
        /// 当调用SETWINDOWLONG函数将要改变一个或多个 窗口的风格时发送此消息给那个窗口
        /// 
        WM_STYLECHANGING = 0x7C,
        /// 
        /// 当调用SETWINDOWLONG函数一个或多个 窗口的风格后发送此消息给那个窗口
        /// 
        WM_STYLECHANGED = 0x7D,
        /// 
        /// 当显示器的分辨率改变后发送此消息给所有的窗口
        /// 
        WM_DISPLAYCHANGE = 0x7E,
        /// 
        /// 此消息发送给某个窗口来返回与某个窗口有关连的大图标或小图标的句柄
        /// 
        WM_GETICON = 0x7F,
        /// 
        /// 程序发送此消息让一个新的大图标或小图标与某个窗口关联
        /// 
        WM_SETICON = 0x80,
        /// 
        /// 当某个窗口第一次被创建时,此消息在WM_CREATE消息发送前发送
        /// 
        WM_NCCREATE = 0x81,
        /// 
        /// 此消息通知某个窗口,非客户区正在销毁 
        /// 
        WM_NCDESTROY = 0x82,
        /// 
        /// 当某个窗口的客户区域必须被核算时发送此消息
        /// 
        WM_NCCALCSIZE = 0x83,
        /// 
        /// 移动鼠标,按住或释放鼠标时发生
        /// 
        WM_NCHITTEST = 0x84,
        /// 
        /// 程序发送此消息给某个窗口当它(窗口)的框架必须被绘制时
        /// 
        WM_NCPAINT = 0x85,
        /// 
        /// 此消息发送给某个窗口仅当它的非客户区需要被改变来显示是激活还是非激活状态
        /// 
        WM_NCACTIVATE = 0x86,
        /// 
        /// 发送此消息给某个与对话框程序关联的控件,widdows控制方位键和TAB键使输入进入此控件通过应
        /// 
        WM_GETDLGCODE = 0x87,
        /// 
        /// 当光标在一个窗口的非客户区内移动时发送此消息给这个窗口 非客户区为:窗体的标题栏及窗 的边框体
        /// 
        WM_NCMOUSEMOVE = 0xA0,
        /// 
        /// 当光标在一个窗口的非客户区同时按下鼠标左键时提交此消息
        /// 
        WM_NCLBUTTONDOWN = 0xA1,
        /// 
        /// 当用户释放鼠标左键同时光标某个窗口在非客户区十发送此消息
        /// 
        WM_NCLBUTTONUP = 0xA2,
        /// 
        /// 当用户双击鼠标左键同时光标某个窗口在非客户区十发送此消息
        /// 
        WM_NCLBUTTONDBLCLK = 0xA3,
        /// 
        /// 当用户按下鼠标右键同时光标又在窗口的非客户区时发送此消息
        /// 
        WM_NCRBUTTONDOWN = 0xA4,
        /// 
        /// 当用户释放鼠标右键同时光标又在窗口的非客户区时发送此消息
        /// 
        WM_NCRBUTTONUP = 0xA5,
        /// 
        /// 当用户双击鼠标右键同时光标某个窗口在非客户区十发送此消息
        /// 
        WM_NCRBUTTONDBLCLK = 0xA6,
        /// 
        /// 当用户按下鼠标中键同时光标又在窗口的非客户区时发送此消息
        /// 
        WM_NCMBUTTONDOWN = 0xA7,
        /// 
        /// 当用户释放鼠标中键同时光标又在窗口的非客户区时发送此消息
        /// 
        WM_NCMBUTTONUP = 0xA8,
        /// 
        /// 当用户双击鼠标中键同时光标又在窗口的非客户区时发送此消息
        /// 
        WM_NCMBUTTONDBLCLK = 0xA9
    }
}
而lParam一般被封装为结构,因消息类型而异,如下的两个结构分别是鼠标和键盘消息的lParam结构:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace HookINCS
{
    /// 
    /// 声明鼠标钩子的封送结构类型
    /// 
    [StructLayout(LayoutKind.Sequential)]
    public class MOUSEHOOKSTRUCT
    {

        /// 
        /// POINT结构对象,保存鼠标在屏幕上的x,y坐标
        /// 
        public POINT pt;
        /// 
        /// 接收到鼠标消息的窗口的句柄
        /// 
        public IntPtr hWnd;
        /// 
        /// hit-test值,详细描述参见WM_NCHITTEST消息
        /// 
        public int wHitTestCode;
        /// 
        /// 指定与本消息联系的额外消息
        /// 
        public int dwExtraInfo;
    }
}

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace HookINCS
{
    /// 
    /// 键盘Hook结构函数
    /// 即钩子发挥作用时能够得到的一些参数
    /// 
    [StructLayout(LayoutKind.Sequential)]
    public class KBDLLHOOKSTRUCT
    {
        /// 
        /// 虚拟按键码(1--254)
        /// 
        public int vkCode;
        /// 
        /// 硬件按键扫描码
        /// 
        public int scanCode;
        /// 
        /// 键按下:128 抬起:0
        /// 
        public int flags;
        /// 
        /// 消息时间戳间
        /// 
        public int time;
        /// 
        /// 额外信息
        /// 
        public int dwExtraInfo;
    }
}

当我们了解了以上信息时,我们就基本了解了钩子函数的C#实现了,然后注意几个问题就好:


    1.钩子对资源占用很多,不用时应及时取消掉,这个需要使用UnhookWindowsHookEx函数


    2.处于礼貌,钩子应返回下一个钩子的处理结果,而不是单一地将当前钩子的处理结果返回(使用CallNextHookEx调用下一个钩子,由于钩子是先设置后生效,所以应该如此来保证钩子链的正常传递)


    3.钩子函数参数中的hInstance是只当前钩子的回调函数在哪儿,一定要给出正确地址


    4.因为使用了委托,应该保证委托的内存地址(对方法的引用)不会垃圾回收,否则在钩子执行时会出现异常


你可能感兴趣的:(C#)