当我继续(可能是徒劳)尝试重新实现在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来获得非阻塞按键输入;相当于使用termios.h并在linux中选择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就会失去&#39;事件,我得到&#34; HEO WLD&#34;而不是&#34; HELLO WORLD&#34;。
发生了什么? ReadConsoleInput是否以某种方式清除输入缓冲区?
我做错了吗?看起来几乎就像我只是根据竞争条件得到事件,当PeekConsoleInput被调用时,按键被按下了#39;。
......但当然不应该这样吗?使用这些缓冲的I / O接口(而不是GetAsyncKeyState)的关键是事件应该正确缓冲吗?
帮助!
答案 0 :(得分:0)
我还发现不能保证事件会一直存在以供阅读。 这是有道理的,否则操作系统将需要提供大量的缓冲空间。
我能做的最好的处理就是这段代码来做我自己的缓冲 但显然超过 128 个字符的粘贴通常会失败:
static int g_eaten_ct = 0; /* Re-eaten char */
static int g_eaten_ix = -1;
static int g_eaten[128];
void reeat(int c)
{ g_eaten_ct += 1;
g_eaten[g_eaten_ix + g_eaten_ct] = c; /* save the char for later */
}
void flush_typah()
{
g_eaten_ct = 0;
g_eaten_ix = -1;
while (_kbhit())
(void)ttgetc();
}
int ttgetc()
{ if (g_eaten_ct > 0)
{ g_eaten_ct -= 1;
return g_eaten[++g_eaten_ix];
}
{ int totalwait = g_timeout_secs;
int oix = -1;
while (1)
{ int got,need;
const DWORD lim = 1000;
INPUT_RECORD rec[32];
int cc = WaitForSingleObject(g_ConsIn, lim);
switch(cc)
{ case WAIT_OBJECT_0:
need = sizeof(g_eaten)/sizeof(g_eaten[0]) - oix;
if (need > 32)
need = 32;
cc = ReadConsoleInput(g_ConsIn,&rec[0],need,(DWORD*)&got);
if (cc && got > 0)
break;
#if _DEBUG
{ DWORD errn = GetLastError();
if (errn != 6)
mlwrite("%pError %d %d ", cc, errn);
}
#endif
continue;
case WAIT_TIMEOUT:
#if _DEBUG
if (g_got_ctrl)
{ g_got_ctrl = false;
return (int)(CTRL | 'C');
}
#endif
if (--totalwait == 0) // -w opt
exit(2);
// drop through
default:continue;
}
{ int ix = -1;
while (++ix < got)
{ INPUT_RECORD * r = &rec[ix];
if (r->EventType == KEY_EVENT && r->Event.KeyEvent.bKeyDown)
{ int ctrl = 0;
int keystate = r->Event.KeyEvent.dwControlKeyState;
if (keystate & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
{ ctrl |= CTRL;
g_chars_since_ctrl = 0;
}
{ int chr = r->Event.KeyEvent.wVirtualKeyCode;
if (in_range(chr, 0x10, 0x12))
continue; /* shifting key only */
if (keystate & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED))
ctrl |= ALTD;
else
chr = r->Event.KeyEvent.uChar.AsciiChar & 0xff;
if (/*chr != 0x7c && */ (chr | 0x60) != 0x7c) // | BSL < or ^ BSL
{ int vsc = r->Event.KeyEvent.wVirtualScanCode;
if (in_range(vsc, SCANK_STT, 0x58))
{ ctrl |= SPEC;
chr = scantokey[vsc - SCANK_STT];
}
// else if (in_range(vsc, 2, 10) && chr == 0)
// chr = '0' - 1 + vsc;
}
if ((keystate & SHIFT_PRESSED) && ctrl) // exclude e.g. SHIFT 7
ctrl |= SHFT;
g_eaten[++oix] = ctrl | (chr == 0xdd ? 0x7c : chr);
++g_chars_since_ctrl;
}}
else if (r->EventType == MENU_EVENT)
{ /*loglog1("Menu %x", r->Event.MenuEvent.dwCommandId);*/
}
}
if (got == need && oix < sizeof(g_eaten) / sizeof(int))
{ PeekConsoleInput(g_ConsIn, &rec[0], 1, (DWORD*)&got);
if (got > 0)
continue;
}
if (oix >= 0)
{ g_eaten_ct = oix;
g_eaten_ix = 0;
return g_eaten[0];
}
}}
}}