【发布时间】:2014-12-21 04:06:25
【问题描述】:
当我继续(可能是徒劳的)尝试重新实现一个在 MIT 许可下支持 *nix 和 windows 的 curses 样式库时,我偶然发现了使用 windows api 读取终端导入的问题。
基本上,我没有得到我期望的所有事件,我也不知道为什么。
首先我将终端设置为非缓冲模式:
DWORD mode;
HANDLE hstdin = GetStdHandle( STD_INPUT_HANDLE );
// Save old mode
GetConsoleMode(hstdin, &mode);
// Set to no line-buffering, no echo, no special-key-processing
SetConsoleMode(hstdin, 0);
然后我在循环中使用 PeekConsoleInput 和 ReadConsoleInput 来获得非阻塞按键输入;相当于在 linux 中使用 termios.h 并在 stdin 上选择:
__EXPORT int sterm_read(void *state) {
DWORD dwRead;
INPUT_RECORD inRecords[1];
PeekConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &inRecords[0], 1, &dwRead);
if (dwRead > 0) {
ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &inRecords[0], 1, &dwRead);
if (inRecords[0].EventType == KEY_EVENT) {
if (inRecords[0].Event.KeyEvent.bKeyDown) {
return inRecords[0].Event.KeyEvent.wVirtualKeyCode;
}
}
}
return -1;
}
忽略状态变量;这样 api 就可以在各种平台上接受任意状态结构。
现在,如果我尝试使用此代码:
#include <sterm.h>
#include <stdio.h>
#define assert(v, msg) if (!v) { printf("FAILED! %s", msg); return 1; }
int main(void) {
void *state = sterm_init();
int i;
char c;
for (;;) {
if ((c = sterm_read(state)) == 81) { // ie. press q to exit
break;
}
if (c != -1) {
sterm_write(state, &c, 1); // This is a thin wrapper around _write(1, ...)
}
}
sterm_shutdown(state);
return 0;
}
它几乎可以工作。我将按下的输入字符推到终端...主要是。
大概每按 10 个字符就会被记录一次。如果我快速输入,API 会“丢失”事件,我会得到“HEO WLD”而不是“HELLO WORLD”。
发生了什么事? ReadConsoleInput 是否以某种方式清除输入缓冲区?
我做错了吗?似乎我只是根据“调用 PeekConsoleInput 时按下键”的竞争条件来获取事件。
...但肯定不应该是这样吗?使用这些缓冲 I/O 接口(而不是 GetAsyncKeyState)的重点是应该缓冲事件对吗?
救命!
【问题讨论】:
-
只是猜测,但可能是使用 CI/O 函数导致 C 运行时库读取控制台输入,窃取您期望的击键并将它们粘贴在 C 运行时的输入缓冲区中.尝试使用 WriteConsoleOutput 或 WriteFile 代替 _write。
-
@HarryJohnston,我只看到提到的
_write,它不会触及控制台输入缓冲区。 Doug 的示例代码对我有用,但需要做更多工作才能正确解码关键事件,如 C 运行时的_getwch函数的已发布源代码所示。 -
@Doug:你用的是什么编译器?你能提供一个 MCVE 吗?
-
首先,如果你想知道是否有输入事件,你应该调用GetNumberOfConsoleInputEvents。其次,您没有检查来自
PeekConsoleInput或ReadConsoleInput的返回值,因此无法判断是否可能发生错误。第三,这是一个非常紧密的循环。在等待输入时,您将消耗 100% 的 CPU。这看起来很极端,很可能是您的问题的原因:CPU 一直在请求输入,而硬件在边缘无法得到一个字。