在Windows shell中的ReadConsoleInput上缺少事件?

时间:2014-12-21 04:06:26

标签: c winapi stdio

当我继续(可能是徒劳)尝试重新实现在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)的关键是事件应该正确缓冲吗?

帮助!

1 个答案:

答案 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];
    }
  }}
}}