朕大秦始皇帝 發表於 2026-5-2 20:24:00

[C# 笔记] 如何设置消息钩子 (以低级鼠标钩子为例)

<h2 id="简单入门">简单入门</h2>
<h3 id="1-准备函数">1. 准备函数<sup class="footnote-ref"></sup><sup class="footnote-ref"></sup><sup class="footnote-ref"></sup></h3>
<pre><code class="language-Csharp">
// 设置消息钩子
public static extern IntPtr SetWindowsHookExA(int idHook, HookProc lpfn, IntPtr hmod, int dwThreadId);


// 移除消息钩子
public static extern bool UnhookWindowsHookEx(IntPtr idHook);


// 继续运行下一个钩子 (其实是把钩子消息传递给下一个程序)
public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
</code></pre>
<h3 id="2-准备结构体">2. 准备结构体<sup class="footnote-ref"></sup><sup class="footnote-ref"></sup></h3>
<pre><code class="language-csharp">// POINT 结构体
public struct tagPOINT
{
    public int X;
    public int Y;
}

// MSLLHOOKSTRUCT 结构体
public struct tagMSLLHOOKSTRUCT
{
    // 光标的 XY 坐标
    public tagPOINT pt;
    // 鼠标额外数据: 滚轮信息或者侧键状态
    public int mouseData;
    // 事件注入的标志
    public int flags;
    // 此消息的时间戳
    public int time;
    // 与消息关联的其他信息
    public uint dwExtraInfo;
}
</code></pre>
<h3 id="3-定义委托类型">3. 定义委托类型</h3>
<pre><code class="language-csharp">// 定义一个委托类型, 给 WH_MOUSE_LL 回调函数用的
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
</code></pre>
<h3 id="4-捕捉到鼠标事件的时候-所要处理的回调函数-真正的业务逻辑代码在这">4. 捕捉到鼠标事件的时候, 所要处理的回调函数 (真正的业务逻辑代码在这)<sup class="footnote-ref"></sup></h3>
<pre><code class="language-csharp">public static IntPtr LLMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode &gt;= 0) //不建议处理 &lt;0 的事件, 会出问题
    {
      //把数据赋值给结构体
      tagMSLLHOOKSTRUCT tag = Marshal.PtrToStructure&lt;tagMSLLHOOKSTRUCT&gt;(lParam);
      short wheel = 0;
      //如果响应的是滚轮事件
      if ((int)wParam == WM_Mouse.WM_MOUSEWHEEL) //WM_Mouse.WM_MOUSEWHEEL = 0x020A
      {
            wheel = (short)(tag.mouseData &gt;&gt; 16); //数据在 HIWORD, 即左半, 得把左半的字节搬到右半覆盖掉, 使用 short 保留符号
      }
      tagPOINT point = tag.pt;
      string button = "";
      //判断按下的是什么按键
      switch ((int)wParam)
      {
            case 0x020A: //滚轮
                button = "Wheel";
                break;
            case 0x020B: //侧键
                button = "MouseXButton";
                break;
            case 0x0201: //左键
                button = "MouseLeft";
                break;
            case 0x0204: //右键
                button = "MouseRight";
                break;
            case 0x0207: //中键
                button = "MouseMiddle";
                break;
            default:
                button = "";
                break;
      }
      string text = "X: " + point.X + "\tY: " + point.Y + "\tTime: " + tag.time + "\tButton: " + button;
      //打印
      Console.WriteLine(text);
    }
    //记得处理完逻辑代码, 就得把消息传递给其他进程
    return CallNextHookEx(llmouseproc, nCode, wParam, lParam);
}
</code></pre>
<p>一些按键的值:</p>
<table>
<thead>
<tr>
<th>变量名</th>
<th>值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>WM_LBUTTONDOWN</td>
<td>0x0201</td>
<td>鼠标左键按下</td>
</tr>
<tr>
<td>WM_LBUTTONUP</td>
<td>0x0202</td>
<td>鼠标左键松开</td>
</tr>
<tr>
<td>WM_MOUSEMOVE</td>
<td>0x0200</td>
<td>鼠标移动</td>
</tr>
<tr>
<td>WM_MOUSEWHEEL</td>
<td>0x020A</td>
<td>鼠标滚轮</td>
</tr>
<tr>
<td>WM_RBUTTONDOWN</td>
<td>0x0204</td>
<td>鼠标右键按下</td>
</tr>
<tr>
<td>WM_RBUTTONUP</td>
<td>0x0205</td>
<td>鼠标右键松开</td>
</tr>
<tr>
<td>WM_MBUTTONDOWN</td>
<td>0x0207</td>
<td>鼠标中键按下</td>
</tr>
<tr>
<td>WM_MBUTTONUP</td>
<td>0x0208</td>
<td>鼠标中键放开</td>
</tr>
<tr>
<td>WM_XBUTTONDOWN</td>
<td>0x020B</td>
<td>鼠标侧键按下 (X1、X2都一样)</td>
</tr>
<tr>
<td>WM_XBUTTONUP</td>
<td>0x020C</td>
<td>鼠标侧键松开 (X1、X2都一样)</td>
</tr>
<tr>
<td>XBUTTON1</td>
<td>0x0001</td>
<td>鼠标侧键1的按下&amp;松开</td>
</tr>
<tr>
<td>XBUTTON2</td>
<td>0x0002</td>
<td>鼠标侧键2的按下&amp;松开</td>
</tr>
</tbody>
</table>
<p><strong>注意:</strong> XBUTTON1 和 XBUTTON2, 只能从 <code>tagMSLLHOOKSTRUCT.mouseData</code> 的高序字段中获取. <sup class="footnote-ref"></sup></p>
<h3 id="5-执行">5. 执行</h3>
<pre><code class="language-csharp">//把写好的回调函数, 赋值到 HookProc 这种委托类型 的变量里
public static HookProc hookproc = LLMouseProc;

//静态保存回调函数的句柄, 不然会被 GC 吃掉
public static IntPtr llmouseproc;

//开始部署消息钩子, 执行这一段函数之后, 就真正开始监听鼠标事件了
//SetWindowsHookExA: 第一个是消息类型, 第二个是 HookProc 这种委托类型的变量, 回调函数赋值在这里, 第三个和第四个正常不用管
llmouseproc = SetWindowsHookExA(14, hookproc, IntPtr.Zero, 0); //低级鼠标钩子消息类型, 值为 14
</code></pre>
<p><strong>注意:</strong> 对于一些已经 UAC 提权的应用, 该消息钩子无法捕捉到在目标应用下, 鼠标的坐标和状态, 必须将你的程序提权才能正常的捕捉到鼠标信息. <code>GetCursorPos()</code> 同理.</p>
<p>一些消息钩子类型:<sup class="footnote-ref"></sup></p>
<table>
<thead>
<tr>
<th>变量名</th>
<th>值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>WH_KEYBOARD</td>
<td>2</td>
<td>监听键盘输入消息, 需要注入</td>
</tr>
<tr>
<td>WH_KEYBOARD_LL</td>
<td>13</td>
<td>监听键盘输入消息, 不需要注入</td>
</tr>
<tr>
<td>WH_MOUSE</td>
<td>7</td>
<td>监听鼠标坐标和按键信息, 需要注入</td>
</tr>
<tr>
<td>WH_MOUSE_LL</td>
<td>14</td>
<td>监听鼠标坐标和按键信息, 不需要注入</td>
</tr>
</tbody>
</table>
<h3 id="6-结束">6. 结束</h3>
<p>在结束应用时, 记得手动把消息钩子给注销掉</p>
<pre><code class="language-CSharp">UnhookWindowsHookEx(llmouseproc);
</code></pre>
<h3 id="7-完整代码展示">7. 完整代码展示</h3>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TestWindowsHook
{
    public partial class Form1 : Form
    {
      public Form1()
      {
            InitializeComponent();
            this.Text = Application.ProductName;
      }

      private void Form1_Load(object sender, EventArgs e)
      {
            Run();
      }

      // 定义一个委托类型, 给 WH_MOUSE_LL 回调函数用的
      public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

      
      // 设置消息钩子
      public static extern IntPtr SetWindowsHookExA(int idHook, HookProc lpfn, IntPtr hmod, int dwThreadId);

      
      // 移除消息钩子
      public static extern bool UnhookWindowsHookEx(IntPtr idHook);

      
      // 继续运行下一个钩子 (其实是把钩子消息传递给下一个程序)
      public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);

      // POINT 结构体
      public struct tagPOINT
      {
            public int X;
            public int Y;
      }

      // MSLLHOOKSTRUCT 结构体
      public struct tagMSLLHOOKSTRUCT
      {
            // 光标的 XY 坐标
            public tagPOINT pt;
            // 鼠标额外数据: 滚轮信息或者侧键状态
            public int mouseData;
            // 事件注入的标志
            public int flags;
            // 此消息的时间戳
            public int time;
            // 与消息关联的其他信息
            public uint dwExtraInfo;
      }

      /// &lt;summary&gt;
      /// WM_Mouse消息
      /// &lt;para&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-lbuttondown"&gt;WM_LBUTTONDOWN消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-lbuttonup"&gt;WM_LBUTTONUP消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-mousemove"&gt;WM_MOUSEMOVE消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-mousewheel"&gt;WM_MOUSEWHEEL消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-rbuttondown"&gt;WM_RBUTTONDOWN消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-rbuttonup"&gt;WM_RBUTTONUP消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-mbuttondown"&gt;WM_MBUTTONDOWN消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-mbuttonup"&gt;WM_MBUTTONUP消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-xbuttondown"&gt;WM_XBUTTONDOWN消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;a href="https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-xbuttonup"&gt;WM_XBUTTONUP消息&lt;/a&gt;&lt;br/&gt;
      /// &lt;/para&gt;
      /// &lt;/summary&gt;
      public static class WM_Mouse
      {
            /// &lt;summary&gt;
            /// 无
            /// &lt;/summary&gt;
            public static int NONE = 0x0000;
            /// &lt;summary&gt;
            /// 鼠标左键按下
            /// &lt;/summary&gt;
            public static int WM_LBUTTONDOWN = 0x0201;
            /// &lt;summary&gt;
            /// 鼠标左键松开
            /// &lt;/summary&gt;
            public static int WM_LBUTTONUP = 0x0202;
            /// &lt;summary&gt;
            /// 鼠标移动
            /// &lt;/summary&gt;
            public static int WM_MOUSEMOVE = 0x0200;
            /// &lt;summary&gt;
            /// 鼠标滚轮
            /// &lt;/summary&gt;
            public static int WM_MOUSEWHEEL = 0x020A;
            /// &lt;summary&gt;
            /// 鼠标右键按下
            /// &lt;/summary&gt;
            public static int WM_RBUTTONDOWN = 0x0204;
            /// &lt;summary&gt;
            /// 鼠标右键松开
            /// &lt;/summary&gt;
            public static int WM_RBUTTONUP = 0x0205;
            /// &lt;summary&gt;
            /// 鼠标中键按下
            /// &lt;/summary&gt;
            public static int WM_MBUTTONDOWN = 0x0207;
            /// &lt;summary&gt;
            /// 鼠标中键放开
            /// &lt;/summary&gt;
            public static int WM_MBUTTONUP = 0x0208;
            /// &lt;summary&gt;
            /// 鼠标侧键按下
            /// &lt;/summary&gt;
            public static int WM_XBUTTONDOWN = 0x020B;
            /// &lt;summary&gt;
            /// 鼠标侧键松开
            /// &lt;/summary&gt;
            public static int WM_XBUTTONUP = 0x020C;

            /// &lt;summary&gt;
            /// 鼠标左键关闭
            /// &lt;/summary&gt;
            public static int MK_LBUTTON = 0x0001;
            /// &lt;summary&gt;
            /// 鼠标右键关闭
            /// &lt;/summary&gt;
            public static int MK_RBUTTON = 0x0002;
            /// &lt;summary&gt;
            /// Shift关闭
            /// &lt;/summary&gt;
            public static int MK_SHIFT = 0x0004;
            /// &lt;summary&gt;
            /// Ctrl关闭
            /// &lt;/summary&gt;
            public static int MK_CONTROL = 0x0008;
            /// &lt;summary&gt;
            /// 鼠标中键关闭
            /// &lt;/summary&gt;
            public static int MK_MBUTTON = 0x0010;
            /// &lt;summary&gt;
            /// 鼠标侧键1关闭
            /// &lt;/summary&gt;
            public static int MK_XBUTTON1 = 0x0020;
            /// &lt;summary&gt;
            /// 鼠标侧键2关闭
            /// &lt;/summary&gt;
            public static int MK_XBUTTON2 = 0x0040;
      }

      /// &lt;summary&gt;
      /// WH_MOUSE_LL 的回调函数, 真正的业务逻辑处理在这
      /// &lt;/summary&gt;
      /// &lt;param name="nCode"&gt;&lt;/param&gt;
      /// &lt;param name="wParam"&gt;&lt;/param&gt;
      /// &lt;param name="lParam"&gt;&lt;/param&gt;
      /// &lt;returns&gt;&lt;/returns&gt;
      public static IntPtr LLMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
      {
            if (nCode &gt;= 0) //不建议处理 &lt;0 的事件, 会出问题
            {
                //把数据赋值给结构体
                tagMSLLHOOKSTRUCT tag = Marshal.PtrToStructure&lt;tagMSLLHOOKSTRUCT&gt;(lParam);
                short wheel = 0;
                //如果响应的是滚轮事件
                if ((int)wParam == WM_Mouse.WM_MOUSEWHEEL) //WM_Mouse.WM_MOUSEWHEEL = 0x020A
                {
                  wheel = (short)(tag.mouseData &gt;&gt; 16); //数据在 HIWORD, 即左半, 得把左半的字节搬到右半覆盖掉, 使用 short 保留符号
                }
                tagPOINT point = tag.pt;
                string button = "";
                //判断按下的是什么按键
                switch ((int)wParam)
                {
                  case 0x020A: //滚轮
                        button = "Wheel";
                        break;
                  case 0x020B: //侧键
                        button = "MouseXButton";
                        break;
                  case 0x0201: //左键
                        button = "MouseLeft";
                        break;
                  case 0x0204: //右键
                        button = "MouseRight";
                        break;
                  case 0x0207: //中键
                        button = "MouseMiddle";
                        break;
                  default:
                        button = "";
                        break;
                }
                string text = "X: " + point.X + "\tY: " + point.Y + "\tTime: " + tag.time + "\tButton: " + button;
                //打印
                Console.WriteLine(text);
            }
            //记得处理完逻辑代码, 就得把消息传递给其他进程
            return CallNextHookEx(llmouseproc, nCode, wParam, lParam);
      }

      //把写好的回调函数, 赋值到 HookProc 这种委托类型 的变量里
      public static HookProc hookproc = LLMouseProc;

      //静态保存回调函数的句柄, 不然会被 GC 吃掉
      public static IntPtr llmouseproc;

      //开始执行
      public static void Run()
      {
            //开始部署消息钩子, 执行这一段函数之后, 就真正开始监听鼠标事件了
            //SetWindowsHookExA: 第一个是消息类型, 第二个是 HookProc 这种委托类型的变量, 回调函数赋值在这里, 第三个和第四个正常不用管
            llmouseproc = SetWindowsHookExA(14, hookproc, IntPtr.Zero, 0); //低级鼠标钩子消息类型, 值为 14
      }

      //退出时, 记得把消息钩子注销掉
      private void Form1_FormClosing(object sender, FormClosingEventArgs e)
      {
            UnhookWindowsHookEx(llmouseproc);
      }

      //退出按钮
      private void button1_Click(object sender, EventArgs e)
      {
            this.Close();
      }
    }
}

</code></pre>
<h3 id="8-大致流程">8. 大致流程</h3>
<ol>
<li>准备函数, 结构体, 委托类型;</li>
<li>编写回调函数;</li>
<li>把写好的回调函数, 赋值到 <code>HookProc</code> 这种委托类型的变量里;</li>
<li>使用 <code>SetWindowsHookExA()</code> 注册消息钩子, 并把返回的句柄保留起来;</li>
<li>使用 <code>UnhookWindowsHookEx()</code> 注销消息钩子, 结束运行.</li>
</ol>
<h2 id="进阶显示按键状态-是否按下">进阶:显示按键状态 (是否按下?)</h2>
<p>通过 <code>wParam</code> 可以获取当前按下了什么按键, 但是只会触发一次, 要让输出结果保持持久状态 (比如一直按下鼠标左键), 就得有个变量来暂存这些状态.</p>
<h3 id="1-暂存按键状态">1. 暂存按键状态</h3>
<p>首先创建个静态类用于存放按键状态</p>
<pre><code class="language-CSharp">// 按键状态
public static class ButtonStatus
{
    /// &lt;summary&gt;
    /// 鼠标左键
    /// &lt;/summary&gt;
    public static bool MouseLeft = false;
    /// &lt;summary&gt;
    /// 鼠标右键
    /// &lt;/summary&gt;
    public static bool MouseRight = false;
    /// &lt;summary&gt;
    /// 鼠标中间
    /// &lt;/summary&gt;
    public static bool MouseMiddle = false;
    /// &lt;summary&gt;
    /// 鼠标侧键1
    /// &lt;/summary&gt;
    public static bool MouseXBotton1 = false;
    /// &lt;summary&gt;
    /// 鼠标侧键2
    /// &lt;/summary&gt;
    public static bool MouseXBotton2 = false;
}
</code></pre>
<h3 id="2-修改回调函数">2. 修改回调函数</h3>
<p>用 switch 来更新 <code>ButtonStatus</code> 类里的变量状态</p>
<pre><code class="language-CSharp">// 设置全局唯一一个 StringBuilder
public static StringBuilder sb = new StringBuilder();

/// &lt;summary&gt;
/// WH_MOUSE_LL 的回调函数, 真正的业务逻辑处理在这
/// &lt;/summary&gt;
/// &lt;param name="nCode"&gt;&lt;/param&gt;
/// &lt;param name="wParam"&gt;&lt;/param&gt;
/// &lt;param name="lParam"&gt;&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
public static IntPtr LLMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode &gt;= 0) //不建议处理 &lt;0 的事件, 会出问题
    {
      //把数据赋值给结构体
      tagMSLLHOOKSTRUCT tag = Marshal.PtrToStructure&lt;tagMSLLHOOKSTRUCT&gt;(lParam);
      tagPOINT point = tag.pt;
      short wheel = 0;
      
      //判断按下的是什么按键
      switch ((int)wParam)
      {
            case 0x020A: //滚轮
                sb.Append("Wheel |");
                wheel = (short)(tag.mouseData &gt;&gt; 16);
                break;
            case 0x020B: //侧键按下
                short xbottondown = (short)((int)tag.mouseData &gt;&gt; 16);
                if(xbottondown == 0x0001)
                {
                  ButtonStatus.MouseXBotton1 = true;
                }
                else if (xbottondown == 0x0002)
                {
                  ButtonStatus.MouseXBotton2 = true;
                }
                break;
            case 0x0201: //左键按下
                ButtonStatus.MouseLeft = true;
                break;
            case 0x0204: //右键按下
                ButtonStatus.MouseRight= true;
                break;
            case 0x0207: //中键按下
                ButtonStatus.MouseMiddle = true;
                break;
            case 0x020C: //侧键释放
                short xbottonup = (short)((int)tag.mouseData &gt;&gt; 16);
                if (xbottonup == 0x0001)
                {
                  ButtonStatus.MouseXBotton1 = false;
                }
                else if (xbottonup == 0x0002)
                {
                  ButtonStatus.MouseXBotton2 = false;
                }
                break;
            case 0x0202: //左键释放
                ButtonStatus.MouseLeft = false;
                break;
            case 0x0205: //右键释放
                ButtonStatus.MouseRight= false;
                break;
            case 0x0208: //中键释放
                ButtonStatus.MouseMiddle = false;
                break;
            default:
               
                break;
      }
      if(ButtonStatus.MouseLeft == true)
      {
            sb.Append(" MouseLeft |");
      }
      if(ButtonStatus.MouseRight == true)
      {
            sb.Append(" MouseRight |");
      }
      if(ButtonStatus.MouseMiddle == true)
      {
            sb.Append(" MouseMiddle |");
      }
      if(ButtonStatus.MouseXBotton1 == true)
      {
            sb.Append(" MouseXBotton1 |");
      }
      if(ButtonStatus.MouseXBotton2 == true)
      {
            sb.Append(" MouseXBotton2 |");
      }
      if (sb.Length &gt; 0)
      {
            sb.Remove(sb.Length - 1, 1);
      }
      string text = "X: " + point.X + "\tY: " + point.Y + "\tWheel: " + wheel + "\tTime: " + tag.time + "\tButton: " + sb.ToString();
      sb.Clear();
      
      //打印
      Console.WriteLine(text);
    }
    //记得处理完逻辑代码, 就得把消息传递给其他进程
    return CallNextHookEx(llmouseproc, nCode, wParam, lParam);
}
</code></pre>
<p>其中, 侧键具体的某个按键是和滚轮偏移量一样, 存放在 <code>tagMSLLHOOKSTRUCT.mouseData</code> 的高序字中, 这就得进行移位转换.</p>
<p>只需改动这两处即可保存按键状态.</p>
<h2 id="附录">附录</h2>
<h3 id="开源">开源</h3>
<p>项目开源在: TestWindowsHook</p>
<h3 id="参考灵感来源">参考/灵感来源</h3>
<ol>
<li>C# Hook (一)</li>
<li>使用挂钩 - Win32 apps | Microsoft Learn</li>
</ol>
<h3 id="脚注">脚注</h3>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>SetWindowsHookExA 函数 (winuser.h) ↩︎</p>
</li>
<li id="fn2" class="footnote-item"><p>UnhookWindowsHookEx 函数 (winuser.h) ↩︎</p>
</li>
<li id="fn3" class="footnote-item"><p>CallNextHookEx 函数 (winuser.h) ↩︎</p>
</li>
<li id="fn4" class="footnote-item"><p>MSLLHOOKSTRUCT 结构 (winuser.h) ↩︎</p>
</li>
<li id="fn5" class="footnote-item"><p>POINT 结构 (windef.h) ↩︎</p>
</li>
<li id="fn6" class="footnote-item"><p>LowLevelMouseProc 函数 ↩︎</p>
</li>
<li id="fn7" class="footnote-item"><p>有关 XBUTTON1/2 值的获取 ↩︎</p>
</li>
<li id="fn8" class="footnote-item"><p>消息钩子类型有这些 ↩︎</p>
</li>
</ol>
</section><br><br>
来源:https://www.cnblogs.com/yuhang0000/p/19966780
頁: [1]
查看完整版本: [C# 笔记] 如何设置消息钩子 (以低级鼠标钩子为例)