it-swarm.com.de

Wie kann man unter Windows UTF-8-Strings in std :: cout drucken?

Ich schreibe eine plattformübergreifende Anwendung in C++. Alle Zeichenfolgen sind intern UTF-8-kodiert. Betrachten Sie den folgenden vereinfachten Code:

#include <string>
#include <iostream>

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

    return 0;
}

Auf Unix-Systemen erwartet std::cout 8-Bit-Zeichenfolgen, die UTF-8-kodiert sind, sodass dieser Code gut funktioniert.

Unter Windows erwartet std::cout jedoch, dass 8-Bit-Zeichenfolgen im Latin-1-Format oder einem ähnlichen Nicht-Unicode-Format vorliegen (abhängig von der Codepage). Dies führt zu folgender Ausgabe:

Griechisch: ╬▒╬▓╬│╬┤; Deutsch: ber £ bergr├├ƒentr├ñger

Was kann ich tun, um 8-Bit-Strings unter std::cout unter UTF-8 unter Windows zu interpretieren?

Das habe ich versucht:

#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;
}

Ich hatte gehofft, dass _setmode den Trick tun würde. Das führt jedoch zu dem folgenden Assertionsfehler in der Zeile, die operator<< aufruft:

Microsoft Visual C++ - Laufzeitbibliothek

Debug-Assertion fehlgeschlagen!

Programm: d:\visual studio 2015\Projects\utf8test\Debug\utf8test.exe Datei: minkernel\crts\ucrt\src\appcrt\stdio\fputc.cpp Linie: 47

Ausdruck: ((_Stream.is_string_backed ()) || (fn = _fileno (_Stream.public_stream ()), ((_textmode_safe (fn) == __crt_lowio_text_mode :: ansi) &&! _Tm_unicode_safe (fn)))

Informationen dazu, wie Ihr Programm eine Assertion verursachen kann Fehler finden Sie in der Visual C++ - Dokumentation zu Zusicherungen.

16
Daniel Wolf

Das Problem ist nicht std::cout, Sondern die Windows-Konsole. Mit C-stdio erhalten Sie den ü Mit fputs( "\xc3\xbc", stdout );, nachdem Sie die UTF-8-Codepage eingestellt haben (entweder mit SetConsoleOutputCP oder chcp) und Festlegen einer Unicode-unterstützenden Schriftart in den cmd-Einstellungen (Consolas sollte nterstützt über 2000 Zeichen und es gibt Registry-Hacks, um cmd leistungsfähigere Schriftarten hinzuzufügen) .

Wenn Sie ein Byte nach dem anderen mit putc('\xc3'); putc('\xbc'); ausgeben, erhalten Sie das doppelte Tofu, da die Konsole sie separat als unzulässige Zeichen interpretiert. Dies ist wahrscheinlich, was die C++ - Streams tun.

Eine ausführliche Beschreibung finden Sie unter TF-8-Ausgabe auf der Windows-Konsole .

Für mein eigenes Projekt habe ich endlich einen std::stringbuf Implementiert, der die Konvertierung auf Windows-1252 durchführt. Wenn Sie wirklich eine vollständige Unicode-Ausgabe benötigen, hilft Ihnen dies jedoch nicht wirklich.

Ein alternativer Ansatz wäre das Überschreiben von couts streambuf, wobei fputs für die eigentliche Ausgabe verwendet wird:

#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;
}

Ich habe die Ausgabepufferung hier deaktiviert, um zu verhindern, dass sie unvollendete UTF-8-Byte-Sequenzen beeinträchtigt.

7
mkluwe

Endlich habe ich es funktioniert. Diese Antwort kombiniert den Input von Miles Budnek, Paul und Mkluwe mit einigen eigenen Recherchen. Lassen Sie mich zunächst mit code beginnen, der unter Windows 10 funktionieren wird. Danach gehe ich Sie durch den Code und erkläre, warum er unter Windows 7 nicht funktioniert.

#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;
}

Der Code beginnt mit der Einstellung der Codepage wie von Miles Budnik vorgeschlagen . Dadurch wird die Konsole angewiesen, den empfangenen Byte-Stream als UTF-8, not als eine Variation von ANSI zu interpretieren.

Als Nächstes liegt ein Problem im mit Visual Studio gelieferten STL-Code vor. std::cout druckt seine Daten in einen Stream-Puffer vom Typ std::basic_filebuf. Wenn dieser Puffer eine Zeichenfolge empfängt (über std::basic_streambuf::sputn()), wird er nicht als Ganzes an die darunterliegende Datei weitergegeben. Stattdessen wird jedes Byte separat übergeben. Wie von mkluwe erklärt , wenn die Konsole eine UTF-8-Bytefolge als einzelne Bytes empfängt, werden sie nicht als einzelner Codepunkt interpretiert. Stattdessen werden sie als mehrere Zeichen behandelt. Jedes Byte innerhalb einer UTF-8-Bytefolge ist ein eigener ungültiger Codepunkt, daher werden stattdessen s angezeigt. Es gibt einen verwandten Fehlerbericht für Visual Studio , der aber als By Design geschlossen wurde. Die Problemumgehung besteht darin, die Pufferung für den Stream zu aktivieren. Als zusätzlicher Bonus erhalten Sie eine bessere Leistung. Möglicherweise müssen Sie jedoch den Stream regelmäßig wie bei std::endl leeren, oder Ihre Ausgabe wird möglicherweise nicht angezeigt.

Schließlich unterstützt die Windows-Konsole sowohl Raster-Schriftarten als auch TrueType-Schriftarten. Wie von Paul erwähnt , ignorieren Rasterschriften einfach die Codepage der Konsole. Nicht-ASCII-Unicode-Zeichen funktionieren daher nur, wenn für die Konsole eine TrueType-Schriftart festgelegt ist. Bis zu Windows 7 ist die Standardeinstellung eine Raster-Schriftart, sodass der Benutzer sie manuell ändern muss. Glücklicherweise ändert Windows 10 ändert die Standardschriftart in Consolas , so dass sich dieser Teil des Problems mit der Zeit lösen sollte.

10
Daniel Wolf

std::cout tut genau das, was es soll: Es sendet Ihren UTF-8-codierten Text an die Konsole, aber Ihre Konsole interpretiert diese Bytes anhand der aktuellen Codepage. Sie müssen die Konsole Ihres Programms auf die UTF-8-Codepage einstellen:

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

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

Es wäre großartig, wenn Windows die Standard-Codepage auf UTF-8 umstellt, dies ist jedoch wahrscheinlich aus Gründen der Rückwärtskompatibilität nicht möglich.

6
Miles Budnek

Einige Unicode-Zeichen können in einem Konsolenfenster nicht richtig angezeigt werden, auch wenn Sie die Codepage geändert haben, da Ihre Schrift dies nicht unterstützt. Beispielsweise müssen Sie eine Schriftart installieren, die Arabisch unterstützt, wenn Sie arabische Zeichen anzeigen möchten.

Diese stackoverflow-Seite sollte hilfreich sein.

Die Unicode-Version von Konsolen-APIs (wie WriteConsoleW) ist übrigens nicht hilfreich, da sie intern ihre entsprechenden Windows-Codepage-APIs (wie WriteConsoleA) aufrufen. Weder wird std :: wcout auch helfen, da es wchar_t string intern in char string konvertiert.

Es scheint, dass Windows-Konsolenfenster Unicode nicht gut unterstützt. Ich empfehle Ihnen, stattdessen MessageBox zu verwenden.

0
liuqx

Legen Sie die Konsolenausgabecodierung mithilfe des folgenden Windows-API-Aufrufs auf UTF-8 fest:

SetConsoleOutputCP(65001);

Die Dokumentation für diese Funktion ist unter Windows Dev Center verfügbar.

0
jfroy