Escrevendo utf16 para arquivar em modo binário

Eu estou tentando escrever um wstring para arquivo com ofstream no modo binário, mas acho que estou fazendo algo errado. Isso é o que eu tentei:

ofstream outFile("test.txt", std::ios::out | std::ios::binary); wstring hello = L"hello"; outFile.write((char *) hello.c_str(), hello.length() * sizeof(wchar_t)); outFile.close(); 

Abrindo o test.txt no exemplo do Firefox com a codificação definida como UTF16 ele mostrará como:

h e l l o

Alguém poderia me dizer por que isso acontece?

EDITAR:

Abrindo o arquivo em um editor hexadecimal eu recebo:

 FF FE 68 00 00 00 65 00 00 00 6C 00 00 00 6C 00 00 00 6F 00 00 00 

Parece que eu recebo dois bytes extras entre cada caractere por algum motivo?

Eu suspeito que sizeof (wchar_t) é 4 em seu ambiente – ou seja, está escrevendo UTF-32 / UCS-4 em vez de UTF-16. Isso é certamente o que o despejo hex parece.

Isso é fácil de testar (apenas imprima sizeof (wchar_t)), mas tenho certeza que é o que está acontecendo.

Para ir de um wstring UTF-32 para UTF-16, você precisará aplicar uma codificação adequada, como pares substitutos entram em jogo.

Aqui nos deparamos com as propriedades de localidade pouco usadas. Se você gerar sua string como uma string (em vez de dados brutos), poderá fazer com que a localidade faça a conversão apropriada automaticamente.

NB Este código não leva em conta edianness do caractere wchar_t.

 #include  #include  #include  // See Below for the facet #include "UTF16Facet.h" int main(int argc,char* argv[]) { // construct a custom unicode facet and add it to a local. UTF16Facet *unicodeFacet = new UTF16Facet(); const std::locale unicodeLocale(std::cout.getloc(), unicodeFacet); // Create a stream and imbue it with the facet std::wofstream saveFile; saveFile.imbue(unicodeLocale); // Now the stream is imbued we can open it. // NB If you open the file stream first. Any attempt to imbue it with a local will silently fail. saveFile.open("output.uni"); saveFile << L"This is my Data\n"; return(0); } 

O arquivo: UTF16Facet.h

  #include  class UTF16Facet: public std::codecvt::state_type> { typedef std::codecvt::state_type> MyType; typedef MyType::state_type state_type; typedef MyType::result result; /* This function deals with converting data from the input stream into the internal stream.*/ /* * from, from_end: Points to the beginning and end of the input that we are converting 'from'. * to, to_limit: Points to where we are writing the conversion 'to' * from_next: When the function exits this should have been updated to point at the next location * to read from. (ie the first unconverted input character) * to_next: When the function exits this should have been updated to point at the next location * to write to. * * status: This indicates the status of the conversion. * possible values are: * error: An error occurred the bad file bit will be set. * ok: Everything went to plan * partial: Not enough input data was supplied to complete any conversion. * nonconv: no conversion was done. */ virtual result do_in(state_type &s, const char *from,const char *from_end,const char* &from_next, wchar_t *to, wchar_t *to_limit,wchar_t* &to_next) const { // Loop over both the input and output array/ for(;(from < from_end) && (to < to_limit);from += 2,++to) { /*Input the Data*/ /* As the input 16 bits may not fill the wchar_t object * Initialise it so that zero out all its bit's. This * is important on systems with 32bit wchar_t objects. */ (*to) = L'\0'; /* Next read the data from the input stream into * wchar_t object. Remember that we need to copy * into the bottom 16 bits no matter what size the * the wchar_t object is. */ reinterpret_cast(to)[0] = from[0]; reinterpret_cast(to)[1] = from[1]; } from_next = from; to_next = to; return((from > from_end)?partial:ok); } /* This function deals with converting data from the internal stream to a C/C++ file stream.*/ /* * from, from_end: Points to the beginning and end of the input that we are converting 'from'. * to, to_limit: Points to where we are writing the conversion 'to' * from_next: When the function exits this should have been updated to point at the next location * to read from. (ie the first unconverted input character) * to_next: When the function exits this should have been updated to point at the next location * to write to. * * status: This indicates the status of the conversion. * possible values are: * error: An error occurred the bad file bit will be set. * ok: Everything went to plan * partial: Not enough input data was supplied to complete any conversion. * nonconv: no conversion was done. */ virtual result do_out(state_type &state, const wchar_t *from, const wchar_t *from_end, const wchar_t* &from_next, char *to, char *to_limit, char* &to_next) const { for(;(from < from_end) && (to < to_limit);++from,to += 2) { /* Output the Data */ /* NB I am assuming the characters are encoded as UTF-16. * This means they are 16 bits inside a wchar_t object. * As the size of wchar_t varies between platforms I need * to take this into consideration and only take the bottom * 16 bits of each wchar_t object. */ to[0] = reinterpret_cast(from)[0]; to[1] = reinterpret_cast(from)[1]; } from_next = from; to_next = to; return((to > to_limit)?partial:ok); } }; 

É fácil se você usar o padrão C++11 (porque há muitas inclusões adicionais como "utf8" que resolve esses problemas para sempre).

Mas se você quiser usar o código multi-plataforma com padrões mais antigos, você pode usar este método para escrever com streams:

  1. Leia o artigo sobre o conversor UTF para streams
  2. Adicione stxutif.h ao seu projeto a partir das fonts acima
  3. Abra o arquivo no modo ANSI e adicione a BOM ao início de um arquivo, desta forma:

     std::ofstream fs; fs.open(filepath, std::ios::out|std::ios::binary); unsigned char smarker[3]; smarker[0] = 0xEF; smarker[1] = 0xBB; smarker[2] = 0xBF; fs << smarker; fs.close(); 
  4. Em seguida, abra o arquivo como UTF e escreva seu conteúdo lá:

     std::wofstream fs; fs.open(filepath, std::ios::out|std::ios::app); std::locale utf8_locale(std::locale(), new utf8cvt); fs.imbue(utf8_locale); fs << .. // Write anything you want... 

Nas janelas usando wofstream e a faceta utf16 definida acima falha porque o wofstream converte todos os bytes com o valor de 0A a 2 bytes 0D 0A, isto é independente de como você passa o byte 0A em, ‘\ x0A’, L ‘\ x0A’, L ‘\ x000A’, ‘\ n’, L ‘\ n’ e std :: endl dão o mesmo resultado. No windows você tem que abrir o arquivo com um ofstream (não um wofsteam) no modo binário e escrever a saída como é feito no post original.

O fornecido pelo Utf16Facet não funcionou no gcc para grandes strings, aqui está a versão que funcionou para mim … Desta forma o arquivo será salvo em UTF-16LE . Para UTF-16BE , simplesmente inverta as atribuições em do_in e do_out , por exemplo, to[0] = from[1] e to[1] = from[0]

 #include  #include  class UTF16Facet: public std::codecvt::state_type> { typedef std::codecvt::state_type> MyType; typedef MyType::state_type state_type; typedef MyType::result result; /* This function deals with converting data from the input stream into the internal stream.*/ /* * from, from_end: Points to the beginning and end of the input that we are converting 'from'. * to, to_limit: Points to where we are writing the conversion 'to' * from_next: When the function exits this should have been updated to point at the next location * to read from. (ie the first unconverted input character) * to_next: When the function exits this should have been updated to point at the next location * to write to. * * status: This indicates the status of the conversion. * possible values are: * error: An error occurred the bad file bit will be set. * ok: Everything went to plan * partial: Not enough input data was supplied to complete any conversion. * nonconv: no conversion was done. */ virtual result do_in(state_type &s, const char *from,const char *from_end,const char* &from_next, wchar_t *to, wchar_t *to_limit,wchar_t* &to_next) const { for(;from < from_end;from += 2,++to) { if(to<=to_limit){ (*to) = L'\0'; reinterpret_cast(to)[0] = from[0]; reinterpret_cast(to)[1] = from[1]; from_next = from; to_next = to; } } return((to != to_limit)?partial:ok); } /* This function deals with converting data from the internal stream to a C/C++ file stream.*/ /* * from, from_end: Points to the beginning and end of the input that we are converting 'from'. * to, to_limit: Points to where we are writing the conversion 'to' * from_next: When the function exits this should have been updated to point at the next location * to read from. (ie the first unconverted input character) * to_next: When the function exits this should have been updated to point at the next location * to write to. * * status: This indicates the status of the conversion. * possible values are: * error: An error occurred the bad file bit will be set. * ok: Everything went to plan * partial: Not enough input data was supplied to complete any conversion. * nonconv: no conversion was done. */ virtual result do_out(state_type &state, const wchar_t *from, const wchar_t *from_end, const wchar_t* &from_next, char *to, char *to_limit, char* &to_next) const { for(;(from < from_end);++from, to += 2) { if(to <= to_limit){ to[0] = reinterpret_cast(from)[0]; to[1] = reinterpret_cast(from)[1]; from_next = from; to_next = to; } } return((to != to_limit)?partial:ok); } }; 

Você deve olhar o arquivo de saída em um editor hexadecimal, como o WinHex, para poder ver os bits e bytes reais, para verificar se a saída é realmente UTF-16. Publique aqui e deixe-nos saber o resultado. Isso nos dirá se devemos culpar o Firefox ou seu programa C ++.

Mas parece-me que o seu programa C ++ funciona e o Firefox não está interpretando corretamente o seu UTF-16. O UTF-16 chama dois bytes para cada caractere. Mas o Firefox está imprimindo duas vezes mais caracteres do que deveria, então provavelmente está tentando interpretar sua string como UTF-8 ou ASCII, que geralmente tem apenas 1 byte por caractere.

Quando você diz “Firefox com codificação definida como UTF16”, o que você quer dizer? Eu sou cético que esse trabalho funcione.