C++, Windows et l'UTF-8

C'est quelque chose…

Aujourd’hui, en pleine rédaction de la suite du tutoriel C++, et notamment du chapitre sur les fichiers, je me suis intéressé à l’encodage des chaînes de caractères et la manipulation d’UTF-8 en C++ moderne. Je précise que je suis sous Windows, parce que GNU/Linux a le bon goût de marcher correctement.

D’abord s’est posée la question de savoir comment lire et écrire des caractères français, russes ou autres dans un fichier. Ça, c’est simple, il suffit d’utiliser std::string et de préfixer les chaînes de u8. Exemple.

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::vector<std::string> const phrases
    {
        u8"Voici un mot important.\n",
        u8"Russe : резиновые перчатки",
        u8"Polonais : gumowe rękawiczki",
        u8"Roumain : mănusi de cauciuc"
    };

    std::ofstream fichier { "sortie.txt" };
    for (auto const & phrase : phrases)
    {
        fichier << phrase << std::endl;
    }

    return 0;
}

Puis je me suis demandé comment afficher dans la console du texte non-ASCII. Je précise que ma police de caractère est Lucida Console, donc compatible. Essayons donc.

#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::vector<std::string> phrases
    {
        u8"Voici un mot important.\n",
        u8"Russe : резиновые перчатки",
        u8"Polonais : gumowe rękawiczki",
        u8"Roumain : mănusi de cauciuc",
        u8"Japonais : ゴム手袋"
    };

    SetConsoleOutputCP(CP_UTF8);
    std::cout << phrases[1] << std::endl;

    return 0;
}
Russe : резиновые перчатки

Cool, c’est plutôt bon signe. Essayons le japonais avec une police NSimSun, parce que Lucida Console ne sait pas afficher le japonais.

Japonais : ゴム手袋

Ah, donc l’affichage, on dirait bien que ça marche. Et si on essaye l’inverse ? Essayons d’entrer du texte UTF-8. Commençons simple, commençons par notre langue à nous.

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

int main()
{
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);

    std::string text;
    std::cin >> text;
    std::cout << "Donc on a : " << text << std::endl;

    return 0;
}
é
Donc on a :

Arf, comment ça rien ? Non mais oh ! Et en cherchant sur StackOverflow, on tombe sur ça.

Be aware that there are many bugs with UTF-8 as codepage. Most are WONTFIX.

Deduplicator

Et en creusant un peu (désolé je ne retrouve plus le lien), certains disent que, même avec Windows 10, cette fonction se contente de planter silencieusement quand on lui passe le code page correspondant à l’UTF-8 et qu’on lui donne des caractères non-ASCII.

À noter que si j’utilise le code page 866 pour le cyrillique, il n’y a aucun problème à taper des lettres russes, tout comme 850 marche pour nos lettres françaises. Voici la liste des codes pages.

Tout ça pour dire que C++ et Windows, c’est parfois compliqué. Et sans verser dans le troll, bein Linux des fois c’est clairement supérieur.

PS : avant de se quitter, jetez un oeil au logo de ce billet et dites moi en toute honnêteté si vous préférer celui-là ou bien celui du tutoriel C++ (ci-dessous). J’ai simplement fait un logo au couleur de notre cher site. :)

Logo actuel du tutoriel C++.
Logo actuel du tutoriel C++.


15 commentaires

Salut,

Mmm… Je ne me rappel pas d’avoir rencontré un tel bug quand je faisais des tests pour le tuto C… Je n’ai pas de Windows sous la main, mais est-ce que tu saurais me dire si le code C suivant te donne le même problème ?

#include <stdio.h>


int
main(void)
{
    char buf[255];

    if (fgets(buf, sizeof buf, stdin) != NULL)
        fputs(buf, stdout);

    return 0;
}

Donc en lui fournissant des caractères français depuis une console utilisant la page de code UTF-8. Par ailleurs, juste par curiosité, ton code fonctionne-t-il différemment si tu supprimes les appels aux fonctions SetConsoleOutputCP() et SetConsoleCP() en gérant la page de code depuis le terminal ?

PS : Je vote pour le nouveau logo. ^^

+0 -0

Je crois que sous windows, le mieux, c’est de passer directement à UTF-16, l’encodage natif du système. ON évite plein de problèmes.

On l’oublie facilement, mais dans std il existe wstring, wcout/wcerr/wcin, wifstream/wofstream, wistringstream/wostringstream. ON n’a pas d’excuse en fait.

De mon côté, c’est avec les noms de fichiers que j’ai souvent eu des problèmes. J’ai régulièrement eu des utilisateurs se plaindre qu’ils ne pouvaient pas ouvrir ou enregistrer des fichiers avec des noms arabes, russes, etc. Le gros piège dont il faut se souvenir c’est que, quand bien même on utilise std::wifstream/wofstream, le contenu n’est pas lu/enregistré en UTF-16 mais bien dans l’encodage local, p.ex. CP1252. Pour lire et enregistrer réellement en UTF-16 ou en UTF-8 dans un fichier, je ne sais pas s’il y a une autre solution qu’avoir recours à MultiByteToWideString/WideStringToMultiByte (cf. MSDN) puis read/write en binaire.

Du coup dans pas mal de mes projets j’ai ce genre de code pour augmenter les chances que ça passe sous linux:

#ifdef UNICODE
#include<cwchar>
// Celui-ci est défini dans windows.h, mais si vous ne l'incluez pas c'est bon à savoir
#define TEXT(X) L##X

typedef std::wstring tstring;
typedef std::wostringstream tostringstream;
// etc.

// Dommage qu'on n'a pas un équivalent à typedef... si quelqu'un a mieux que des macros, je suis preneur
#define tstrlen wcslen
#define tstrcmp wcscmp
#define tstrtol wcstol
//etc..

#else
#include<cstring>
#deifne TEXT(X) X
typedef std::string tstring;
typedef tostringstream std::ostringstream;
#define tstrcmp strcmp
#define tstrlen strlen
#define tstrtol strtol
//etc.
#endif

LE bonus chiant en plus avec les chaînes en UTF_16, c’est qu’il faut souvent préciser explicitement l’encodage du code, sous peine de devoir se battre avec les \xNNNN et leur lisibilité et surtout leur interprétation foireuse (`L"Pouvez-vous pr\xE9dire ce que contienra r\xE9ellement cette cha\xEEne ?"`) D’un certain côté c’est bien, ça pousse très vite à externaliser les chaînes hors du code source, ce qui favorise la traduction de son programme.

+0 -0

Une deuxième version du logo, avec les + plus grands. Qu’en dites-vous, @germinolegrand, @Taurre et @Blackline ?

informaticienzero

Tu devrais la faire en SVG, sinon, cela m’a l’air pas mal, si ce n’est que comme ça on dirait que le « ++ » est mal détouré, mais c’est peut-être moi.

J’ai testé le code C et effectivement, ça marche impec' en français. Et ce code C++ aussi. Mais seulement en français, pas en russe ni en roumain.

informaticienzero

Arf ! Il semble que je sois passé à côté de ça durant mes tests pour le tuto C, j’au dû me focaliser sur l’affichage et non sur les entrées. >_<
Après recherches je tombe sur la même solution que celle proposée par @QuentinC, à savoir MultiByteToWideString() et WideStringToMultiByte()… La seule autre option que je vois c’est de se passer de la ligne de commande Windows et d’utiliser MSys2 en lieu et place. A priori tu n’auras pas de problèmes avec (en tout les cas pour lire et écrire depuis et vers le terminal).

+0 -0

Je n’ai rien à apporter sur les histoires de C et de C++ par contre j’ai l’avis contraire de @Blackline sur le logo.
Je ne sais pas trop comment le dire sans que ça sonne trop mal mais je trouve qu’un logo au couleur du site fait "image par défaut" et du coup un peu "cheap". Au premier coup d’œil je n’avais pas vu que ce n’était pas le cas.

V1>V3>V2\mathrm{V_1>V_3>V_2}

@Backmachine Quand j’ai un logiciel sous la main, je clique sur des icone +/- proche des couleurs de l’interface. Ce genre de thématique créer un univers assez "identifiable" et cela permet d’avoir une identité.

Prendre un vieux jpg de google image, ça, c’est cheap à mon sens. Puis si on a le choix de faire notre propre logo je trouve ça plus sain d’utiliser les couleurs du site plutôt que du noir sur blanc. Avec peu de travail on obtient un résultat plus original !

+0 -0

La nouvelle forme me paraît plus sympa, mais niveau couleurs, pour l’identité visuelle ZdS, il en manque une qui est essentielle : le blanc, qui a pour effet de rendre le logo moins plat.

Que pensez-vous de ceci :) :

logo C++
logo C++

On peut également envisager de faire le C plus grand et de mettre un + intérieur et l’autre extérieur.

+5 -0

 : en bref, c’est vraiment la galère avec C++ et Windows.

mais non. C’est comme tout, il faut juste savoir s’y prendre. ET pour savoir comment s’y prendre il faut faire des recherches et observer comment les autres font.

Moi j’ai le problème inverse, je code essentiellement pour windows, et donc je galère les rares fois où je dois faire marcher du code sous linux.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte