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

在Rust中使用SetWindowsHookExW设置WH_KEYBOARD钩子无效求助

WH_KEYBOARD钩子未触发的问题分析与解决

问题根源

你的代码存在两个核心问题,导致钩子完全没有触发:

  1. WH_KEYBOARD依赖线程消息循环
    WH_KEYBOARD钩子的触发逻辑是:只有当目标线程调用GetMessage/PeekMessage处理消息队列时,系统才会在键盘消息分发前调用钩子回调。但你的主线程被stdin.read_line阻塞,全程没有处理Windows消息,钩子根本没有被触发的机会。

  2. 控制台输入不走普通线程消息队列
    控制台程序的stdin.read_line是通过Windows控制台子系统的专用API处理的,键盘输入不会被投递到当前线程的消息队列中——就算你加了消息循环,WH_KEYBOARD也捕获不到这些输入。

解决方案

根据你的需求,有两种可行的修正方向:

方案1:改用WH_KEYBOARD_LL全局低级别钩子

WH_KEYBOARD_LL是全局钩子,不需要目标线程的消息循环,系统会直接在键盘事件发生时调用回调(它会自动把事件投递到安装钩子的线程,所以需要主线程运行消息循环来处理)。修改后的代码示例:

use windows::Win32::{
    Foundation::{HINSTANCE, LPARAM, LRESULT, WPARAM},
    System::Threading::GetCurrentThreadId,
    UI::WindowsAndMessaging::{
        CallNextHookEx, GetMessageW, SetWindowsHookExW, UnhookWindowsHookEx, HHOOK, 
        WH_KEYBOARD_LL, MSG, WM_QUIT,
    },
};

static mut FOO: u32 = 0u32;

unsafe extern "system" fn low_level_keyboard_proc_callback(
    code: i32,
    wparam: WPARAM,
    lparam: LPARAM,
) -> LRESULT {
    if code >= 0 {
        FOO += 1;
    }
    CallNextHookEx(HHOOK::default(), code, wparam, lparam)
}

fn main() {
    unsafe { println!("初始值: {}", FOO) };
    
    let hook_handle = unsafe {
        SetWindowsHookExW(
            WH_KEYBOARD_LL,
            Some(low_level_keyboard_proc_callback),
            HINSTANCE::default(),
            0, // 全局钩子,线程ID设为0
        )
        .unwrap()
    };

    // 子线程处理控制台输入,避免阻塞主线程消息循环
    std::thread::spawn(move || {
        for _ in 0..2 {
            let mut line = String::new();
            std::io::stdin().read_line(&mut line).unwrap();
            println!("输入内容: {}", line.trim());
        }
        // 输入完成后发送退出消息终止主线程消息循环
        unsafe {
            windows::Win32::UI::WindowsAndMessaging::PostThreadMessageW(
                GetCurrentThreadId(),
                WM_QUIT,
                WPARAM(0),
                LPARAM(0),
            ).unwrap();
        }
    });

    // 主线程运行消息循环,处理WH_KEYBOARD_LL的回调事件
    let mut msg = MSG::default();
    unsafe {
        while GetMessageW(&mut msg, None, 0, 0).as_bool() {
            windows::Win32::UI::WindowsAndMessaging::TranslateMessage(&msg);
            windows::Win32::UI::WindowsAndMessaging::DispatchMessageW(&msg);
        }
    }

    unsafe {
        UnhookWindowsHookEx(hook_handle).unwrap();
        println!("最终值: {}", FOO)
    };
}

方案2:使用控制台专用API捕获键盘事件

如果你只需要捕获控制台的键盘输入,直接用控制台专属API更高效,比如ReadConsoleInput:

use windows::Win32::{
    System::Console::{ReadConsoleInputW, INPUT_RECORD, KEY_EVENT_RECORD, INPUT_RECORD_TYPE, KEY_EVENT},
    Foundation::HANDLE,
    UI::Input::KeyboardAndMouse::VK_RETURN,
};

fn main() {
    let mut foo = 0u32;
    let stdin_handle = unsafe { 
        windows::Win32::System::Console::GetStdHandle(windows::Win32::System::Console::STD_INPUT_HANDLE)
            .unwrap() 
    };
    
    let mut records = [INPUT_RECORD::default(); 1];
    let mut num_read = 0;
    let mut lines_read = 0;

    while lines_read < 2 {
        unsafe {
            ReadConsoleInputW(
                stdin_handle,
                &mut records,
                1,
                &mut num_read,
            ).unwrap();
        }

        if records[0].EventType == KEY_EVENT {
            let key_event = unsafe { records[0].Event.KeyEvent() };
            if key_event.bKeyDown.as_bool() {
                foo += 1;
                // 检测回车键,统计输入行数
                if key_event.wVirtualKeyCode == VK_RETURN {
                    lines_read += 1;
                }
            }
        }
    }

    println!("按键次数: {}", foo);
}

补充说明

  • WH_KEYBOARD钩子主要用于捕获GUI线程中的键盘消息(比如Win32窗口的WM_KEYDOWN/WM_KEYUP),完全不适合控制台程序场景。
  • WH_KEYBOARD_LL是少数不需要DLL注入的全局钩子,回调会在安装钩子的线程中执行,因此必须保证该线程的消息循环持续运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 05:35:09