it-swarm.dev

Como criar um literal de seqüência de caracteres UTF-8 no Visual C++ 2008

Em VC++ 2003, eu poderia salvar o arquivo de origem como UTF-8 e todas as seqüências foram usadas como estão. Em outras palavras, o código a seguir imprimiria as seqüências de caracteres como está no console. Se o arquivo de origem foi salvo como UTF-8, a saída será UTF-8.

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

Salvei o arquivo no formato UTF-8 com a lista de materiais UTF-8. No entanto, compilar com o VC2008 resulta em:

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.

Os caracteres que causam esses avisos estão corrompidos. Os que se encaixam na localidade (neste caso, 932 = japonês) são convertidos para a codificação de localidade, ou seja, Shift-JIS.

Não consigo encontrar uma maneira de obter o VC++ 2008 para compilar isso para mim. Observe que não importa qual localidade eu uso no arquivo de origem. Não parece haver uma localidade que diz "Eu sei o que estou fazendo, portanto, não altere meus literais de string". Em particular, o inútil pseudo-localidade UTF-8 não funciona.

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

Nem o "C":

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

Parece que o VC2008 força todos os caracteres na localidade especificada (ou padrão), e essa localidade não pode ser UTF-8. Eu não quero alterar o arquivo para usar seqüências de caracteres de escape como "\ xbf\x11 ..." porque a mesma fonte é compilada usando o gcc que pode muito bem lidar com arquivos UTF-8.

Existe alguma maneira de especificar que a compilação do arquivo de origem deve deixar literais string intocados?

Para perguntar de forma diferente, quais sinalizadores de compilação posso usar para especificar compatibilidade com versões anteriores do VC2003 ao compilar o arquivo de origem. ou seja, não altere os literais de string, use-os como bytes para byte.

Atualizar

Obrigado pelas sugestões, mas eu quero evitar wchar. Como este aplicativo lida com strings exclusivamente em UTF-8, o uso de wchar exigiria que eu convertesse todas as strings de volta em UTF-8, o que seria desnecessário. Toda entrada, saída e processamento interno estão em UTF-8. É um aplicativo simples que funciona bem como no Linux e quando compilado com o VC2003. Eu quero ser capaz de compilar o mesmo aplicativo com o VC2008 e fazê-lo funcionar. 

Para que isso aconteça, eu preciso do VC2008 para não tentar convertê-lo para o código de idioma da minha máquina local (japonês, 932). Eu quero que o VC2008 seja retrocompatível com o VC2003. Eu quero uma configuração de local ou compilador que diz que seqüências de caracteres são usadas como está, essencialmente como matrizes opacas de char ou como UTF-8. Parece que eu posso estar preso com VC2003 e gcc, VC2008 está tentando ser muito inteligente neste caso.

60
brofield

Atualizar:

Eu decidi que não há maneira garantida de fazer isso. A solução que apresento abaixo funciona para a versão em inglês VC2003, mas falha ao compilar com a versão em japonês VC2003 (ou talvez seja o sistema operacional japonês). Em qualquer caso, não pode depender do trabalho. Note que mesmo declarar tudo como "" strings não funcionou (e é doloroso no gcc como descrito abaixo). 

Em vez disso, acredito que você só precisa morder a bala e mover todo o texto para um arquivo de dados e carregá-lo de lá. Agora estou armazenando e acessando o texto em arquivos INI via SimpleIni (biblioteca de arquivos INI multiplataforma). Pelo menos há uma garantia de que funciona como todo o texto está fora do programa.

Original:

Eu estou respondendo a mim mesmo desde que apenas Evan apareceu para entender o problema. As respostas sobre o que é Unicode e como usar wchar_t não são relevantes para este problema, pois não se trata de internacionalização, nem de um mal-entendido de Unicode, codificações de caracteres. Eu aprecio sua tentativa de ajudar, desculpas se eu não estivesse claro o suficiente.

O problema é que eu tenho arquivos fonte que precisam ser compilados em uma variedade de plataformas e compiladores. O programa faz o processamento UTF-8. Não se importa com outras codificações. Eu quero ter literais de string em UTF-8 como atualmente trabalha com gcc e vc2003. Como faço isso com o VC2008? (isto é, solução compatível com versões anteriores). 

Isto é o que eu encontrei:

gcc (v4.3.2 20081105):

  • literais de string são usados ​​como são (strings raw)
  • suporta arquivos de origem codificados em UTF-8
  • arquivos de origem não devem ter uma lista técnica UTF-8

vc2003:

  • literais de string são usados ​​como são (strings raw)
  • suporta arquivos de origem codificados em UTF-8
  • arquivos de origem podem ou não ter uma lista de materiais UTF-8 (não importa)

vc2005 +:

  • literais de string são massageados pelo compilador (sem strings raw)
  • literais de cadeia de caractere são recodificados em uma localidade especificada
  • UTF-8 não é suportado como localidade de destino
  • arquivos de origem devem ter uma BOM UTF-8

Portanto, a resposta simples é que, para essa finalidade específica, o VC2005 + está quebrado e não fornece um caminho de compilação compatível com versões anteriores. A única maneira de obter strings Unicode no programa compilado é via UTF-8 + BOM + wchar, o que significa que eu preciso converter todas as strings de volta para UTF-8 no momento do uso.

Não existe um método simples de multiplataforma para converter wchar em UTF-8, por exemplo, em que tamanho e codificação o wchar está? No Windows, UTF-16. Em outras plataformas? Varia. Veja o projeto ICU para alguns detalhes.

No final, decidi que evitarei o custo de conversão em todos os compiladores que não sejam o vc2005 + com uma fonte como a seguinte. 

#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

Note que este código é apenas um exemplo simplificado. O uso da produção precisaria ser limpo de várias maneiras (segurança de thread, verificação de erros, verificações de tamanho de buffer, etc).

Isso é usado como o código a seguir. Compila de forma limpa e funciona corretamente nos meus testes no gcc, vc2003 e vc2008:

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

Brofield,

Eu tive exatamente o mesmo problema e apenas tropecei em uma solução que não requer a conversão de suas strings fonte para caracteres largos e vice-versa: salve seu arquivo fonte como UTF-8 sem signature e o VC2008 o deixará sozinho. Funcionou muito bem quando resolvi abandonar a assinatura. Resumindo:

Unicode (UTF-8 sem assinatura) - Página de códigos 65001, não lança o aviso c4566 no VC2008 e não causa VC para mexer com a codificação, enquanto a página de código 65001 (UTF-8 com assinatura) executa c4566 (como você encontrou).

Espero que não seja tarde demais para ajudá-lo, mas pode acelerar o seu aplicativo VC2008 para remover sua solução alternativa.

16
echo

Embora seja provavelmente melhor usar seqüências de caracteres largas e, em seguida, converter conforme necessário para UTF-8. Eu acho que sua melhor aposta é como você mencionou usar hexágonos escapes nas cordas. Como se você quisesse o ponto de código \uC911, você poderia fazer isso.

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

Eu acredito que isso funcionará muito bem, mas não é muito legível, então se você fizer isso, por favor, comente-o para explicar.

16
Evan Teran

Arquivo/Opções avançadas de gravação/codificação: "Unicode (UTF-8 sem assinatura ) - página de código 65001"

14
Vladius

Visual C++ (2005+) COMPILER comportamento padrão para arquivos de origem é:

  • CP1252 (para este exemplo, página de códigos da Europa Ocidental):
    • "Ä"C4 00
    • 'Ä'C4
    • L"Ä"00C4 0000
    • L'Ä'00C4
  • UTF-8 sem BOM:
    • "Ä"C3 84 00 (= UTF-8)
    • 'Ä' → warning: constante de múltiplos caracteres
    • "Ω"E2 84 A6 00 (= UTF-8, como esperado)
    • L"A"00C3 0084 0000 (errado!)
    • L'Ä' → warning: constante de múltiplos caracteres
    • L"Ω"00E2 0084 00A6 0000 (errado!)
  • UTF-8 com BOM:
    • "Ä"C4 00 (= CP1252, não mais UTF-8),
    • 'Ä'C4
    • "Ω" → erro: não é possível converter para CP1252!
    • L"Ä"00C4 0000 (correto)
    • L'Ä'00C4
    • L"Ω"2126 0000 (correto)

Você vê, o compilador C lida com arquivos UTF-8 sem o BOM da mesma forma que o CP1252. Como resultado, é impossível para o compilador misturar strings UTF-8 e UTF-16 na saída compilada! Então você tem que decidir por um arquivo de código-fonte:

  • ou use UTF-8 com BOM e gere somente strings UTF-16 (isto é, use sempre o prefixo L),
  • ou UTF-8 sem BOM e gerar strings UTF-8 apenas (ou seja, nunca use o prefixo L).
  • Caracteres de 7 bits ASCII não estão envolvidos e podem ser usados ​​com ou sem prefixo L

Independentemente, o EDITOR pode detectar automaticamente arquivos UTF-8 sem BOM como arquivos UTF-8.

8
Henrik Haftmann

De um comentário para este blog muito legal
"Usando UTF-8 como representação interna para strings em C e C++ com Visual Studio"
=> http://www.nubaria.com/en/blog/?p=289

#pragma execution_character_set("utf-8") 

Requer o Visual Studio 2008 SP1 e o seguinte hotfix:

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

6
Alexander Jung

Que tal agora? Você armazena as seqüências de caracteres em um arquivo codificado em UTF-8 e, em seguida, pré-processa-as em um arquivo de origem C++ codificado ASCII. Você mantém a codificação UTF-8 dentro da string usando escapes hexadecimais. A corda

"中国語 (繁体)"

é convertido para

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

Claro que isso é ilegível para qualquer ser humano, e o objetivo é apenas evitar problemas com o compilador.

Você poderia usar o pré-processador C++ para referenciar as strings no arquivo de cabeçalho convertido ou você poderia converter toda a fonte UTF-8 em ASCII antes da compilação usando este truque.

4
Martin Liversage

Uma conversão portátil de qualquer codificação nativa que você tenha é simples usando 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());
}

Em teoria, a viagem de retorno, de UTF-16 a UTF-8, deve ser igualmente fácil, mas descobri que as localidades UTF-8 não funcionam corretamente em meu sistema (VC10 Express no Win7).

Assim eu escrevi um conversor simples baseado no 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;
}

Acredito que isso deve funcionar em qualquer plataforma, mas não consegui testá-lo, exceto em meu próprio sistema, portanto, pode haver bugs.

#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

Talvez tente uma experiência:

#pragma setlocale(".UTF-8")

ou:

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

Eu sei que estou atrasado para a festa, mas acho que preciso espalhe isso . Para o Visual C++ 2005 e superior, se o arquivo de origem não contiver BOM (marca de ordem de byte) e sua localidade de sistema não for o inglês, VC assumirá que o arquivo de origem não está em Unicode.

Para obter seus arquivos fonte UTF-8 compilados corretamente, você deve salvar em UTF-8 sem codificação BOM, e a localidade do sistema (idioma não-Unicode) deve ser o inglês.

enter image description here

1
raymai97

Eu tive um problema parecido. Meus literais de string UTF-8 foram convertidos para a página de códigos do sistema atual durante a compilação - acabei de abrir arquivos .obj em um visualizador hexadecimal e eles já estavam mutilados. Por exemplo, o caractere ć era apenas um byte.

A solução para mim foi salvar em UTF-8 e sem BOM. Foi assim que enganei o compilador. Agora ele acha que é apenas uma fonte normal e não traduz seqüências de caracteres. Nos arquivos. Obj ć é agora dois bytes.

Desconsidere alguns comentaristas, por favor. Eu entendo o que você quer - eu quero o mesmo também: fonte UTF-8, arquivos gerados UTF-8, arquivos de entrada UTF-8, UTF-8 sobre as linhas de comunicação, sem nunca traduzir.

Talvez isso ajude ...

1
Daniel N.

Eu tive um problema semelhante compilando literais string strings (char) UTF-8 e o que eu descobri é basicamente que eu tinha que ter uma BOM UTF-8 e #pragma execution_character_set("utf-8") [1], ou nem a BOM nem o pragma [2]. Usando um sem o outro resultou em uma conversão incorreta.

Eu documentei os detalhes em https://github.com/jay/compiler_string_test

[1]: o Visual Studio 2012 não suporta execution_character_set. Visual Studio 2010 e 2015 funciona bem, e como você sabe com o patch em 2008, funciona bem.

[2]: Alguns comentários neste tópico observaram que não usar nem o BOM nem o pragma pode resultar em uma conversão incorreta para desenvolvedores usando uma página de códigos local que é multibyte (por exemplo, Japão).

0
Jay

Arquivos de origem UTF-8 

  • Sem BOM : são tratados como brutos, exceto se seu sistema estiver usando uma página de códigos de 1 byte/char (como Shift JIS). Você precisa alterar a página de código do sistema para qualquer byte único e, em seguida, você deve ser capaz de usar caracteres Unicode dentro de literais e compilar sem problemas (pelo menos eu espero).
  • Com BOM : eles têm caracteres e literais de string convertidos para a página de códigos do sistema durante a compilação. Você pode verificar a página de código atual do sistema com GetACP (). AFAIK, não há como definir a página de código do sistema para 65001 (UTF-8), portanto, não há como usar o UTF-8 diretamente com a BOM.

A única forma independente e independente do compilador é usar charset ASCII e seqüências de escape, porque não há garantias de que qualquer compilador aceitaria o arquivo codificado em UTF-8.

0
user206334

Então, coisas a serem mudadas Agora eu tenho uma solução.

Primeiro de tudo, você deve rodar sob a Página de Código de Byte Único Local, como o Inglês, para que o cl.exe não consiga que os códigos cheguem ao caos.

Em segundo lugar, salve o código-fonte em UTF8-NO BOM, observe, NO-BOM e compile com Cl.exe, NÃO chamando nenhuma API C, como printf wprint, todos esses funcionários não trabalhando, não sei porque:) .... pode ter um estudo mais tarde ...

Então apenas compilar e correr, você verá o resultado ..... Meu e-mail é luoyonggang, (Google) espero por alguns ......

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')

Executando o 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

Código fonte 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

Eu tive um problema parecido, a solução foi salvar em UTF8 sem usar opções de salvamento avançadas

0
Dennis