ノンブロッキングなgets

コマンドプロンプトで走る単純なツールを作る機会は少なくない、 基板と通信するようなコードだとgets でコマンド入力して送信する くらいの「対話型インタフェース」で充分だったりする。

ただ、ユーザからの入力待ち中に基板からのパケットを受け取れないと困る。 gets を非同期にしたいが、Linux的なノンブロックIOはどうも上手く互換動作 しないようだ。

なのでちょっと作ってみる

【AsyncInput.h】

#pragma once
class AsyncInput
{
public:
  static AsyncInput* GetInstance();
  static void Finalize();
  static char *Gets(char* buffer, size_t bufferSize);
  static void ScanKey();
private:
  static AsyncInput* _instance;
  AsyncInput();
  ~AsyncInput();
  void ScanKeyImpl();
  char* AsyncGets(char* buffer, size_t bufferSize);
  HANDLE _hStdin;
  DWORD _fdwMode;
  uint16_t _writeChar;
  uint16_t _readChar;
  CHAR  _charBuffer[256];
};


【AsyncInput.cpp】

#include <Windows.h>
#include <stdint.h>
#include <stdio.h>
//#include "_string.h"  //  改行コードの除去とかBackSpace対応とか
#include "AsyncInput.h"

AsyncInput* AsyncInput::_instance = NULL;

AsyncInput::AsyncInput() {
  _hStdin = GetStdHandle(STD_INPUT_HANDLE);
  GetConsoleMode(_hStdin, &_fdwMode);
  SetConsoleMode(_hStdin, ENABLE_WINDOW_INPUT);
  _writeChar = 0;
  _readChar = 0;
  _charBuffer[0] = '\0';
}

AsyncInput::~AsyncInput() {
  SetConsoleMode(_hStdin, _fdwMode);
}

AsyncInput* AsyncInput::GetInstance() {
  if (_instance != NULL)  return _instance;
  _instance = new AsyncInput();
  return _instance;
}

void AsyncInput::Finalize() {
  if (_instance == NULL)  return;
  delete _instance;
  _instance = NULL;
}

void AsyncInput::ScanKey() {
  auto instance = GetInstance();
  instance->ScanKeyImpl();
}

void AsyncInput::ScanKeyImpl() {
  INPUT_RECORD irInBuf[32];
  DWORD irRead;
  if (PeekConsoleInput(_hStdin, irInBuf, _countof(irInBuf), &irRead) && irRead > 0) {
    ReadConsoleInput(_hStdin, irInBuf, _countof(irInBuf), &irRead);
    for (int i = 0; i < irRead; ++i) {
      if (irInBuf[i].EventType != KEY_EVENT) continue;
      auto ascii = irInBuf[i].Event.KeyEvent.uChar.AsciiChar;
      auto keyDown = irInBuf[i].Event.KeyEvent.bKeyDown;
      if (ascii == 0 || !keyDown) continue;
      putchar(ascii);
      if (ascii == '\r') putchar('\n');
      uint16_t nw = (_writeChar + 1) % _countof(_charBuffer);
      if (nw == _readChar)  _readChar = (_readChar + 1) % _countof(_charBuffer);
      _charBuffer[_writeChar] = ascii;
      _writeChar = nw;
    }
  }
}

char* AsyncInput::Gets(char* line, size_t maxSize) {
  auto instance = GetInstance();
  instance->ScanKeyImpl();
  char* result = instance->AsyncGets(line, maxSize);
  if (result == NULL) return NULL;
  //_chomp(result);  //  改行を除去
  //_removebackspace(result);  //  バックスペースを適用
  return result;
}

char* AsyncInput::AsyncGets(char* line, size_t maxSize) {
  if (maxSize <= 1) {
    return NULL;
  }
  uint16_t wpos = 0, rpos;
  uint8_t c;
  uint16_t size = maxSize;
  --size;	//	make room for '\0'
  rpos = _readChar;
  while (wpos < size) {
    if (rpos == _writeChar) return NULL;
    c = _charBuffer[rpos];
    rpos = (rpos + 1) % _countof(_charBuffer);
    if (c != '\r' && c != '\n' && c != '\0') {
      line[wpos++] = c;
    }
    else if (wpos > 0) {
      break;
    }
  }
  _readChar = rpos;
  line[wpos] = '\0';
  return line;
}
このままだとカーソルキーはおろか、バックスペースにも対応していないので注意!
STL のqueue を使えば良さそうではあるが、説明が面倒くさいしそんな大げさなものはいらないので、 リングバッファを自前で実装

【使い方】


#include <stdio.h>
#include "AsyncInput.h"
void main(){
  char buffer[256];
  for(;;){
    char *input = AsyncInput::Gets(buffer,sizeof(buffer));
    if (input == NULL) continue;
    printf("%s\r\n",input);
  }
}