2008-10-16 48 views
11

Estoy intentando escribir una wstring a presentar con ofstream en modo binario, pero creo que estoy haciendo algo mal. Esto es lo que he intentado:escritura utf16 a archivo en modo binario

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(); 

test.txt de apertura, por ejemplo, en Firefox con la codificación establecida en UTF16 se mostrará como:

hola

¿Alguien podría decirme por qué sucede esto?

EDIT:

Al abrir el archivo en un editor hexadecimal me sale:

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

Parece que tengo dos bytes adicionales entre cada carácter por alguna razón?

+0

Añadir una faceta a la local asociada con la corriente para hacer la conversión de wchar_t a la salida correcta. Vea abajo. –

Respuesta

6

Sospecho que sizeof (wchar_t) es 4 en su entorno, es decir, está escribiendo UTF-32/UCS-4 en lugar de UTF-16. Eso es ciertamente lo que parece el volcado hexadecimal.

Eso es bastante fácil de probar (simplemente imprima sizeof (wchar_t)) pero estoy bastante seguro de que es lo que está pasando.

Para pasar de un UTF-32 wstring a un UTF-16 necesitarás aplicar una codificación adecuada, ya que los pares de sustitución entran en juego.

+0

Sí, tienes razón wchar_t es de tamaño 4, estoy en un mac. Eso explica mucho :) Estoy al tanto de los pares de sustitución en UTF-16, tendré que estudiar un poco más. – Cactuar

+0

Desde la salida no se puede decir que sea UTF-16 o UTF-32, todo lo que muestra es que wchar_t tiene 4 bytes de ancho. La codificación de la cadena no está definida por el lenguaje (aunque es más probable que sea UCS-4). –

0

usted debe buscar en el archivo de salida en un editor hexadecimal como WinHex para que pueda ver los bits y bytes reales, para verificar que la salida es en realidad UTF-16. Póngalo aquí y díganos el resultado. Eso nos dirá si culpar a Firefox o su programa C++.

Pero me parece que su programa C++ funciona y Firefox no está interpretando correctamente su UTF-16. UTF-16 requiere dos bytes para cada personaje. Pero Firefox está imprimiendo el doble de caracteres como debe ser, por lo que probablemente está tratando de interpretar su cadena como UTF-8 o ASCII, que por lo general sólo tienen 1 byte por carácter.

Cuando dices "Firefox con codificación establecida en UTF16", ¿qué quieres decir? Soy escéptico de que ese trabajo funcione.

14

Aquí nos encontramos con las pequeñas propiedades región utilizada. Si la salida de su cadena como una cadena (en lugar de los datos en bruto) se puede obtener la configuración regional para hacer la conversión apropiada de auto-mágicamente.

N.B. Este código no tiene en cuenta la calidad del carácter wchar_t.

#include <locale> 
#include <fstream> 
#include <iostream> 
// 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); 
}  

El Archivo: UTF16Facet.h

#include <locale> 

class UTF16Facet: public std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type> 
{ 
    typedef std::codecvt<wchar_t,char,std::char_traits<wchar_t>::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<char*>(to)[0] = from[0]; 
      reinterpret_cast<char*>(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<const char*>(from)[0]; 
      to[1]  = reinterpret_cast<const char*>(from)[1]; 

     } 
     from_next = from; 
     to_next  = to; 

     return((to > to_limit)?partial:ok); 
    } 
}; 
+0

Tenga en cuenta que su Facet implementa la conversión a/desde UCS-2, no UTF-16. UTF-16 es una codificación de longitud variable que los instrumentos llamados pares de sustitución. UCS-2 es un subconjunto de Unicode, que es la razón por la que UTF-16 se ha inventado. –

2

para Windows utilizando wofstream y la faceta utf16 definido anteriormente falla becuase la wofstream convierte todos los bytes con el valor 0A a 2 bytes 0D 0A, esto es independientemente de cómo se pasa el byte 0A en, '\ x0A', L '\ x0A', L '\ x000A', '\ n', L '\ n' y std :: endl todos dan el mismo resultado. En las ventanas hay que abrir el archivo con una ofstream (no un wofsteam) en modo binario y escribir la salida al igual que se hace en el post original.

1

El Utf16Facet proporcionado no funcionó en gcc para grandes cadenas, aquí está la versión que funcionó para mí ... De esta manera el archivo se guardará en UTF-16LE. Para UTF-16BE, simplemente invierta las asignaciones en do_in y do_out, p. Ej. to[0] = from[1] y to[1] = from[0]

#include <locale> 
#include <bits/codecvt.h> 


class UTF16Facet: public std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type> 
{ 
    typedef std::codecvt<wchar_t,char,std::char_traits<wchar_t>::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<char*>(to)[0] = from[0]; 
       reinterpret_cast<char*>(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<const char*>(from)[0]; 
       to[1]  = reinterpret_cast<const char*>(from)[1]; 

       from_next = from; 
       to_next  = to; 
      } 
     } 

     return((to != to_limit)?partial:ok); 
    } 
}; 
6

Es fácil si utiliza el estándar C++11 (porque hay una gran cantidad de adicional incluye como "utf8" que resuelve este problema para siempre).

Pero si usted quiere usar el código multiplataforma con las normas más antiguas, puede utilizar este método para escribir con corrientes:

  1. Read the article about UTF converter for streams
  2. Añadir stxutif.h a su proyecto a partir de fuentes anteriormente
  3. Abra el archivo en modo ANSI y agregue la lista de materiales al inicio de un archivo, como este:

    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. A continuación, abra el archivo como UTF y escribir su contenido allí:

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

'+ 1' para la explicación clara y de referencia :) – Anne

Cuestiones relacionadas