.NET6后台服务监听Print Screen按键实现问题
解决.NET6 Background Service中SetWindowsHookEx键盘钩子回调不触发的问题
问题根源
你的代码里几个关键问题导致钩子回调没触发:
- 缺少Windows消息循环:
WH_KEYBOARD_LL是低级键盘钩子,系统会把键盘事件消息发送到安装钩子的线程,该线程必须运行消息循环才能处理这些消息,而你原代码的ExecuteAsync是空循环,完全没处理消息。 - Unhook方法空实现:没有正确卸载钩子,会造成资源泄漏,还可能引发钩子异常。
- 冗余的LoadLibrary调用:
WH_KEYBOARD_LL不需要注入其他进程,直接传IntPtr.Zero作为实例句柄即可,加载User32.dll属于多余操作。
修正后的完整代码
namespace LSShot; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; // 需要安装System.Windows.Forms NuGet包 public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; #region Win32 API导入 [DllImport("User32.dll")] public static extern IntPtr GetDC(IntPtr hwnd); [DllImport("User32.dll")] public static extern void ReleaseDC(IntPtr hwnd, IntPtr dc); [DllImport("User32.dll")] public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc); [DllImport("User32.dll")] public static extern bool UnregisterHotKey(IntPtr hWnd, int id); [DllImport("User32.dll", SetLastError = true)] public static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId); [DllImport("user32.dll", SetLastError = true)] public static extern bool UnhookWindowsHookEx(IntPtr hHook); [DllImport("user32.dll")] static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam); [DllImport("user32.dll")] private static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); [DllImport("user32.dll")] private static extern bool TranslateMessage([In] ref MSG lpMsg); [DllImport("user32.dll")] private static extern IntPtr DispatchMessage([In] ref MSG lpMsg); #endregion #region 结构体定义 public struct keyboardHookStruct { public int vkCode; public int scanCode; public int flags; public int time; public int dwExtraInfo; } private struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam; public IntPtr lParam; public uint time; public POINT pt; } private struct POINT { public int x; public int y; } #endregion public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam); private IntPtr _hhook = IntPtr.Zero; private static keyboardHookProc _callbackDelegate; const int WH_KEYBOARD_LL = 13; const int VK_SNAPSHOT = 0x2C; // Print Screen按键的虚拟键码 const int HC_ACTION = 0; public Worker(ILogger<Worker> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 初始化钩子 Hook(); // 运行Windows消息循环,这是触发钩子回调的核心 while (!stoppingToken.IsCancellationRequested) { if (GetMessage(out MSG msg, IntPtr.Zero, 0, 0)) { TranslateMessage(ref msg); DispatchMessage(ref msg); } await Task.Delay(10, stoppingToken); } // 服务停止时卸载钩子 Unhook(); } private void Hook() { if (_callbackDelegate != null) throw new InvalidOperationException("不能重复注册钩子!"); _callbackDelegate = new keyboardHookProc(HookProc); // WH_KEYBOARD_LL无需注入其他进程,直接传IntPtr.Zero _hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _callbackDelegate, IntPtr.Zero, 0); if (_hhook == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } private void Unhook() { if (_hhook != IntPtr.Zero) { UnhookWindowsHookEx(_hhook); _hhook = IntPtr.Zero; _callbackDelegate = null; } } private int HookProc(int code, int wParam, ref keyboardHookStruct lParam) { // 仅当code为HC_ACTION时处理键盘事件 if (code >= HC_ACTION) { // 检测Print Screen按键 if (lParam.vkCode == VK_SNAPSHOT) { _logger.LogInformation("Print Screen按键已触发"); // 在这里执行你的业务逻辑,比如截图 // CaptureScreen(); } } // 必须调用此方法,否则其他键盘钩子会失效 return CallNextHookEx(_hhook, code, wParam, ref lParam); } // 示例截图方法(可选) // private void CaptureScreen() // { // using (Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)) // { // using (Graphics g = Graphics.FromImage(bitmap)) // { // g.CopyFromScreen(0, 0, 0, 0, bitmap.Size); // } // bitmap.Save("screenshot.png"); // } // } }
关键修改说明
- 添加消息循环:在
ExecuteAsync中调用GetMessage、TranslateMessage、DispatchMessage处理Windows消息,确保低级键盘钩子的回调能被系统触发。 - 完善Unhook逻辑:在服务停止时主动卸载钩子,避免资源泄漏。
- 简化钩子注册:去掉多余的
LoadLibrary调用,直接用IntPtr.Zero作为实例句柄,同时添加SetLastError = true方便排查注册失败的原因。 - Print Screen按键检测:在回调中判断虚拟键码是否为
VK_SNAPSHOT(0x2C),触发你的业务逻辑。 - 依赖说明:需要安装
System.Windows.FormsNuGet包,若不需要截图功能,可删除相关代码,但消息循环的API仍需保留。
内容的提问来源于stack exchange,提问作者Aleksa Ristic
相关产品推荐
相关产品推荐

