none
1809 での Console-API の不具合と思われるもの RRS feed

  • 全般的な情報交換

  • Win10 1809 での Console-API の不具合と思われる現象を見つけました。

    ただ、こちらの環境でもフォントやウィンドウサイズを変えたりすると起きなくなったりするので、他の方の環境で同様に再現するのか自信はありません。

    下記のCサンプルはコマンドプロンプトや PowerShell などのコンソールから起動するもので、コンソールおよびソースプログラムのコードページは 932 (ShiftJIS) を使用しています。

    処理内容は、まずスクリーン・バッファを切り替えた後、カーソルを原点に置いて "12あいうえお" と出力し、3秒後に再び原点に戻して前の表示を上書きする形で "12あ56789" と出力、続けて "abcde" と出力しています。

    これで画面の表示は最終的に "12あ56789abcde" となるはずですが、'9' が表示されず "12あ5678 abcde" となってしまう、という不具合です。

    コメントにしている Sleep(1) を有効にすると、なぜか正常に表示されます。また、レガシーコンソール・モードではこの現象は起きません。

    みなさんの環境ではどう表示されるでしょうか?

    #include <windows.h>
    #include <locale.h>
    
    int main()
    {
        DWORD dwWrote;
        BOOL fRc;
    
        setlocale(LC_ALL, "");
    
        // スクリーンバッファ切替
        HANDLE hCon1 = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE,
                                FILE_SHARE_WRITE, 0, CONSOLE_TEXTMODE_BUFFER, 0);
        if (hCon1 == INVALID_HANDLE_VALUE) return 1;
    
        fRc = SetConsoleActiveScreenBuffer(hCon1);
        if(!fRc) return 1;
    
        // カーソルを原点に
        COORD tCoordOrg;
        tCoordOrg.X = tCoordOrg.Y = 0;
    
        fRc = SetConsoleCursorPosition(hCon1, tCoordOrg);
        if (!fRc) return 1;
    
        // 最初のマルチバイトを含む出力
        const char* psz0 = "12あいうえお";
    
        fRc = WriteConsole(hCon1, psz0, (DWORD)strlen(psz0), &dwWrote, 0);
        if (!fRc) return 1;
    
        Sleep(3000);    // 3秒待つ
    
        // カーソルを原点に戻す
        fRc = SetConsoleCursorPosition(hCon1, tCoordOrg);
        if (!fRc) return 1;
    
        // 再び出力。末尾の '9' は前に書いた「え」を半分つぶす
        const char* psz1 = "12あ56789";
    
        fRc = WriteConsole(hCon1, psz1, (DWORD)strlen(psz1), &dwWrote, 0);
        if (!fRc) return 1;
    
    //  Sleep(1);   // ← これを有効にすると問題は起きない
    
        // 続けて出力。これで "12あ56789abcde" と書いたはず
        const char* psz2 = "abcde";
    
        fRc = WriteConsole(hCon1, psz2, (DWORD)strlen(psz2), &dwWrote, 0);
        if (!fRc) return 1;
    
        Sleep(5000);    // 5秒後に終了
        return 0;
    }
    

    2019年4月7日 18:42

すべての返信

  • FYI
    --------------------------------------------------------
    strlen, wcslen, _mbslen, _mbslen_l, _mbstrlen, _mbstrlen_l
    https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/strlen-wcslen-mbslen-mbslen-l-mbstrlen-mbstrlen-l?view=vs-2019

    Remarks
    strlen interprets the string as a single-byte character string,
    so its return value is always equal to the number of bytes,
    even if the string contains multibyte characters.
    --------------------------------------------------------
    2019年4月8日 0:55
  • 知っていますよ。
    strlen の仕様は WriteConsole に与える値として相応しいものです。
    シフトJIS 環境ですからね。
    2019年4月8日 14:33
  • 知っているなら、ていじしているコードはおかしいと思うのだが?
    2019年4月8日 15:23
  • 「思う」ではなく、確認してから書いて頂けるとありがたいです。
    ちなみにですが WriteConsole の実体は、ここでは WriteConsoleA ですね。
    ShiftJIS と言っているので紛らわしくはないはずですが、念のため。
    2019年4月8日 16:58
  • 1809 の手元で実行しましたが同様の実行結果でした。
    _T マクロや TCHAR、_tcslen に置き換えて、Unicode 文字セットでも試し、引き続き再現するようなので文字セット依存ではなさそうですね。
    なお、当方の環境では Sleep(1); では現象が改善しませんでしたので、もっと長めの安定待ちが必要そうです。

    -----
    直前に書いた内容は間違っていたので消しました。返信通知を受けている方は読み捨ててください。
    個人的意見ですが、マルチバイト文字セット前提でコードを書くなら、WriteConsole といったプロジェクトの文字セットの設定で展開結果が変わるものより、WriteConsoleA と書いた方が誤解は少ないやもしれません。

    2019年4月8日 21:40
    モデレータ
  • だから偶数文字数に対して、strlen が奇数文字数を返してしまうことが問題なのでは?
    SBCS と DBCS の混在文字列に対して文字化けや文字ピッチずれが起きるのは、Console API に限った話ではない。
    「全角 半角 混在」等で検索すれば、似たような事例はいくらでも出てくる。
    一番手っ取り早い対応策としては、SBCS の文字数を偶数個に合わせること。
    例えば提示してあるコードの場合、以下のように SBCS の文字数を偶数個に合わせるだけで問題はおきなくなる「はず」。

    const char* psz1 = "012あ56789";
    2019年4月9日 9:22
  • Azulean 様、網羅的に検証頂いたようで恐縮です。やはり起きましたか。
    Sleep(1) の有無は環境依存のようですが、私のマシンが非力なせいもあるかもしれません。参考になります。

    WriteConsoleA の件はおっしゃるとおりです。
    後で自分でも弁解がましい追記をしてますが、分かる人には分かるだろうから、まいっかと (笑)
    いや、サンプルであれば特に曖昧性のないように気を配るべきでした。
    以後、気を付けます。

    2019年4月9日 15:30
  • WriteConsoleA の第三引数に渡すのは、第二引数で指定した領域の単なる領域長 (バイト数) ですので、その偶奇と領域に含まれている文字数の偶奇を合わせなければならないとしたら随分な制限ですね。関係ないと思いますが。

    > const char* psz1 = "012あ56789";

    これで問題が起きないのは当然なんですが、私のサンプルの意図を汲み取って頂けてはいないようです。
    次のパターンでは SBCS も DBCS も、SJIS でのバイト長 (strlen) も全て偶数ですが、私の環境では同じ現象が起こります。

    const char* psz0 = "123あいうえおか4";
    const char* psz1 = "123あい789";
    const char* psz2 = "abcdef";

    Console-API 以外でも同じような現象は、そりゃ起きてるかも知れません。
    でも、それはその領域の不具合ではないでしょうか。

    言いたいのは、私の知る限り WinNT 時代からこのかた、いや Win95 時代の Console-API からでも存在しなかった問題が、1809 では起きているということです。

    2019年4月9日 15:32
  • https://docs.microsoft.com/en-us/windows/console/writeconsole
    第3引数の説明には「The number of characters to be written. 」と書いてあります。バッファサイズではなく、書き出す文字の数、のようですが?
    AとWで引数の意味が違うなら、それはそれで面倒…

    Jitta@わんくま同盟

    2019年4月10日 0:46
  • ntdll.dll 内に実装されている以下の Native API の違いをきちんと理解されることをお勧めします。
    (WriteConsole API が最終的に以下の関数を呼び出すかは確認していませんが、おそらくこれに準ずる Native API を呼び出していると「思う」ので。)
    ------------------------------------------------
    RtlStringCbVPrintfA function
    https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntstrsafe/nf-ntstrsafe-rtlstringcbvprintfa

    RtlStringCchPrintfA function
    https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntstrsafe/nf-ntstrsafe-rtlstringcchprintfa
    ------------------------------------------------


    • 編集済み お馬鹿 2019年4月10日 1:14 先に返信された方との重複部分を削除
    2019年4月10日 1:09
  • Jitta 様
    記述者の意図と読む人の解釈にはギャップが生じ得るものですから、それを埋めるには実際に動かして確かめるしかないですね。

    strlen の仕様は (Unicodeモードでない) fwrite などの旧来の C 標準関数と整合しています。

    const char* s = "ABCあいう";
    fwrite((void*)s, sizeof(char), strlen(s), fp);

    A付き API の仕様は、これらのレガシーと整合するように決められているということです。

    A と W で引数の意味が違うというか、元々異なるカテゴリなので共通に論じること自体に無理がある気がします。
    # TCHAR とかで隠蔽しようとするから、却ってぐちゃぐゃになるような。

    2019年4月10日 19:24
  • 質問者さんの検証によるとレガシーコンソール・モードで発生しないとのこと、Azuleanさんの検証によるとUnicode 文字セットでも発生するとのこと、この2点をふまえるとJittaさん、お馬鹿さんが指摘されているような引数誤りとは考えにくいです。私自身は何も検証していませんが、1809のバグが濃厚かなぁとみています。
    # 1903(1905?)で検証すると改善しているかもしれませんね。
    2019年4月10日 21:30
  • ご無沙汰してます。

    1903 ではこの不具合は発生しないことを確認しました。どうやらバグフィックスされたみたいです。
    これ以外にも、再現サンプルの提供が難しかったので書きませんでしたが、コンソールのカーソルブリンク問題 (点滅が止まったり不規則に点滅する) も解消してるようです。偉いぞ MS!

    まぁ、これから正式版が出てくるであろう新しい Windows Terminal への対応のことを思うと気が重いのですが (笑)
    ともかくご報告まで。皆さんありがとうございました。

    2019年7月13日 13:37