俺と Win32 コンソール(1):オレオレ wcwidth
最近なんとなく Win32 のコンソール API をごにょごにょしたくなる気分だったのです。なんか微妙に説明不足感が漂うんですよね、このへんって…。
ということで(どういうことなんだろう)、コンソールに表示する文字の幅を調べる wcwidth 的なやつ。素直に WideCharToMultiByte とかでやりゃいい気もしますが、せっかくだからコンソールに直接訊いてみたりなんかして。
ということでそれらしいソース。
つ
/* int wcwidth_win32(WCHAR) あらかじめ init_wcwidth_win32(FALSE) を実行しておくこと */ static BOOL bAllocConsoleByMyself = FALSE; static HANDLE hHiddenConsole = INVALID_HANDLE_VALUE; static int init_wcwidth_win32(BOOL bForceAllocConsole) { HANDLE h; if (bForceAllocConsole && !bAllocConsoleByMyself) { bAllocConsoleByMyself = AllocConsole(); } if (hHiddenConsole != INVALID_HANDLE_VALUE) return hHiddenConsole; h = CreateConsoleScreenBuffer( GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, CONSOLE_TEXTMODE_BUFFER, NULL); if (h != INVALID_HANDLE_VALUE) { CONSOLE_SCREEN_BUFFER_INFO csbi; memset(&csbi, 0, sizeof(csbi)); if (GetConsoleScreenBufferInfo(h, &csbi) && csbi.dwSize.X >= 2) { SetConsoleMode(h, 0); hHiddenConsole = h; } else { CloseHandle(h); h = INVALID_HANDLE_VALUE; } } return h; } static void exit_wcwidth_win32() { HANDLE h = hHiddenConsole; if (h != INVALID_HANDLE_VALUE) { hHiddenConsole = INVALID_HANDLE_VALUE; CloseHandle(h); } if (bAllocConsoleByMyself) { bAllocConsoleByMyself = FALSE; FreeConsole(); } } int wcwidth_win32(WCHAR wc) { int rc = -1; if (hHiddenConsole != INVALID_HANDLE_VALUE) { COORD pos; CONSOLE_CURSOR_INFO cci, cci_prev; DWORD dw; WCHAR w[2]; if (wc == 0) return 0; pos.X = 0; pos.Y = 0; memset(&cci, 0, sizeof(cci)); GetConsoleCursorInfo(hHiddenConsole, &cci_prev); cci = cci_prev; cci.bVisible = FALSE; SetConsoleCursorInfo(hHiddenConsole, &cci); SetConsoleCursorPosition(hHiddenConsole, pos); w[0] = w[1] = 0; WriteConsoleW(hHiddenConsole, w, 2, NULL, NULL); /* clear buffer (just for a proof) */ SetConsoleCursorPosition(hHiddenConsole, pos); w[0] = wc; dw = 0; if (WriteConsoleW(hHiddenConsole, w, 1 /* sizeof(w)/sizeof(w[0]) */, &dw, NULL)) { CONSOLE_SCREEN_BUFFER_INFO csbi; if (GetConsoleScreenBufferInfo(hHiddenConsole, &csbi)) { rc = csbi.dwCursorPosition.X; } } SetConsoleCursorInfo(hHiddenConsole, &cci_prev); } return rc; }
要するにやってることは、「指定された WCHAR 文字コードを隠しコンソールバッファに WriteConsoleW で書きこみ、その際のカーソルの移動量を文字幅とみなす」ということです。隠しコンソールなので書き込んでもメインコンソールに影響は出ません。そのはずなのになぜカーソルの一時消去・再表示を行っているのかというと、「そうしないとメインコンソール内のカーソル表示が微妙にバグったから」です。DOS/V の $DISP.SYS じゃないんだからさ、まったくもう…。
しかしサロゲートペアが必要な文字とか、コンソールバッファ中ではどういう扱いになってるんでしょうねえ。単にまったく考慮してないだけのような気もしますが。