web-dev-qa-db-fra.com

Comment imprimer des chaînes UTF-8 sur std :: cout sous Windows?

J'écris une application multiplateforme en C++. Toutes les chaînes sont codées en UTF-8 en interne. Considérons le code simplifié suivant:

#include <string>
#include <iostream>

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

    return 0;
}

Sur les systèmes Unix, std::cout s'attend à ce que les chaînes 8 bits soient codées en UTF-8. Ce code fonctionne donc bien.

Cependant, sous Windows, std::cout s'attend à ce que les chaînes 8 bits soient au format Latin-1 ou dans un format similaire non Unicode (selon la page de code). Cela conduit à la sortie suivante:

Grec:; Allemand: ber £ bergr├ƒ├ƒentr├ñger

Que puis-je faire pour que std::cout interprète les chaînes de 8 bits en UTF-8 sous Windows?

C'est ce que j'ai essayé:

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

J'espérais que _setmode ferait l'affaire. Cependant, cela entraîne l'erreur d'assertion suivante dans la ligne qui appelle operator<<:

Bibliothèque d'exécution Microsoft Visual C++

Debug assertion a échoué!

Programme: d:\visual studio 2015\Projets\utf8test\Debug\utf8test.exe Fichier: minkernel\crts\ucrt\src\appcrt\stdio\fputc.cpp Ligne: 47

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

Pour savoir comment votre programme peut provoquer une assertion En cas d'échec, voir la documentation de Visual C++ sur les assertions.

16
Daniel Wolf

Le problème n'est pas std::cout mais la console Windows. En utilisant C-stdio, vous obtiendrez le ü avec fputs( "\xc3\xbc", stdout ); après avoir défini la page de codes UTF-8 (soit en utilisant SetConsoleOutputCP ou chcp) et en définissant une police prenant en charge la police dans les paramètres de cmd (Consolas devrait prend en charge plus de 2000 caractères et il y a des hacks de registre pour ajouter plus de polices capables à cmd).

Si vous affichez un octet après l'autre avec putc('\xc3'); putc('\xbc');, vous obtiendrez le double tofu au fur et à mesure que la console les interprète séparément. C'est probablement ce que font les flux C++.

Voir Sortie UTF-8 sur la console Windows pour une discussion approfondie.

Pour mon propre projet, j'ai finalement implémenté un std::stringbuf effectuant la conversion vers Windows-1252. Si vous avez vraiment besoin d’une sortie Unicode intégrale, cela ne vous aidera pas vraiment.

Une autre approche consisterait à écraser le streambuf de cout, en utilisant fputs pour la sortie réelle:

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

J'ai désactivé la mise en mémoire tampon de sortie ici pour l'empêcher d'interférer avec les séquences d'octets UTF-8 non terminées.

7
mkluwe

Enfin, je le fais fonctionner. Cette réponse combine les commentaires de Miles Budnek, Paul et mkluwe avec des recherches personnelles. Tout d’abord, laissez-moi commencer par code qui fonctionnera sous Windows 10. Après cela, je vais vous expliquer le code et expliquer pourquoi il ne fonctionne pas immédiatement sous 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;
}

Le code commence par définir la page de code, comme suggéré par Miles Budnik . Cela indiquera à la console d'interpréter le flux d'octets reçu comme UTF-8, not comme une variante de la norme ANSI.

Ensuite, il y a un problème dans le code STL fourni avec Visual Studio. std::cout imprime ses données dans un tampon de flux de type std::basic_filebuf. Lorsque ce tampon reçoit une chaîne (via std::basic_streambuf::sputn()), il ne la transmet pas au fichier sous-jacent dans son ensemble. Au lieu de cela, il passera chaque octet séparément. Comme l'explique mkluwe , si la console reçoit une séquence d'octets UTF-8 sous forme d'octets individuels, elle ne les interprétera pas comme un seul point de code. Au lieu de cela, il les traitera comme plusieurs personnages. Chaque octet d'une séquence d'octets UTF-8 est un point de code non valide. Vous verrez donc les à la place. Il y a un rapport de bogue associé à Visual Studio , mais il était fermé en tant que By Design. La solution de contournement consiste à activer la mise en mémoire tampon pour le flux. En prime, cela vous donnera de meilleures performances. Cependant, il se peut que vous deviez maintenant vider le flux régulièrement comme je le fais avec std::endl ou votre sortie risque de ne pas s'afficher.

Enfin, la console Windows prend en charge les polices raster et les polices TrueType. Comme l'a souligné Paul , les polices raster ignoreront simplement la page de code de la console. Les caractères Unicode non-ASCII ne fonctionneront donc que si la console est définie sur une police TrueType. Jusqu'à Windows 7, la valeur par défaut est une police raster. Par conséquent, l'utilisateur devra la modifier manuellement. Heureusement, Windows 10 modifie la police par défaut en Consolas , cette partie du problème devrait donc se résoudre elle-même avec le temps.

10
Daniel Wolf

std::cout fait exactement ce qu'il devrait faire: il envoie votre texte codé UTF-8 à la console, mais celle-ci interprétera ces octets à l'aide de sa page de code actuelle. Vous devez configurer la console de votre programme sur la page de codes 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;
}

Ce serait bien que Windows change la page de codes par défaut en UTF-8, mais cela est probablement impossible en raison de problèmes de compatibilité ascendante.

6
Miles Budnek

Certains caractères Unicode ne peuvent pas être affichés correctement dans une fenêtre de console, même si vous avez modifié la page de code, car votre police ne le prend pas en charge. Par exemple, vous devez installer une police prenant en charge l'arabe si vous souhaitez afficher les caractères arabes.

Cette page de stackoverflow devrait être utile.

À propos, la version Unicode des API de la console (telle que WriteConsoleW) ne viendra pas à la rescousse, car elles appellent en interne leurs API de version de page de code Windows correspondantes (telles que WriteConsoleA). Std :: wcout n’aide pas non plus, car il convertira la chaîne wchar_t en chaîne de caractères en interne.

Il semble que la fenêtre de la console Windows ne prenne pas correctement en charge le format Unicode. Je vous suggère plutôt d'utiliser MessageBox.

0
liuqx

Définissez le codage de sortie de la console sur UTF-8 à l'aide de l'appel API Windows suivant:

SetConsoleOutputCP(65001);

La documentation relative à cette fonction est disponible sur Centre de développement Windows .

0
jfroy