前两天无意间从一个取色工具GolorSpy的源码里(我回头翻了我的Google Reader始终没有找到那篇文,劳烦作者如果看到了本文可以联系我一下我将会贴上原文地址引用)发现了一个封装了Windows全局钩子方面操作的名为WindowsHooks的库,详细地翻看了源码,觉得相当好用而且易于扩展。于是撰写本文单独介绍该库的使用和扩展方法。文末提供该库的源码(如果该源码不愿意公开请通知我我会主动撤下下载地址)和咱自己写的弱弱的Sample。
1、全局钩子的概念
(以下定义来自MSDN)
A global hook monitors messages for all threads in the same desktop as the calling thread.
一个全局钩子将会监视所有与调用该钩子的线程处在同一个桌面上的所有线程所收到的消息。
Hooks tend to slow down the system because they increase the amount of processing the system must perform for each message. You should install a hook only when necessary, and remove it as soon as possible.
使用钩子往往会降低系统的运行速度,因为这将增加系统处理每个消息的次数。你应当只在需要时才安装一个钩子,并且及时地清除它
简而言之就是这样,每当系统产生一个消息时,全局钩子将会在这个消息被发送到其它订阅该消息的线程之前截获这个消息。也就是说即便创建了该全局钩子的进程在桌面上不处于被激活状态,它依旧可以截获它关注的消息并提前做出一些处理。
其实我们常用的金山词霸、有道词典等的屏幕取词功能,就使用到了钩子技术。
2、WindowsHooks库的基本结构
生成的类图如下:
下面简要介绍一下每个类的功能:
GlobalHook:表示一个全局钩子类,这是一个抽象类。所有用户自己实现的钩子类都必须继承自这个类。GlobalHook类中已经定义好了大部分的方法和属性,用 户自定义钩子类的时候只需要重写HookCallBackProcedure方法来定义处理截获到的消息时要做的工作即可。
WindowsAPIs:这里声明了库里将会使用到的Win32 APIs,扩展时可以不关注。
WindowsMessages:这个类包含了可以处理的各种Windows消息的二进制码,当你要扩展一个自己的钩子类的时候,需要先从这里面选择好你所要截获的事件类型。
WindowsStruct:这个类里包含的是一些可能会用到的Struct类型,主要是为了兼容原有的Win32 APIs中使用到的相关结构类型。注意这里需要加上属性声明说明定义的结构的内存模型按照用户自己定义的字段顺序来排布,否则.Net在编译时为了优化可能会重新排布字段在内存模型中的顺序,这样的话有可能会导致错误。
KeyHooks:这个类定义了一个用来截获键盘按键消息的钩子,继承自GlobalHook,可以看做是一个用户自定义钩子的范例,也可以直接在自己的程序中使用。启动钩子使用父类GlobalHooks的Start方法,结束钩子时使用父类GlobalHooks的Stop方法,一旦截获到了消息则会执行重写的HookCallBackProcedure方法,在这个方法中将会引发KeyUp事件,这里注意KeyHooks的实现中应用了事件处理机制,定义了事件KeyUp,这代表我们在使用时可以直接写好事件处理函数然后让KeyUp订阅该处理过程即可,使用起来很方便。
3、使用KeyHooks截获键盘按键消息
下面我们使用KeyHooks类来做一个可以截获键盘消息并显示按下的是哪个键的Sample范例
首先上界面
创建好解决方案后,别忘了把WindowsHooks的工程引用进来,并且在Sample工程中引用这个工程。
下面我们给frm_MainForm类中加入一个KeyHooks类型的私有成员_KeyHooks
/// 键盘钩子对象
/// </summary>
private KeyHooks _KeyHooks;
然后我们定义窗体的Load事件,以在一开始初始化_KeyHooks对象,并且事先订阅_KeyHooks对象的KeyUp事件。
/// frm_MainForm的Load事件处理函数
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void frm_MainForm_Load(object sender, EventArgs e)
{
_KeyHooks = new KeyHooks();
_KeyHooks.KeyUp += new KeyEventHandler(_KeyHooks_KeyUp);
}
下面我们将窗体中的Button控件所要响应的Click事件定义下。
/// btn_Begin的Click事件处理函数
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Begin_Click(object sender, EventArgs e)
{
if (!_KeyHooks.IsStarted)
{
try
{
_KeyHooks.Start();
this.btn_Begin.Text = "停止捕获键盘消息";
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
else
{
try
{
_KeyHooks.Stop();
this.btn_Begin.Text = "开始捕获键盘消息";
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
/// <summary>
/// btn_Quit的Click事件处理函数
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Quit_Click(object sender, EventArgs e)
{
Application.Exit();
}
我们的需求是这样的,用户点击下“开始捕获键盘消息”按钮后钩子被启动,然后用户无论什么时候(即便该进程在桌面上不是出于被激活到前台的状态)按下键盘上的按键,ListBox中就会显示用户按下的是哪个按键。
为此,我们需要订阅_KeyHooks的KeyUp事件(在Form的Load事件中已订阅过,下面只差方法体),描述当截获到键盘按键的消息(WindowsMessages.WH_KEYBOARD_LL)的时候,要做什么样的处理工作。
具体代码如下:
/// _KeyHooks的KeyUp事件处理函数
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _KeyHooks_KeyUp(object sender, KeyEventArgs e)
{
this.lst_DisplayMessage.Items.Add("刚刚 " + e.KeyData.ToString() + "被按下");
}
当然,由于这个方法体很短,为了节约篇幅和代码量,你也可以这么写(使用Lambda表达式):
/// frm_MainForm的Load事件处理函数
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void frm_MainForm_Load(object sender, EventArgs e)
{
_KeyHooks = new KeyHooks();
//_KeyHooks.KeyUp += new KeyEventHandler(_KeyHooks_KeyUp);
_KeyHooks.KeyUp +=
new KeyEventHandler((s, ea) => { this.lst_DisplayMessage.Items.Add("刚刚 " + ea.KeyData.ToString() + "被按下"); });
}
至此这个简单的Sample就完成了,下面是运行效果(注意右边全局钩子测试的进程不处于被激活状态):
4、结语
限于篇幅本文暂时就先写到这里了,以后用到WindowsHooks的扩展的时候我会再补充扩展出自己的钩子类的方法的文章。
下面是WindowsHooks库和本文的Sample下载地址(Visual Studio 2008下运行通过):
Over
Weatherpop
2010/2/11