Запись utf16 в файл в двоичном режиме - PullRequest
11 голосов
/ 16 октября 2008

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

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 

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

Ответы [ 6 ]

14 голосов
/ 16 октября 2008

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

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);
   }
};
6 голосов
/ 20 сентября 2012

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

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

  1. Прочтите статью о UTF-конвертере для потоков
  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...
    
6 голосов
/ 16 октября 2008

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

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

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

2 голосов
/ 22 мая 2009

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

1 голос
/ 09 июня 2012

Предоставленный 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);
   }
};
0 голосов
/ 16 октября 2008

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

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

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

...