You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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");
    //     }
    // }
}

关键修改说明

  1. 添加消息循环:在ExecuteAsync中调用GetMessage、TranslateMessage、DispatchMessage处理Windows消息,确保低级键盘钩子的回调能被系统触发。
  2. 完善Unhook逻辑:在服务停止时主动卸载钩子,避免资源泄漏。
  3. 简化钩子注册:去掉多余的LoadLibrary调用,直接用IntPtr.Zero作为实例句柄,同时添加SetLastError = true方便排查注册失败的原因。
  4. Print Screen按键检测:在回调中判断虚拟键码是否为VK_SNAPSHOT(0x2C),触发你的业务逻辑。
  5. 依赖说明:需要安装System.Windows.Forms NuGet包,若不需要截图功能,可删除相关代码,但消息循环的API仍需保留。

内容的提问来源于stack exchange,提问作者Aleksa Ristic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 03:40:22