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

Win32 API输入函数KEEP_TIME模式计数异常问题排查

问题描述

基于Win32 API用ANSI C89编写的输入函数包含三种输入模式:

  • SINGLE:仅触发一次命令,再次按下才重新激活
  • KEEP:按键持续按下时循环触发命令
  • KEEP_TIME:统计按键按下的毫秒时长

KEEP_TIME模式存在异常:本该在按键释放时返回正确的按下时长,却在游戏循环中持续输出递增的时长数值,示例输出如下:

KEY : 'W' pressed by 422 ms
KEY : 'W' pressed by 437 ms
KEY : 'W' pressed by 500 ms
KEY : 'W' pressed by 516 ms
KEY : 'W' pressed by 531 ms
KEY : 'W' pressed by 547 ms
KEY : 'W' pressed by 562 ms
KEY : 'W' pressed by 578 ms
KEY : 'W' pressed by 594 ms
KEY : 'W' pressed by 609 ms
KEY : 'W' pressed by 625 ms
KEY : 'W' pressed by 641 ms
KEY : 'W' pressed by 656 ms

输入函数代码

#define PRESS_STATE 0x8000

typedef enum 
{
    SINGLE,
    KEEP,
    KEEP_TIME
} InputMode;


static DWORD keyInput(int keyCode, InputMode mode) 
{
    static bool_ keyState[256] = { FALSE };  /* Array to control the state of the keys */
    static bool_ prevKeyState[256] = { FALSE }; /* Array to store the previous state of keys */
    static DWORD keyPressTimes[256] = { 0 }; /* Array to record key press times */
    DWORD releaseTime = 0;
    DWORD pressTime = 0;
    DWORD pressDuration = 0;

    /* Get current key state */
    bool_ currentKeyState = GetAsyncKeyState(keyCode) & PRESS_STATE;

    /* Check if the key is pressed at the current time */
    if (currentKeyState) 
    {
        /* Check if the key was released in the previous frame */
        if (!prevKeyState[keyCode]) 
        {
            /* Mark the key as pressed in the current frame */
            keyState[keyCode] = true;
            keyPressTimes[keyCode] = GetTickCount(); /* Records key press time */
            prevKeyState[keyCode] = true;

            if (mode == SINGLE) 
            {
               return true;
            }
        }
    } 
    else 
    {
        if (keyState[keyCode]) 
        {
            prevKeyState[keyCode] = FALSE; /* Mark the key as released in the current frame */

            if (mode == SINGLE) 
            {
                keyState[keyCode] = FALSE;
            }

            releaseTime = GetTickCount();
            pressTime = keyPressTimes[keyCode];
            pressDuration = releaseTime - pressTime;

           if (mode == KEEP_TIME) 
            {
                return pressDuration;
                keyState[keyCode] = false;
            }            

        }
    }

    /* Return 0 if KEEP mode is used and the key is pressed */
    return (mode == KEEP) && currentKeyState ? 1 : 0;
}

游戏循环调用代码

void GLRender()
{/* GAME LOOP */
    
    DWORD pressDuration = keyInput(KEY_W, KEEP_TIME);

    if (pressDuration > 1) 
    {
            /* Print key press time */
            printf("KEY : '%c' pressed by %d ms\n", KEY_W, pressDuration);
    }
}

需求:修复KEEP_TIME模式,使其仅在按键释放时返回正确的按下时长。

问题分析
  1. 状态未重置:KEEP_TIME分支中,return pressDuration;语句在keyState[keyCode] = false;之前执行,导致重置代码永远无法运行。按键释放后,keyState[keyCode]始终保持true,后续每帧都会进入释放分支,重新计算当前时间与按下时间的差值,返回值持续递增。
  2. 返回值语义混乱:函数在不同模式下返回值含义不一致(SINGLE返回布尔值,KEEP返回1/0,KEEP_TIME返回时长),游戏循环中只要返回值>1就输出,进一步放大了状态未重置的问题。
修复方案

调整代码逻辑,确保按键释放后正确重置状态,且仅在释放瞬间返回时长:

#define PRESS_STATE 0x8000

typedef enum 
{
    SINGLE,
    KEEP,
    KEEP_TIME
} InputMode;


static DWORD keyInput(int keyCode, InputMode mode) 
{
    static bool_ keyState[256] = { FALSE };
    static bool_ prevKeyState[256] = { FALSE };
    static DWORD keyPressTimes[256] = { 0 };
    DWORD pressDuration = 0;

    bool_ currentKeyState = GetAsyncKeyState(keyCode) & PRESS_STATE;

    if (currentKeyState) 
    {
        if (!prevKeyState[keyCode]) 
        {
            keyState[keyCode] = true;
            keyPressTimes[keyCode] = GetTickCount();
            prevKeyState[keyCode] = true;

            if (mode == SINGLE) 
            {
               return 1; // 统一返回值类型,用1表示触发
            }
        }
    } 
    else 
    {
        if (keyState[keyCode]) 
        {
            prevKeyState[keyCode] = FALSE;
            // 先计算时长并重置状态,再返回
            pressDuration = GetTickCount() - keyPressTimes[keyCode];
            keyState[keyCode] = false;

            if (mode == KEEP_TIME) 
            {
                return pressDuration;
            }
        }
    }

    if (mode == KEEP) 
    {
        return currentKeyState ? 1 : 0;
    }
    // KEEP_TIME模式下,未释放时返回0
    return 0;
}

修复说明

  1. 调整代码执行顺序:将keyState[keyCode] = false;移到return pressDuration;之前,确保按键释放后状态被重置,不会重复进入释放分支计算时长。
  2. 统一返回值语义:所有模式下,返回0表示无有效触发/时长,返回非0值对应触发(SINGLE/KEEP)或时长(KEEP_TIME),避免逻辑混淆。
  3. 优化分支逻辑:KEEP_TIME模式仅在按键释放瞬间返回时长,其余帧返回0,游戏循环中只会输出一次正确的时长。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 03:44:55