【问题标题】:Missing events on ReadConsoleInput in windows shell?Windows shell 中的 ReadConsoleInput 上缺少事件?
【发布时间】: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。其次,您没有检查来自PeekConsoleInputReadConsoleInput 的返回值,因此无法判断是否可能发生错误。第三,这是一个非常紧密的循环。在等待输入时,您将消耗 100% 的 CPU。这看起来很极端,很可能是您的问题的原因:CPU 一直在请求输入,而硬件在边缘无法得到一个字。

标签: c winapi stdio


【解决方案1】:

我还发现,事件并不能保证会一直被阅读。 这是有道理的,否则操作系统将需要提供大量的缓冲空间。

我能做的最好的处理就是这段代码来做我自己的缓冲 但显然超过 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];
    }
  }}
}}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-01-23
    • 2023-01-14
    • 1970-01-01
    • 2018-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多