web-dev-qa-db-ja.com

WindowsでUTF-8文字列をstd :: coutに出力する方法は?

C++でクロスプラットフォームアプリケーションを作成しています。すべての文字列は、内部でUTF-8エンコードされています。次の簡略化されたコードを検討してください。

#include <string>
#include <iostream>

int main() {
    std::string test = u8"Greek: αβγδ; German: Übergrößenträger";
    std::cout << test;

    return 0;
}

Unixシステムでは、std::coutは8ビット文字列がUTF-8でエンコードされていると想定しているため、このコードは正常に機能します。

ただし、Windowsでは、std::coutは、8ビット文字列がLatin-1または同様の非Unicode形式(コードページによって異なる)であると想定しています。これにより、次の出力が得られます。

ギリシャ語:╬▒╬▓╬│╬┤;ドイツ語:├£bergr├Â├ƒentr├ñger

Windowsでstd::coutに8ビット文字列をUTF-8として解釈させるにはどうすればよいですか?

これは私が試したものです:

#include <string>
#include <iostream>
#include <io.h>
#include <fcntl.h>

int main() {
    _setmode(_fileno(stdout), _O_U8TEXT);
    std::string test = u8"Greek: αβγδ; German: Übergrößenträger";
    std::cout << test;

    return 0;
}

_setmodeがうまくいくことを望んでいました。ただし、これにより、operator<<を呼び出す行で次のアサーションエラーが発生します。

Microsoft Visual C++ランタイムライブラリ

デバッグアサーションに失敗しました!

プログラム:d:\ visual studio 2015\Projects\utf8test\Debug\utf8test.exeファイル:minkernel\crts\ucrt\src\appcrt\stdio\fputc.cpp行:47

式:((_Stream.is_string_backed())||(fn = _fileno(_Stream.public_stream())、((_textmode_safe(fn)== __crt_lowio_text_mode :: ansi)&&!_tm_unicode_safe(fn)))))

プログラムがアサーションエラーを引き起こす方法については、アサーションに関するVisual C++ドキュメントを参照してください。

18
Daniel Wolf

問題は_std::cout_ではなく、Windowsコンソールです。 C-stdioを使用すると、UTF-8コードページを設定した後、fputs( "\xc3\xbc", stdout );で_ü_を取得できます(SetConsoleOutputCPまたはchcpを使用)andcmdの設定でUnicodeをサポートするフォントを設定します(Consolasは 2000文字以上をサポート であり、cmdにより高性能なフォントを追加するレジストリハッキングがあります)。

putc('\xc3'); putc('\xbc');を使用して1バイトずつ出力すると、コンソールが不正な文字として個別に解釈されるため、二重豆腐が得られます。これはおそらくC++ストリームが行うことです。

詳細な説明については、 WindowsコンソールでのUTF-8出力 を参照してください。

私自身のプロジェクトでは、最終的にWindows-1252への変換を行う_std::stringbuf_を実装しました。完全なUnicode出力が本当に必要なのですが、これは実際には役に立ちません。

別のアプローチは、実際の出力にcoutを使用して、fputsのstreambufを上書きすることです。

_#include <iostream>
#include <sstream>

#include <Windows.h>

class MBuf: public std::stringbuf {
public:
    int sync() {
        fputs( str().c_str(), stdout );
        str( "" );
        return 0;
    }
};

int main() {
    SetConsoleOutputCP( CP_UTF8 );
    setvbuf( stdout, nullptr, _IONBF, 0 );
    MBuf buf;
    std::cout.rdbuf( &buf );
    std::cout << u8"Greek: αβγδ\n" << std::flush;
}
_

未完成のUTF-8バイトシーケンスと干渉しないように、ここで出力バッファリングをオフにしました。

7
mkluwe

ついに、私はそれを機能させました。この回答は、Miles Budnek、Paul、およびmkluweからの入力と、独自のいくつかの研究を組み合わせたものです。最初に、Windows 10で動作するコードから始めましょう。その後、コードを説明し、Windows 7ですぐに動作しない理由を説明します。

_#include <string>
#include <iostream>
#include <Windows.h>
#include <cstdio>

int main() {
    // Set console code page to UTF-8 so console known how to interpret string data
    SetConsoleOutputCP(CP_UTF8);

    // Enable buffering to prevent VS from chopping up UTF-8 byte sequences
    setvbuf(stdout, nullptr, _IOFBF, 1000);

    std::string test = u8"Greek: αβγδ; German: Übergrößenträger";
    std::cout << test << std::endl;
}
_

コードは、コードページを設定することから始まります Miles Budnikの提案どおり これにより、受信したバイトストリームをUTF-8として解釈するようコンソールに指示します。ANSIのバリエーションとしてnot

次に、Visual Studioに付属のSTLコードに問題があります。 _std::cout_は、データを_std::basic_filebuf_型のストリームバッファーに出力します。そのバッファが(std::basic_streambuf::sputn()を介して)文字列を受け取ると、それを全体として基礎となるファイルに渡しません。代わりに、各バイトを個別に渡します。 mkluweで説明したように 、コンソールがUTF-8バイトシーケンスを個々のバイトとして受け取った場合、それらは単一のコードポイントとして解釈されません。代わりに、それらを複数の文字として扱います。 UTF-8バイトシーケンス内の各バイトは、それ自体では無効なコードポイントであるため、代わりに�が表示されます。 Visual Studioの関連バグレポート がありますが、By Designとしてクローズされました。回避策は、ストリームのバッファリングを有効にすることです。追加のボーナスとして、パフォーマンスが向上します。ただし、_std::endl_の場合と同様に、ストリームを定期的にフラッシュする必要がある場合があります。そうしないと、出力が表示されない場合があります。

最後に、Windowsコンソールは、ラスターフォントとTrueTypeフォントの両方をサポートしています。 Paulが指摘したように の場合、ラスタフォントは単にコンソールのコードページを無視します。そのため、非ASCII Unicode文字は、コンソールがTrueTypeフォントに設定されている場合にのみ機能します。 Windows 7までは、デフォルトはラスタフォントであるため、ユーザーは手動で変更する必要があります。幸いなことに、 Windows 10はデフォルトのフォントをConsolasに変更します なので、問題のこの部分は時間とともに解決するはずです。

11
Daniel Wolf

std::coutはまさにそのとおりです。UTF-8でエンコードされたテキストをコンソールに送信しますが、コンソールは現在のコードページを使用してこれらのバイトを解釈します。プログラムのコンソールをUTF-8コードページに設定する必要があります。

#include <string>
#include <iostream>
#include <Windows.h>

int main() {
    std::string test = u8"Greek: αβγδ; German: Übergrößenträger";
    SetConsoleOutputCP(CP_UTF8);
    std::cout << test;
}

Windowsが既定のコードページをUTF-8に切り替えた場合、それは素晴らしいことですが、下位互換性の問題のためにできない可能性があります。

6
Miles Budnek

次のWindows API呼び出しを使用して、コンソール出力エンコーディングをUTF-8に設定します。

SetConsoleOutputCP(65001);

その機能のドキュメントは Windows Dev Center で入手できます。

1
jfroy

一部のUnicode文字は、フォントがサポートしていないため、コードページを変更した場合でもコンソールウィンドウに正しく表示できません。たとえば、アラビア語の文字を表示する場合は、アラビア語をサポートするフォントをインストールする必要があります。

このstackoverflowページ は役立つはずです。

ところで、UnicodeバージョンのコンソールAPI(WriteConsoleWなど)は、対応するWindowsコードページバージョンAPI(WriteConsoleAなど)を内部的に呼び出すため、助けにはなりません。どちらもstd :: wcoutには役立ちません。内部でwchar_t文字列をchar文字列に変換するからです。

WindowsコンソールウィンドウはUnicodeをサポートしていないようです。代わりにMessageBoxを使用することをお勧めします。

0
liuqx