c - Windows Shell 中的 ReadConsoleInput 缺少事件?

标签 c winapi stdio

当我继续(可能是徒劳的)尝试重新实现一个支持 *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 和 select on 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 接口(interface)(而不是 GetAsyncKeyState)的要点是事件应该正确缓冲吗?

帮助!

最佳答案

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

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

关于c - Windows Shell 中的 ReadConsoleInput 缺少事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27586641/

相关文章:

c++ - COM+ 发布方法未降为 0

c - 如何访问 UNICODE_STRING 命令行变量?

winapi - Google 登录并在 Windows Phone 8.1 WinRT 应用程序中检索个人资料信息

C文件读取留下乱码

c - 如何使用C从文件中读取二维数组?

c - pthread_mutex_lock 在 C 中返回 22

c++ - 在不更改源文件的情况下重建exe时如何自动链接新的静态库?

c - 引用 char 数组时出现段错误

c - 该程序的输出

c - stdin 上的 close/fclose 是否保证正确?