PAYATHON 2026

C 语言按键计数器如何用 F1 暂停和恢复计数?

数据阿明

用状态变量控制计数

可以保留键盘钩子,用 paused 控制计数。按下 F1 时切换暂停状态,其他按键只在未暂停时计数。暂停期间仍需接收键盘事件,以便再次按 F1 恢复计数。

还需要记录 F1 是否处于按下状态,防止长按时产生的重复按下事件让暂停和恢复连续切换。下面的实现将 F1 作为控制键,不计入总数。

原代码需要一并修正的地方

  • LPMSG msg; 只是一个指针,没有有效的 MSG 对象供消息循环使用。应声明 MSG msg;,再将 &msg 传给相关函数。
  • 通过 lParam 指向的 KBDLLHOOKSTRUCT 读取 vkCode,用 VK_F1 判断 F1。
  • 当 code < 0 时,必须直接调用 CallNextHookEx。对于本程序没有拦截的其他事件,也应继续传递给后续钩子。
  • GetMessage 可能返回 -1,应区分消息读取失败和正常退出。

完整示例

以下代码按 Windows 控制台程序编写。原来的 Stealth() 只涉及控制台窗口,与暂停逻辑无关,示例中将其省略。

#include <stdio.h>
#include <windows.h>

static HHOOK hook = NULL;
static unsigned long long counter = 0;
static BOOL paused = FALSE;
static BOOL f1Down = FALSE;

LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
    if (code < 0) {
        return CallNextHookEx(hook, code, wParam, lParam);
    }

    if (code == HC_ACTION) {
        const KBDLLHOOKSTRUCT *key =
            (const KBDLLHOOKSTRUCT *)lParam;

        BOOL keyDown =
            (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN);
        BOOL keyUp =
            (wParam == WM_KEYUP || wParam == WM_SYSKEYUP);

        if (key->vkCode == VK_F1) {
            if (keyDown && !f1Down) {
                f1Down = TRUE;
                paused = !paused;
                puts(paused ? "Paused" : "Resumed");
            } else if (keyUp) {
                f1Down = FALSE;
            }
        } else if (keyDown && !paused) {
            ++counter;
            printf("Current count: %llu\n", counter);
        }
    }

    return CallNextHookEx(hook, code, wParam, lParam);
}

int main(void)
{
    MSG msg;
    BOOL result;

    hook = SetWindowsHookEx(
        WH_KEYBOARD_LL,
        KeyboardProc,
        GetModuleHandle(NULL),
        0
    );

    if (hook == NULL) {
        fprintf(stderr, "SetWindowsHookEx failed: %lu\n",
                GetLastError());
        return 1;
    }

    puts("Press F1 to pause or resume counting.");

    while ((result = GetMessage(&msg, NULL, 0, 0)) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if (result == -1) {
        fprintf(stderr, "GetMessage failed: %lu\n",
                GetLastError());
    }

    UnhookWindowsHookEx(hook);
    return result == -1 ? 1 : 0;
}

编译时需要链接 User32 库,具体设置取决于使用的编译器和构建工具。

暂停和恢复的执行过程

第一次按下 F1 时,f1Down 从 FALSE 变为 TRUE,同时切换 paused。持续按住 F1 时,即使收到重复的按下事件,也不会再次切换状态。松开 F1 后,f1Down 恢复为 FALSE,下一次按下时才会再次切换。

暂停时,已有的 counter 值会保留;恢复后从原来的数值继续累计。示例还处理了 WM_SYSKEYDOWN 和 WM_SYSKEYUP,用于覆盖系统键消息,例如与 Alt 相关的按键组合。

使用时的注意事项

这里统计的是按下事件的数量。长按普通按键产生的重复 WM_KEYDOWN 仍会增加计数。如果要求每次完整的按下、松开只算一次,需要为各个按键分别维护按下状态。

暂停期间不能停止消息循环或卸载钩子,否则当前方案无法继续通过该钩子接收 F1。示例始终调用 CallNextHookEx,因此 F1 和其他按键仍会继续传递,其他应用也可能响应它们。

低级键盘钩子回调应尽快返回。示例为了便于观察,在回调里使用了 printf;持续使用时,宜将显示或文件写入移到回调之外,避免输出阻塞影响钩子处理。该程序会接收当前桌面范围内的键盘事件,应在用户知情并授权的环境中使用。

备注:内容仅供参考。