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模式,使其仅在按键释放时返回正确的按下时长。
问题分析
- 状态未重置:KEEP_TIME分支中,
return pressDuration;语句在keyState[keyCode] = false;之前执行,导致重置代码永远无法运行。按键释放后,keyState[keyCode]始终保持true,后续每帧都会进入释放分支,重新计算当前时间与按下时间的差值,返回值持续递增。 - 返回值语义混乱:函数在不同模式下返回值含义不一致(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; }
修复说明
- 调整代码执行顺序:将
keyState[keyCode] = false;移到return pressDuration;之前,确保按键释放后状态被重置,不会重复进入释放分支计算时长。 - 统一返回值语义:所有模式下,返回0表示无有效触发/时长,返回非0值对应触发(SINGLE/KEEP)或时长(KEEP_TIME),避免逻辑混淆。
- 优化分支逻辑:KEEP_TIME模式仅在按键释放瞬间返回时长,其余帧返回0,游戏循环中只会输出一次正确的时长。
内容的提问来源于stack exchange,提问作者PHIKILL
相关产品推荐
相关产品推荐

