2008-10-16 3 views
11

Я пытаюсь написать wstring для файла с потоком в двоичном режиме, но я думаю, что я делаю что-то неправильно. Это то, что я пробовал:Запись utf16 в файл в двоичном режиме

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 Открытие, например, в Firefox с кодировкой установлен utf16 будет отображаться как:

привет

Может ли кто-нибудь сказать мне, почему это происходит?

EDIT:

Открытие файла в шестнадцатеричном редакторе, я получаю:

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

Похоже, я получаю два дополнительных байта между каждым символом по какой-то причине?

+0

Добавить фаску в местный, связанный с потоком, чтобы сделать преобразование из wchar_t к правильному выходу. Смотри ниже. – 2008-10-16 13:01:42

ответ

6

Я подозреваю, что sizeof (wchar_t) равен 4 в вашей среде - то есть он выписывает UTF-32/UCS-4 вместо UTF-16. Это, конечно, то, как выглядит шестнадцатеричный дамп.

Это достаточно легко проверить (просто распечатайте sizeof (wchar_t)), но я уверен, что это то, что происходит.

Чтобы перейти от UTF-32 wstring к UTF-16, вам необходимо применить правильную кодировку, поскольку в нее входят суррогатные пары.

+0

Да, вы правы. Wchar_t имеет размер 4, я нахожусь в маке. Так что многое объясняет :) Я знаю суррогатные пары в UTF-16, придется заглянуть в это немного больше. – Cactuar 2008-10-16 08:01:10

+0

На выходе вы не можете сказать, что это UTF-16 или UTF-32, все это показывает, что wchar_t имеет ширину 4 байта. Кодирование строки не определяется языком (хотя, скорее всего, это UCS-4). – 2008-10-16 13:10:30

0

Вы должны посмотреть на выходной файл в шестнадцатеричном редакторе, таком как WinHex, чтобы вы могли видеть фактические биты и байты, чтобы убедиться, что вывод на самом деле является UTF-16. Отправьте его здесь и сообщите нам о результатах. Это скажет нам, следует ли обвинять Firefox или вашу C++-программу.

Но мне кажется, что ваша программа на C++ работает, и Firefox не интерпретирует ваш UTF-16 правильно. UTF-16 вызывает два байта для каждого символа. Но Firefox печатает в два раза больше символов, чем должен, поэтому, вероятно, он пытается интерпретировать вашу строку как UTF-8 или ASCII, которые обычно имеют только 1 байт на символ.

Когда вы говорите «Firefox с кодировкой, установленной в UTF16», что вы имеете в виду? Я скептически отношусь к этой работе.

14

Здесь мы сталкиваемся с небольшим количеством используемых свойств локали. Если вы выводите свою строку в виде строки (а не сырых данных), вы можете заставить локаль выполнить соответствующее преобразование автоматически.

N.B. Этот код не учитывает edianness символа 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); 
}  

файл: 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

Обратите внимание, что ваш Facet реализует преобразование в/из UCS-2, а не UTF-16. UTF-16 - это кодирование с переменной длиной, которое обрабатывает так называемые суррогатные пары. UCS-2 является подмножеством Unicode, поэтому была изобретена UTF-16. – 2017-05-04 21:12:29

2

на окнах с использованием wofstream и тому utf16 фаска определены выше не becuase wofstream преобразует все байты со значением 0A до 2 байта 0D 0A, это независимо от о том, как вы передаете байт 0A, '\ x0A', L '\ x0A', L '\ x000A', '\ n', L '\ n' и std :: endl дают одинаковый результат. В окнах вам нужно открыть файл с потоком (не wofsteam) в двоичном режиме и написать вывод так же, как в исходном сообщении.

1

Предоставленный Utf16Facet не работает в gcc для больших строк, вот версия, которая сработала для меня ... Таким образом, файл будет сохранен в UTF-16LE. Для UTF-16BE просто инвертируйте назначения в do_in и do_out, например. to[0] = from[1] и 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

Это легко, если вы используете стандарт C++11 (потому что есть много дополнительной включает в себя как "utf8", который решает эти проблемы навсегда).

Но если вы хотите использовать код мультиплатформенный со старыми стандартами, вы можете использовать этот метод для записи с потоками:

  1. Read the article about UTF converter for streams
  2. Добавить stxutif.h в свой проект из источников выше
  3. Откройте файл в режиме ANSI и добавьте спецификацию в начало файла, например:

    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. Затем откройте файл как UTF и написать содержание там:

    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... 
    
Смежные вопросы