趁空闲的时间,对键盘钩子进行了学习,通过C#这门语言来设计和实现:下面是我设计的类图:
键盘钩子包括两类:全局钩子和私有钩子,这里我分成两个类来设计:
public enum HookType
{
WH_KEYBOARD = 2,//私有钩子
WH_KEYBOARD_LL = 13//全局钩子
}
{
//设置钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(HookType idHook, HOOKPROC lpfn, IntPtr hInstance, int threadId);
//抽调钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("kernel32")]
public static extern int GetCurrentThreadId();
/// <summary>
/// 钩子处理委托
/// </summary>
public HOOKPROC proc;
/// <summary>
/// 钩子类型
/// </summary>
public HookType type;
/// <summary>
/// 钩子的句柄
/// </summary>
public int hHook = 0;
public hook(HOOKPROC proc, HookType type)
{
this.proc = proc;
this.type = type;
}
public abstract int SetWindowsHookEx();
public virtual void UnhookWindowsHookEx()
{
bool retKeyboard = true;
if (hHook != 0)
{
retKeyboard = UnhookWindowsHookEx(hHook);
hHook = 0;
}
if (!retKeyboard)
{
throw new Exception("UnhookWindowsHookEx failed.");
}
}
}
{
public PublicHook(HOOKPROC proc)
: base(proc, HookType.WH_KEYBOARD_LL)
{ }
public override int SetWindowsHookEx()
{
if (hHook == 0)
hHook = SetWindowsHookEx(this.type, this.proc, Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 0);
return hHook;
}
}
public class PrivateHook : hook
{
public PrivateHook(HOOKPROC proc)
: base(proc, HookType.WH_KEYBOARD)
{ }
public override int SetWindowsHookEx()
{
if (hHook == 0)
hHook = SetWindowsHookEx(this.type, this.proc, IntPtr.Zero, GetCurrentThreadId());
return hHook;
}
}
private void btnOpen_Click(object sender, EventArgs e)
{
if (this.comboBox1.SelectedIndex == 0)
hook = new PublicHook(MyKeyboardProc);
else
hook = new PrivateHook(MyKeyboardProc);
int hHook = hook.SetWindowsHookEx();
if (hHook == 0)
{
MessageBox.Show("设置钩子失败!");
}
}
private void btnClose_Click(object sender, EventArgs e)
{
try
{
if (hook != null)
hook.UnhookWindowsHookEx();
}
catch
{
MessageBox.Show("关闭钩子失败!");
}
}
public int MyKeyboardProc(int nCode, int wParm, int lParam)
{
MessageBox.Show("你已经按下了按钮!");
return 0;
}
运行后主界面效果如下图所示:
下面备注一下在使用过程中一些类库说明及注意事项:
1、AppDomain.GetCurrentThreadId()在.net 2.0中过时了,VS2005和VS2008警告这个方法已经过时,建议使用System.Threading.Thread.CurrentThread.ManagedThreadId,但实际上这两个值是不一样的。AppDomain.GetCurrentThreadId()的实际上调用Win32 API,其返回的是该线程在Windows中的ThreadId,即同这个等价:
public static extern int GetCurrentThreadId();
而System.Threading.Thread.CurrentThread.ManagedThreadId返回的是作为一个ManagedThread在.Net CLR中的ThreadId,所以这和Windows的ThreadId是完全不同的。
2、使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个Hook子程需要调用CallNextHookEx函数.函数签名如下:
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
int idHook, // 钩子的类型,即它处理的消息类型
HOOKPROC lpfn, // 钩子子程的地址指针。如果dwThreadId参数为0
// 或是一个由别的进程创建的线程的标识,
// lpfn必须指向DLL中的钩子子程。
// 除此以外,lpfn可以指向当前进程的一段钩子子程代码。
// 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。
HINSTANCE hMod, // 应用程序实例的句柄。标识包含lpfn所指的子程的
DLL。
// 如果dwThreadId 标识当前进程创建的一个线程,
// 而且子程代码位于当前进程,hMod必须为NULL。
// 可以很简单的设定其为本应用程序的实例句柄。
DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识符。
// 如果为0,钩子子程与所有的线程关联,即为全局钩子。
);
函数成功则返回钩子子程的句柄,失败返回NULL。
以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给钩子子程,且被钩子子程先处理。
3、系统钩子和线程钩子:
4、钩子处理函数:public int MyKeyboardProc(int nCode, int wParm, int lParam)
可以添加自己想要的信息处理(如发邮件,增加文件等)
{
if (nCode >= 0)
{
KeyBoardHookStruct kbh = (KeyBoardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyBoardHookStruct));
if (kbh.vkCode == (int)Keys.S && (int)Control.ModifierKeys == (int)Keys.Control) // 截获F8
{
MessageBox.Show("快捷键已拦截!不能保存!");
return 1;
}
if ((int)Control.ModifierKeys == (int)Keys.Delete && (int)Control.ModifierKeys == (int)Keys.Alt && (int)Control.ModifierKeys == (int)Keys.Control)
{
MessageBox.Show("捕捉到Ctrl+Alt+Delete");
return 1;
}
if (kbh.vkCode == (int)Keys.Y
&& (int)Control.ModifierKeys == (int)Keys.Control + (int)Keys.Alt) //截获Ctrl+Alt+Y
{
About msg = new About();
msg.Show();
MessageBox.Show("不能全部保存!");
return 1;
}
if (kbh.vkCode == (int)Keys.A)
{
MessageBox.Show("A");
this.label1.Text += "A";
}
if (kbh.vkCode == (int)Keys.B)
{
MessageBox.Show("B");
this.label1.Text += "B";
}
if (kbh.vkCode == (int)Keys.Enter)
{
this.label1.Text = "执行成功!";
}
if (kbh.vkCode == (int)Keys.Back)
{
this.label1.Text = this.label1.Text.Remove(this.label1.Text.Length - 1);
}
if (kbh.vkCode == (int)Keys.D1)
{
this.label1.Text += "1";
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
注意:如果返回1,则结束消息,这个消息到此为止,不再传递。如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者.
//键盘Hook结构函数(详情查看msdn的Platform SDK中的KBDLLHOOKSTRUCT结构)
[StructLayout(LayoutKind.Sequential)]
public class KeyBoardHookStruct
{
public int vkCode;//表达一个在1到254间的虚拟键盘码
public int scanCode;//表示硬件扫描码
public int flags;
public int time;
public int dwExtraInfo;
}
5、钩子函数执行两次的解决办法:http://www.boluor.com/solution-to-the-keyboard-hook-function-is-executed-twice.html
最后,欢迎朋友们进行指正,谢谢!
Best Reagards,
Charles Chen