Ring3层的鼠标和键盘Hook
Hook 是一种拦截特定类型消息的方法,例如注册一个全局鼠标 Hook ,就可以在事件发生前截取到这个消息。
Windows 中的 Hook 大概分为……这么多吧。
虽说C#是托管语言,不可以通过本身的类库和方法去创建钩子的,但是调用非托管类库还是没有问题的。我们可以使用 DllImport 引用非托管类库中的方法。安装钩子的方法(SetWindowsHookEx)的类库在 user32.dll 文件中,函数的原型如下:
1 HHOOK WINAPI SetWindowsHookEx(
2 _In_ int idHook,
3 _In_ HOOKPROC lpfn,
4 _In_ HINSTANCE hInstance,
5 _In_ DWORD dwThreadId
6 );
返回值为钩子的句柄,这个句柄很重要,卸载钩子时要用。在C#中应这样声明:
1 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
2 private static extern IntPtr SetWindowsHookEx(
3 int idHook, //钩子的类型
4 HookProc lpfn, //引发钩子时的回调函数
5 IntPtr hInstance, //应用程序的实例句柄
6 int dwThreadId); //要监听的线程的ID
有安装也当然有卸载,原型为:
1 BOOL WINAPI UnhookWindowsHookEx(
2 _In_ HHOOK hhk
3 );
C#中声明:
1 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
2 private static extern bool UnhookWindowsHookEx(
3 IntPtr idHook); //要卸载钩子的句柄
SetWindowsHookEx第一个参数为钩子类型,一共有 15 种钩子,这里只给出鼠标和键盘钩子,详细下载代码:
1 /// <summary>
2 /// 监视输入到线程消息队列中的键盘消息
3 /// </summary>
4 int WH_KEYBOARD_ALL = 13,
5
6 /// <summary>
7 /// 监视输入到线程消息队列中的鼠标消息
8 /// </summary>
9 int WH_MOUSE_ALL = 14
SetWindowsHookEx 还有一个回调函数 HookProc ,需要我们去声明:
1 private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
回调函数中的 nCode 表示前端(或前一个)钩子(钩子可以有多个,但一般不这样做,因为很耗性能,除非你是搞破坏的)传回来的参数,0 表示消息被废弃,非0表示消息仍然有效。wParam 表示消息类型,一共有 N 多种消息,这里就不一一列举了,自己看代码。lParam 返回消息结构的句柄,通过 Marshal.PtrToStructure 方法获得消息结构,键盘和鼠标消息结构分别为:
1 /// <summary>
2 /// 键盘消息结构
3 /// </summary>
4 [StructLayout(LayoutKind.Sequential)]
5 public class KeyboardHookStruct
6 {
7 /// <summary>
8 /// 定一个虚拟键码。该代码必须有一个价值的范围1至254
9 /// </summary>
10 public int vkCode;
11 /// <summary>
12 /// 指定的硬件扫描码
13 /// </summary>
14 public int scanCode;
15 /// <summary>
16 /// 键标志
17 /// </summary>
18 public int flags;
19 /// <summary>
20 /// 消息时间戳间
21 /// </summary>
22 public int time;
23 /// <summary>
24 /// 指定额外信息相关的信息
25 /// </summary>
26 public int dwExtraInfo;
27 }
28
29 /// <summary>
30 /// 鼠标消息结构
31 /// </summary>
32 [StructLayout(LayoutKind.Sequential)]
33 public class MouseHookStruct
34 {
35 /// <summary>
36 /// POINT结构对象,保存鼠标在屏幕上的x,y坐标
37 /// </summary>
38 public Point pt;
39 /// <summary>
40 /// 接收到鼠标消息的窗口的句柄
41 /// </summary>
42 public IntPtr hWnd;
43 /// <summary>
44 /// hit-test值,详细描述参见WM_NCHITTEST消息
45 /// </summary>
46 public int wHitTestCode;
47 /// <summary>
48 /// 指定与本消息联系的额外消息
49 /// </summary>
50 public int dwExtraInfo;
51 }
SetWindowsHookEx 中的 hInstance 为应用程序的实例句柄,所以得再声明一个 API 函数:
1 [DllImport("kernel32.dll")]
2 private static extern IntPtr GetModuleHandle(
3 string name); //要获取句柄的线程的名字
万事俱备,仅需要两个安装钩子和两个卸载钩子的函数。
1 /// <summary>
2 /// 安装键盘钩子
3 /// </summary>
4 private void InsertKeyBoardHook()
5 {
6 if (KeyBoardHookPtr == IntPtr.Zero)
7 {
8 KeyboardHookProcedure = new HookProc(KeyboardHookProc);
9 KeyBoardHookPtr = SetWindowsHookEx((int)HookType.WH_KEYBOARD_ALL, KeyboardHookProcedure, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0); //使用全局钩子时,线程号为0
10 if (KeyBoardHookPtr == IntPtr.Zero) throw new Exception("安装钩子失败!");
11 }
12 }
13
14 /// <summary>
15 /// 卸载键盘钩子
16 /// </summary>
17 private void DeleteKeyBoardHook()
18 {
19 if (KeyBoardHookPtr == IntPtr.Zero) return;
20 if (!UnhookWindowsHookEx(KeyBoardHookPtr)) throw new Exception("卸载钩子失败!");
21 KeyBoardHookPtr = IntPtr.Zero;
22 }
23
24
25 /// <summary>
26 /// 安装鼠标钩子
27 /// </summary>
28 private void InsertMouseHook()
29 {
30 if (MouseHookPtr == IntPtr.Zero)
31 {
32 MouseHookProcedure = new HookProc(MouseHookProc);
33 MouseHookPtr = SetWindowsHookEx((int)HookType.WH_MOUSE_ALL, MouseHookProcedure, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0); //使用全局钩子时,线程号为0
34 if (MouseHookPtr == IntPtr.Zero) throw new Exception("安装钩子失败!");
35 }
36 }
37
38 /// <summary>
39 /// 卸载鼠标钩子
40 /// </summary>
41 private void DeleteMouseHook()
42 {
43 if (MouseHookPtr == IntPtr.Zero) return;
44 if (!UnhookWindowsHookEx(MouseHookPtr)) throw new Exception("卸载钩子失败!");
45 MouseHookPtr = IntPtr.Zero;
46 }
这里给两个回掉函数例子:
键盘钩子的回调函数,当按下数字键(数字键盘)就弹出提示框:
1 private int KeyboardHookProc(int nCode, int wParam, IntPtr lParam)
2 {
3 if ((nCode >= 0) && (wParam == (int)MsgType.WM_KEYDOWN))
4 {
5 KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
6 switch ((Keys)MyKeyboardHookStruct.vkCode)
7 {
8 case Keys.NumPad0:
9 case Keys.NumPad1:
10 case Keys.NumPad2:
11 case Keys.NumPad3:
12 case Keys.NumPad4:
13 case Keys.NumPad5:
14 case Keys.NumPad6:
15 case Keys.NumPad7:
16 case Keys.NumPad8:
17 case Keys.NumPad9:
18
19 System.Windows.MessageBox.Show("你按下了数字键"); break;
20 }
21 }
22 return 0; //0为废弃消息,非0为继续传递消息
23 }
鼠标钩子的回调函数,当按下右键就弹出提示框,并显明按下时的位置:
1 private int MouseHookProc(int nCode, int wParam, IntPtr lParam)
2 {
3 if ((nCode >= 0) && (wParam == (int)MsgType.WM_RBUTTONDOWN))
4 {
5 MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
6 label1.Content = "你按下了鼠标右键,位置在:x " + MyMouseHookStruct.pt.X + " y " + MyMouseHookStruct.pt.Y; //label1为一个label控件
7 }
8 return 0;
9 }
代码下载 密码为:sy4u (VS2015项目)