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项目)

原文地址:https://www.cnblogs.com/Bita/p/5268522.html