ReadConsoleInput 関数を呼び出すと次の文字の上位 1 バイトが返される場合がある

現象

Windows 8 / Windows Server 2012 以降のクライアントおよびサーバー OS において ReadConsoleInput 関数を使用して日本語を入力した際、以下の挙動となります。

  • 1回目の関数呼び出し時に日本語文字の上位 1 バイトが返され、2 回目の呼び出し時に、次の文字の上位 1 バイトが返されます

Windows 7 / Windows Server 2008 R2 の OS では、1 回目の関数呼び出し時に日本語文字の上位 1 バイトが返され、2 回目の呼び出し時に次の文字の下位 1 バイトが返されます。この関数の異なる挙動に伴い Windows 8 以降の getch() などのダブルバイト文字は扱う関数は影響を受け、telnet クライアントで日本語入力が文字化けする問題が確認されています。

例えば、以下の再現サンプルコードを実行し、”あ” を入力しますと Windows 7 の環境では ”82 a0” が返されますが、Windows 8 以降の環境では ”82 82” が返されます。

int _tmain(int argc, _TCHAR* argv[])
{
HANDLE std_in = GetStdHandle(STD_INPUT_HANDLE);
SetConsoleMode(std_in, ENABLE_PROCESSED_INPUT);
INPUT_RECORD input_record;
DWORD s = 0;

while (true) {

ReadConsoleInputA(std_in, &input_record, 1, &s);

if (input_record.EventType == KEY_EVENT && input_record.Event.KeyEvent.bKeyDown) {

printf("char code : %x\n", (unsigned char) input_record.Event.KeyEvent.uChar.AsciiChar);
}
}
return 0;
}

 

原因

Windows 8 ではソフトウェア キーボードをサポートするために ReadConsoleInput 関数の実装が変更されました。

Windows 7 までは CSRSS プロセスにおいて、キーボード入力を受け取る Raw Input Thread と通信をおこなってコンソール入力データを受信していました。 一方、Windows 8 / Windows Server 2012 以降の OS では、CSRSS プロセスが入力を処理する構成が変更され、ソフトウェア キーボードをサポートするために実装が変更されました。モジュール構成も、コンソール デバイスとの入出力を管理する ConDrv.sys ドライバーを通じてデータを受信するようになりました。 このような構成変更により、Windows 8 以降の OS では、 ReadConsoleInput 関数は ConDrv.sys の動作に依存した動作となり今回の現象が発生しています。

 

回避策

本現象は、ReadConsoleInput 関数の第三引数に指定するバッファ配列の個数を 2 または 2 以上に指定することで回避できます。ReadConsoleInput 関数の第三引数に指定するバッファを増やす方法で対処できます。

例えば、上記の再現サンプルコードにて、第三引数であるバッファーサイズを 2 に設定しています。

int _tmain(int argc, _TCHAR* argv[])
{
HANDLE std_in = GetStdHandle(STD_INPUT_HANDLE);
SetConsoleMode(std_in, ENABLE_PROCESSED_INPUT);
INPUT_RECORD input_record[2];
DWORD s = 0;
int i = 0;

while (true) {

ReadConsoleInputA(std_in, input_record, 2, &s);

if (input_record[0].EventType == KEY_EVENT && input_record[0].Event.KeyEvent.bKeyDown) {

for (i = 0; i < 2; i++)
{
printf("Byte %d char code : %x\n", i, (unsigned char)input_record[i].Event.KeyEvent.uChar.AsciiChar);
}
}
}
return 0;
}