C#实现键盘钩子主要依靠Windows系统的底层钩子机制,通过调用user32.dll中的相关API函数来完成全局或局部的键盘事件监听,适用于快捷键响应、键盘输入监控等多种场景。

C#键盘钩子的基础原理
键盘钩子是Windows提供的一种消息拦截机制,当键盘有按键动作时,系统会先向钩子链中的钩子过程发送消息,钩子处理完成后再决定是否将消息传递给下一个钩子或目标窗口。C#中无法直接调用系统钩子,需要通过平台调用服务(P/Invoke)调用user32.dll中的相关函数。
常用的键盘钩子类型有两种,WH_KEYBOARD_LL是底层键盘钩子,可以监听全局所有键盘事件,不受应用程序窗口焦点影响;WH_KEYBOARD是普通键盘钩子,只能监听当前线程的键盘事件。
核心API函数说明
实现键盘钩子需要用到以下几个关键函数,都位于user32.dll中:
SetWindowsHookEx:用于安装钩子,指定钩子类型、回调函数和关联的模块或线程UnhookWindowsHookEx:用于卸载已经安装的钩子,避免资源泄漏CallNextHookEx:在钩子回调中调用,将消息传递给下一个钩子GetModuleHandle:获取模块句柄,用于底层钩子的安装
实现步骤与代码示例
1. 声明API函数和常量
首先需要定义用到的常量、结构体和方法签名,所有函数都需要使用DllImport特性标记:
using System;
using System.Runtime.InteropServices;
public class KeyboardHook
{
// 钩子类型常量
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_SYSKEYUP = 0x0105;
// 键盘钩子结构体
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
public int vkCode; // 虚拟键码
public int scanCode; // 扫描码
public int flags; // 标志位
public int time; // 时间戳
public IntPtr dwExtraInfo; // 额外信息
}
// 声明API函数
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
// 钩子回调函数委托
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private LowLevelKeyboardProc proc;
private IntPtr hookId = IntPtr.Zero;
}
2. 安装和卸载钩子
安装底层键盘钩子需要获取当前模块的句柄,回调函数中处理键盘事件后可以选择拦截或放行消息:
public class KeyboardHook
{
// 前面的声明代码省略...
// 安装钩子
public void InstallHook()
{
proc = HookCallback;
using (var curProcess = System.Diagnostics.Process.GetCurrentProcess())
using (var curModule = curProcess.MainModule)
{
// 安装全局底层键盘钩子
hookId = SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
}
}
// 卸载钩子
public void UninstallHook()
{
if (hookId != IntPtr.Zero)
{
UnhookWindowsHookEx(hookId);
hookId = IntPtr.Zero;
}
}
// 钩子回调函数
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
// 获取键盘事件信息
var kbStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
// 按键按下事件
if (wParam == (IntPtr)WM_KEYDOWN)
{
Console.WriteLine($"按键按下:虚拟键码 {kbStruct.vkCode}");
// 如果要拦截该按键,返回1即可,不再传递给其他程序
// return (IntPtr)1;
}
// 按键抬起事件
else if (wParam == (IntPtr)WM_KEYUP)
{
Console.WriteLine($"按键抬起:虚拟键码 {kbStruct.vkCode}");
}
}
// 传递给下一个钩子
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
}
3. 使用示例
在控制台程序中调用钩子的代码示例如下:
class Program
{
static void Main(string[] args)
{
var hook = new KeyboardHook();
hook.InstallHook();
Console.WriteLine("键盘钩子已安装,按任意键测试,按ESC退出程序");
// 保持程序运行,否则钩子会被卸载
Console.ReadKey();
hook.UninstallHook();
}
}
常见问题与注意事项
- 钩子安装后如果程序退出没有调用
UnhookWindowsHookEx,会导致系统钩子残留,所以需要在程序退出时确保卸载钩子,可以在析构函数或using语句中处理。 - 底层键盘钩子需要程序有足够的权限,部分安全软件可能会拦截钩子行为,导致钩子失效。
- 不要在钩子回调中执行耗时操作,否则会影响键盘响应速度,甚至导致系统卡顿。
- 使用键盘钩子监听用户输入需要遵守相关隐私法规,不得用于非法获取用户信息。
普通键盘钩子的实现
如果需要监听当前线程的键盘事件,可以使用WH_KEYBOARD类型的钩子,安装时指定当前线程ID即可,不需要获取模块句柄:
private const int WH_KEYBOARD = 2;
// 安装线程内键盘钩子
public void InstallThreadHook()
{
proc = ThreadHookCallback;
// 获取当前线程ID,dwThreadId参数不为0时表示钩子仅监听该线程
uint threadId = GetCurrentThreadId();
hookId = SetWindowsHookEx(WH_KEYBOARD, proc, IntPtr.Zero, threadId);
}
[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();
private IntPtr ThreadHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
{
Console.WriteLine($"线程内按键事件:键码 {wParam.ToInt32()}");
}
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
C#键盘钩子user32_dllWH_KEYBOARD_LL钩子回调修改时间:2026-06-18 04:54:53