web-dev-qa-db-fra.com

Comment créer un littéral de chaîne UTF-8 dans Visual C++ 2008

Dans VC++ 2003, je pouvais simplement enregistrer le fichier source au format UTF-8 et toutes les chaînes étaient utilisées telles quelles. En d'autres termes, le code suivant imprimerait les chaînes telles quelles sur la console. Si le fichier source a été enregistré au format UTF-8, la sortie sera UTF-8.

printf("Chinese (Traditional)");
printf("中国語 (繁体)");
printf("중국어 (번체)");
printf("Chinês (Tradicional)");

J'ai enregistré le fichier au format UTF-8 avec la nomenclature UTF-8. Cependant, la compilation avec VC2008 a pour résultat:

warning C4566: character represented by universal-character-name '\uC911' 
cannot be represented in the current code page (932)
warning C4566: character represented by universal-character-name '\uAD6D' 
cannot be represented in the current code page (932)
etc.

Les personnages à l'origine de ces avertissements sont corrompus. Ceux qui correspondent aux paramètres régionaux (dans ce cas, 932 = japonais) sont convertis en codage des paramètres régionaux, à savoir Shift-JIS.

Je ne peux pas trouver un moyen d'obtenir VC++ 2008 pour compiler cela pour moi. Notez que les paramètres régionaux que j'utilise dans le fichier source importent peu. Il ne semble pas y avoir de paramètres régionaux disant "Je sais ce que je fais, alors ne f $ $ ## ng changez mes littéraux de chaîne". En particulier, la pseudo-locale UTF-8 inutile ne fonctionne pas.

#pragma setlocale(".65001") 
=> error C2175: '.65001' : invalid locale

"C" non plus:

#pragma setlocale("C") 
=> see warnings above (in particular locale is still 932)

Il semble que VC2008 force tous les caractères dans les paramètres régionaux spécifiés (ou par défaut), et que ces paramètres régionaux ne peuvent pas être UTF-8. Je ne souhaite pas que le fichier utilise des chaînes d'échappement telles que "\ xbf\x11 ..." car la même source est compilée à l'aide de gcc, qui peut très bien gérer les fichiers UTF-8.

Existe-t-il un moyen de spécifier que la compilation du fichier source doit laisser les littéraux de chaîne intacts?

Pour poser la question différemment, quels indicateurs de compilation puis-je utiliser pour spécifier la compatibilité en amont avec VC2003 lors de la compilation du fichier source. c'est-à-dire ne changez pas les littéraux de chaîne, utilisez-les octet pour octet tels qu'ils sont.

Mettre à jour

Merci pour les suggestions, mais je veux éviter wchar. Puisque cette application traite exclusivement des chaînes en UTF-8, utiliser wchar me demanderait alors de reconvertir toutes les chaînes en UTF-8, ce qui devrait être inutile. Tous les traitements d'entrée, de sortie et internes sont en UTF-8. C'est une application simple qui fonctionne bien telle quelle sous Linux et lorsqu'elle est compilée avec VC2003. Je veux pouvoir compiler la même application avec VC2008 et le faire fonctionner. 

Pour que cela se produise, j’ai besoin de VC2008 pour ne pas essayer de le convertir dans les paramètres régionaux de mon ordinateur local (japonais, 932). Je veux que VC2008 soit rétrocompatible avec VC2003. Je veux une locale ou un paramètre de compilateur qui dit que les chaînes sont utilisées telles quelles, essentiellement en tant que tableaux opaques de caractères, ou en tant que UTF-8. Il semble que je sois coincé avec VC2003 et gcc, cependant, VC2008 essaie d’être trop intelligent dans ce cas.

60
brofield

Mettre à jour:

J'ai décidé qu'il n'y avait pas de moyen garanti de le faire. La solution que je présente ci-dessous fonctionne pour la version anglaise VC2003, mais échoue lors de la compilation avec la version japonaise VC2003 (ou peut-être s'agit-il d'un système d'exploitation japonais). En tout cas, on ne peut pas compter sur lui pour travailler. Notez que même tout déclarer en tant que L "" chaînes ne fonctionnait pas (et est douloureux dans gcc comme décrit ci-dessous). 

Au lieu de cela, je pense qu'il suffit de mordre la balle, de déplacer tout le texte dans un fichier de données et de le charger à partir de là. Je suis en train de stocker et d'accéder au texte dans des fichiers INI via SimpleIni (bibliothèque de fichiers INI multiplate-forme). Au moins, il y a une garantie que cela fonctionne car tout le texte est en dehors du programme.

Original:

Je réponds moi-même puisque seul Evan semblait comprendre le problème. Les réponses concernant ce qu'est Unicode et comment utiliser wchar_t ne sont pas pertinentes pour ce problème car il ne s'agit ni d'internationalisation, ni d'un malentendu sur Unicode, les codages de caractères. J'apprécie votre tentative d'aider cependant, excusez-moi si je n'ai pas été assez clair.

Le problème est que j'ai des fichiers source qui doivent être compilés de manière croisée sous diverses plates-formes et compilateurs. Le programme effectue le traitement UTF-8. Il se fiche de tout autre encodage. Je veux avoir des littéraux de chaîne en UTF-8 comme cela fonctionne actuellement avec gcc et vc2003. Comment puis-je le faire avec VC2008? (c’est-à-dire une solution compatible en amont). 

Voici ce que j'ai trouvé:

gcc (v4.3.2 20081105):

  • les littéraux de chaîne sont utilisés tels quels (chaînes brutes)
  • prend en charge les fichiers source codés UTF-8
  • les fichiers source ne doivent pas avoir de nomenclature UTF-8

vc2003:

  • les littéraux de chaîne sont utilisés tels quels (chaînes brutes)
  • prend en charge les fichiers source codés UTF-8
  • les fichiers sources peuvent avoir ou non une nomenclature UTF-8 (peu importe)

vc2005 +:

  • les littéraux de chaîne sont massés par le compilateur (pas de chaînes brutes)
  • les littéraux de chaîne de caractères sont ré-encodés selon les paramètres régionaux spécifiés
  • UTF-8 n'est pas pris en charge en tant que paramètres régionaux cibles.
  • les fichiers source doivent avoir une nomenclature UTF-8

Donc, la réponse simple est que, dans ce but particulier, VC2005 + est cassé et ne fournit pas de chemin de compilation rétrocompatible. Le seul moyen d'obtenir des chaînes Unicode dans le programme compilé est via UTF-8 + BOM + wchar, ce qui signifie que je dois reconvertir toutes les chaînes en UTF-8 au moment de leur utilisation.

Il n'y a pas de méthode multi-plateforme simple pour convertir wchar en UTF-8, par exemple, en quelle taille et en quel encodage le wchar est-il utilisé? Sous Windows, UTF-16. Sur d'autres plateformes? Cela varie. Voir le projet ICU pour plus de détails.

En fin de compte, j'ai décidé d'éviter les coûts de conversion sur tous les compilateurs autres que vc2005 + avec la source suivante. 

#if defined(_MSC_VER) && _MSC_VER > 1310
// Visual C++ 2005 and later require the source files in UTF-8, and all strings 
// to be encoded as wchar_t otherwise the strings will be converted into the 
// local multibyte encoding and cause errors. To use a wchar_t as UTF-8, these 
// strings then need to be convert back to UTF-8. This function is just a rough 
// example of how to do this.
# define utf8(str)  ConvertToUTF8(L##str)
const char * ConvertToUTF8(const wchar_t * pStr) {
    static char szBuf[1024];
    WideCharToMultiByte(CP_UTF8, 0, pStr, -1, szBuf, sizeof(szBuf), NULL, NULL);
    return szBuf;
}
#else
// Visual C++ 2003 and gcc will use the string literals as is, so the files 
// should be saved as UTF-8. gcc requires the files to not have a UTF-8 BOM.
# define utf8(str)  str
#endif

Notez que ce code est juste un exemple simplifié. L'utilisation en production devrait être nettoyée de différentes manières (sécurité des threads, vérification des erreurs, vérification de la taille de la mémoire tampon, etc.).

Ceci est utilisé comme le code suivant. Il compile proprement et fonctionne correctement dans mes tests sur gcc, vc2003 et vc2008:

std::string mText;
mText = utf8("Chinese (Traditional)");
mText = utf8("中国語 (繁体)");
mText = utf8("중국어 (번체)");
mText = utf8("Chinês (Tradicional)");
31
brofield

Brofield,

J'ai eu exactement le même problème et je suis tombé sur une solution qui ne nécessite pas de convertir vos chaînes source en caractères larges et inversement: enregistrez votre fichier source au format UTF-8 without signature et VC2008 le laissera tranquille. A bien fonctionné lorsque j'ai décidé de laisser tomber la signature. Pour résumer:

Unicode (UTF-8 sans signature) - Codepage 65001, ne lance pas l’avertissement c4566 dans VC2008 et ne provoque pas VC de perturber le codage, contrairement à Codepage 65001 (UTF-8 avec signature) lancez c4566 (comme vous l'avez trouvé).

J'espère que ce n'est pas trop tard pour vous aider, mais cela pourrait accélérer votre application VC2008 pour supprimer votre solution de contournement.

16
echo

Bien qu'il soit probablement préférable d'utiliser des chaînes larges et de les convertir au besoin en UTF-8. Je pense que votre meilleur pari est, comme vous l'avez mentionné, d'utiliser des échappements hexagonaux dans les cordes. Comme si vous vouliez un point de code \uC911, vous pouvez simplement le faire.

const char *str = "\xEC\xA4\x91";

Je crois que cela fonctionnera très bien, mais que ce n’est pas très lisible, alors si vous le faites, veuillez le commenter.

16
Evan Teran

Fichier/Options de sauvegarde avancées/Codage: "Unicode (UTF-8 sans signature ) - Codepage 65001"

14
Vladius

Le comportement standard de COMPILER de Visual C++ (2005+) pour les fichiers source est:

  • CP1252 (pour cet exemple, page de code d'Europe occidentale):
    • "Ä"C4 00
    • 'Ä'C4
    • L"Ä"00C4 0000
    • L'Ä'00C4
  • UTF-8 sans nomenclature:
    • "Ä"C3 84 00 (= UTF-8)
    • 'Ä' → warning: constante multi-caractères
    • "Ω"E2 84 A6 00 (= UTF-8, comme prévu)
    • L"A"00C3 0084 0000 (faux!)
    • L'Ä' → warning: constante multi-caractères
    • L"Ω"00E2 0084 00A6 0000 (faux!)
  • UTF-8 avec nomenclature:
    • "Ä"C4 00 (= CP1252, pas plus UTF-8),
    • 'Ä'C4
    • "Ω" → erreur: impossible de convertir en CP1252!
    • L"Ä"00C4 0000 (correct)
    • L'Ä'00C4
    • L"Ω"2126 0000 (correct)

Vous voyez, le compilateur C gère les fichiers UTF-8 sans nomenclature de la même manière que CP1252. En conséquence, il est impossible pour le compilateur de mélanger des chaînes UTF-8 et UTF-16 dans la sortie compilée! Vous devez donc choisir un fichier de code source:

  • soit utilise UTF-8 avec BOM et génère uniquement des chaînes UTF-16 (c'est-à-dire, utilisez toujours le préfixe L),
  • ou UTF-8 sans nomenclature et génère uniquement des chaînes UTF-8 (c'est-à-dire, n'utilisez jamais le préfixe L).
  • Les caractères ASCII 7 bits ne sont pas impliqués et peuvent être utilisés avec ou sans le préfixe L

Indépendamment, l'EDITEUR peut détecter automatiquement les fichiers UTF-8 sans nomenclature comme fichiers UTF-8.

8
Henrik Haftmann

D'un commentaire à ce très joli blog
"Utilisation de UTF-8 comme représentation interne des chaînes en C et C++ avec Visual Studio"
=> http://www.nubaria.com/fr/blog/?p=289

#pragma execution_character_set("utf-8") 

Il nécessite Visual Studio 2008 SP1 et le correctif suivant:

http://support.Microsoft.com/kb/980263 ....

6
Alexander Jung

Que dis-tu de ça? Vous stockez les chaînes dans un fichier codé UTF-8, puis les pré-traitez dans un fichier source C++ codé ASCII. Vous conservez le codage UTF-8 à l'intérieur de la chaîne en utilisant des échappements hexadécimaux. La ficelle

"中国語 (繁体)"

est converti en

"\xE4\xB8\xAD\xE5\x9B\xBD\xE8\xAA\x9E (\xE7\xB9\x81\xE4\xBD\x93)"

Bien sûr, ceci est illisible pour tout être humain, et le but est simplement d'éviter des problèmes avec le compilateur.

Vous pouvez utiliser le préprocesseur C++ pour référencer les chaînes dans le fichier d'en-tête converti ou convertir votre source UTF-8 entière en ASCII avant la compilation à l'aide de cette astuce.

4
Martin Liversage

Une conversion portable à partir de tout encodage natif que vous avez est simple en utilisant char_traits :: widen (). 

#include <locale>
#include <string>
#include <vector>

/////////////////////////////////////////////////////////
// NativeToUtf16 - Convert a string from the native 
//                 encoding to Unicode UTF-16
// Parameters:
//   sNative (in): Input String
// Returns:        Converted string
/////////////////////////////////////////////////////////
std::wstring NativeToUtf16(const std::string &sNative)
{
  std::locale locNative;

  // The UTF-16 will never be longer than the input string
  std::vector<wchar_t> vUtf16(1+sNative.length());

  // convert
  std::use_facet< std::ctype<wchar_t> >(locNative).widen(
        sNative.c_str(), 
        sNative.c_str()+sNative.length(), 
        &vUtf16[0]);

  return std::wstring(vUtf16.begin(), vUtf16.end());
}

En théorie, le trajet aller-retour entre UTF-16 et UTF-8 devrait être aussi simple, mais j’ai constaté que les paramètres régionaux UTF-8 ne fonctionnaient pas correctement sur mon système (VC10 Express sur Win7).

J'ai donc écrit un convertisseur simple basé sur RFC 3629.

/////////////////////////////////////////////////////////
// Utf16ToUtf8 -   Convert a character from UTF-16 
//                 encoding to UTF-8.
//                 NB: Does not handle Surrogate pairs.
//                     Does not test for badly formed 
//                     UTF-16
// Parameters:
//   chUtf16 (in): Input char
// Returns:        UTF-8 version as a string
/////////////////////////////////////////////////////////
std::string Utf16ToUtf8(wchar_t chUtf16)
{
    // From RFC 3629
    // 0000 0000-0000 007F   0xxxxxxx
    // 0000 0080-0000 07FF   110xxxxx 10xxxxxx
    // 0000 0800-0000 FFFF   1110xxxx 10xxxxxx 10xxxxxx

    // max output length is 3 bytes (plus one for Nul)
    unsigned char szUtf8[4] = "";

    if (chUtf16 < 0x80)
    {
        szUtf8[0] = static_cast<unsigned char>(chUtf16);
    }
    else if (chUtf16 < 0x7FF)
    {
        szUtf8[0] = static_cast<unsigned char>(0xC0 | ((chUtf16>>6)&0x1F));
        szUtf8[1] = static_cast<unsigned char>(0x80 | (chUtf16&0x3F));
    }
    else
    {
        szUtf8[0] = static_cast<unsigned char>(0xE0 | ((chUtf16>>12)&0xF));
        szUtf8[1] = static_cast<unsigned char>(0x80 | ((chUtf16>>6)&0x3F));
        szUtf8[2] = static_cast<unsigned char>(0x80 | (chUtf16&0x3F));
    }

    return reinterpret_cast<char *>(szUtf8);
}


/////////////////////////////////////////////////////////
// Utf16ToUtf8 -   Convert a string from UTF-16 encoding
//                 to UTF-8
// Parameters:
//   sNative (in): Input String
// Returns:        Converted string
/////////////////////////////////////////////////////////
std::string Utf16ToUtf8(const std::wstring &sUtf16)
{
    std::string sUtf8;
    std::wstring::const_iterator itr;

    for (itr=sUtf16.begin(); itr!=sUtf16.end(); ++itr)
        sUtf8 += Utf16ToUtf8(*itr);
    return sUtf8;
}

Je pense que cela devrait fonctionner sur n’importe quelle plate-forme, mais je n’ai pas pu le tester, sauf sur mon propre système. Il peut donc y avoir des bogues.

#include <iostream>
#include <fstream>

int main()
{
    const char szTest[] = "Das tausendschöne Jungfräulein,\n"
                          "Das tausendschöne Herzelein,\n"
                          "Wollte Gott, wollte Gott,\n"
                          "ich wär' heute bei ihr!\n";

    std::wstring sUtf16 = NativeToUtf16(szTest);
    std::string  sUtf8  = Utf16ToUtf8(sUtf16);

    std::ofstream ofs("test.txt");
    if (ofs)
        ofs << sUtf8;
    return 0;
}
3
Michael J

Peut-être essayer une expérience:

#pragma setlocale(".UTF-8")

ou:

#pragma setlocale("english_england.UTF-8")
1
Windows programmer

Je sais que je suis en retard pour la fête, mais je pense que je dois étaler ceci . Pour Visual C++ 2005 et versions ultérieures, si le fichier source ne contient pas BOM (marque d'ordre des octets) et que les paramètres régionaux de votre système ne sont pas anglais, VC supposera que votre fichier source n'est pas en Unicode.

Pour que vos fichiers source UTF-8 soient correctement compilés, vous devez enregistrer en UTF-8 sans codage BOM, et les paramètres régionaux du système (langue autre que Unicode) doivent être en anglais.

 enter image description here

1
raymai97

J'avais un problème similaire. Mes littéraux de chaîne UTF-8 ont été convertis en page de code système actuelle lors de la compilation - je viens d'ouvrir des fichiers .obj dans une visionneuse hexagonale et ils étaient déjà mutilés. Par exemple, le caractère ć n'était qu'un octet.

La solution pour moi était d'économiser en UTF-8 et SANS BOM. C'est comme ça que j'ai trompé le compilateur. Il pense maintenant qu'il ne s'agit que d'une source normale et ne traduit pas les chaînes. Dans les fichiers .obj ć est maintenant deux octets.

Ne tenez pas compte des commentateurs, s'il vous plaît. Je comprends ce que vous voulez - je veux aussi la même chose: source UTF-8, fichiers générés UTF-8, fichiers d'entrée UTF-8, UTF-8 sur des lignes de communication sans jamais être traduits.

Peut-être que ça aide ...

1
Daniel N.

J'ai eu un problème similaire lors de la compilation de littéraux de chaîne UTF-8 étroits (char) et ce que j'ai découvert, c'est que je devais avoir à la fois une nomenclature UTF-8 et #pragma execution_character_set("utf-8") [1], ou ni la nomenclature ni le pragma [2]. L'utilisation de l'une sans l'autre entraînait une conversion incorrecte.

J'ai documenté les détails sur https://github.com/jay/compiler_string_test

[1]: Visual Studio 2012 ne prend pas en charge execution_character_set. Visual Studio 2010 et 2015, cela fonctionne bien, et comme vous le savez avec le correctif de 2008, cela fonctionne bien.

[2]: Certains commentaires dans ce fil ont noté que l'utilisation de la nomenclature ni du pragma peut entraîner une conversion incorrecte pour les développeurs utilisant une page de codes locale à plusieurs octets (par exemple, au Japon).

0
Jay

Fichiers source UTF-8 

  • Without BOM : sont traités comme des fichiers bruts, sauf si votre système utilise une page de codes> 1 octet/caractères (comme Shift JIS). Vous devez modifier la page de codes du système en un seul octet, puis vous devriez pouvoir utiliser les caractères Unicode dans les littéraux et compiler sans problèmes (du moins, j'espère).
  • Avec BOM : ont-ils converti les littéraux char et string en page de code système lors de la compilation. Vous pouvez vérifier la page de codes du système actuel avec GetACP (). Si je comprends bien, il n’existe aucun moyen de régler la page de codes du système sur 65001 (UTF-8). Il n’ya donc aucun moyen d’utiliser UTF-8 directement avec BOM.

La seule façon portable et indépendante du compilateur est d'utiliser ASCII le jeu de caractères et les séquences d'échappement, car rien ne garantit qu'un compilateur accepterait un fichier codé UTF-8.

0
user206334

Donc, les choses doivent changer… .. Maintenant, j'ai une solution.

Tout d’abord, vous devriez exécuter sous la page de codes à octet unique locale, telle que l’anglais, pour que cl.exe ne puisse pas obtenir les codes devenir chaos.

Deuxièmement, sauvegardez le code source dans UTF8-NO BOM, notez NO-BOM, puis compilez-le avec Cl.exe, n’appelez aucune API C, telle que printf wprint, tous les employés ne travaillant pas, Je ne sais pas pourquoi:) .... peut avoir une étude plus tard ...

Ensuite, il suffit de compiler et d’exécuter, vous verrez le résultat ..... Mon email est luoyonggang, (Google) espère quelque chose ......

wscript:

#! /usr/bin/env python
# encoding: utf-8
# Yonggang Luo

# the following two variables are used by the target "waf dist"
VERSION='0.0.1'
APPNAME='cc_test'

top = '.'

import waflib.Configure

def options(opt):
    opt.load('compiler_c')

def configure(conf):
    conf.load('compiler_c')
    conf.check_lib_msvc('gdi32')
    conf.check_libs_msvc('kernel32 user32')

def build(bld):
    bld.program(
        features = 'c',
        source   = 'chinese-utf8-no-bom.c',
        includes = '. ..',
        cflags   = ['/wd4819'],
        target   = 'myprogram',
        use      = 'KERNEL32 USER32 GDI32')

Exécution du script run.bat

rd /s /q build
waf configure build --msvc_version "msvc 6.0"
build\myprogram

rd /s /q build
waf configure build --msvc_version "msvc 9.0"
build\myprogram

rd /s /q build
waf configure build --msvc_version "msvc 10.0"
build\myprogram

Code source main.c:

//encoding : utf8 no-bom
#include <stdio.h>
#include <string.h>

#include <Windows.h>

char* ConvertFromUtf16ToUtf8(const wchar_t *wstr)
{
    int requiredSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, 0, 0, 0, 0);
    if(requiredSize > 0)
    {
        char *buffer = malloc(requiredSize + 1);
        buffer[requiredSize] = 0;
        WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buffer, requiredSize, 0, 0);
        return buffer;
    }
    return NULL;
}

wchar_t* ConvertFromUtf8ToUtf16(const char *cstr)
{
    int requiredSize = MultiByteToWideChar(CP_UTF8, 0, cstr, -1, 0, 0);
    if(requiredSize > 0)
    {
        wchar_t *buffer = malloc( (requiredSize + 1) * sizeof(wchar_t) );
        printf("converted size is %d 0x%x\n", requiredSize, buffer);
        buffer[requiredSize] = 0;
        MultiByteToWideChar(CP_UTF8, 0, cstr, -1, buffer, requiredSize);
        printf("Finished\n");
        return buffer;
    }
    printf("Convert failed\n");
    return NULL;
}

void ShowUtf8LiteralString(char const *name, char const *str)
{
    int i = 0;
    wchar_t *name_w = ConvertFromUtf8ToUtf16(name);
    wchar_t *str_w = ConvertFromUtf8ToUtf16(str);

    printf("UTF8 sequence\n");
    for (i = 0; i < strlen(str); ++i)
    {
        printf("%02x ", (unsigned char)str[i]);
    }

    printf("\nUTF16 sequence\n");
    for (i = 0; i < wcslen(str_w); ++i)
    {
        printf("%04x ", str_w[i]);
    }

    //Why not using printf or wprintf? Just because they do not working:)
    MessageBoxW(NULL, str_w, name_w, MB_OK);
    free(name_w);
    free(str_w);

}

int main()
{
    ShowUtf8LiteralString("English english_c", "Chinese (Traditional)");
    ShowUtf8LiteralString("简体 s_chinese_c", "你好世界");
    ShowUtf8LiteralString("繁体 t_chinese_c", "中国語 (繁体)");
    ShowUtf8LiteralString("Korea korea_c", "중국어 (번체)");
    ShowUtf8LiteralString("What? what_c", "Chinês (Tradicional)");
}
0
lygstate

J'ai eu un problème similaire, la solution était de sauvegarder en UTF8 sans utiliser les options de sauvegarde avancées

0
Dennis