Страница 1 из 4

Не получается приведение

Добавлено: 09 апр 2014, 07:37
Сионист

Код: Выделить всё

std::string  WStringToUtf8  (const std::wstring &s)
{
 char        *Buffer;
 size_t       BufferSize;
 std::string  Result="";
 if (!s.empty())
 {
  BufferSize=WideCharToMultiByte(CP_UTF8, WC_COMPOSITECHECK, s.c_str(), s.length(), NULL, 0, NULL, NULL);
  Buffer=new char[BufferSize+1];
  if (Buffer!=NULL)
  {
   WideCharToMultiByte(CP_UTF8, WC_COMPOSITECHECK, s.c_str(), s.length(), Buffer, BufferSize, NULL, NULL);
   Buffer[BufferSize]='\0';
   Result=Buffer;
   delete [] Buffer;
  }
 }
 return Result;
}
. s=L"MOV EAX, 0", но BufferSize в 8-й строке получает значение 0. Функция предназначена для приведения строки std::wstring к киду, пригодному для вывода в поток

Код: Выделить всё

std: :o fstream file
, такому, чтоб в результате получить файл в кодировке UTF-8 с BOM. В начале вызывающей функции уже есть

Код: Выделить всё

std: :o fstream File;
File.open(ToString(Directory+L"\\"+Name+L"\\"+Name+L".asm").c_str());
File<<(char)0xEF<<(char)0xBB<<(char)0xBF;
и вроде бы Notepad++ распознал файл, как UTF-8, но из пустых строк.

Re: Не получается приведение

Добавлено: 09 апр 2014, 10:34
Romeo
О, это шаг вперёд, ты всё-таки поверил, что std::wstring - это просто широкая строка и вызвал WinAPI функицю для перекодировки. Хорошо, это первый шаг к успеху. А вот дальше всё плохо.

Давай начнём с того, что такое std::string и как работает её конструктор. Класс std::string - это, фактически, обёртка над старой доброй null terminated string, которая пришла к нам ещё из С. Что такое null terminated? Это значит символ с ASCII значением 0 является признаком конца строки. Совершенно очевидно, что конструктор std::string сначала вызывает strlen для входного буфера, потом выделяет внутренний буфер на len + 1 элементов, а затем вызывает strcpy из входного буфера во внутренний. Ты уже понял в каком месте будет фейл? Если не понял, то подсказываю. Функция strlen, кототорая работает с входным буфером, как с null terminated строкой, вернёт длину ноль. Знаешь почему? Потому, что вместо узкой null terminated строки ты передал широкую двухбайтную строка, в которой латинские символы, а следовательно первый байт в каждом двубайтном символе - НУЛЕВОЙ. Этот ноль в первом символе коструктор std::string воспримет, как конец строки, и создаст пустой объект, после записи которого в файл, ты увидишь пустоту.

Как решить проблему? Либо вернуть наружу перекодированный массив по поинтеру (и не забыть ему сделать delete [] после выводы в файл), либо возвращать std::vector<char>, воспользовавшись для создания его объекта конструтором, принимающим два итератора. Второй подход более красив, так как позволяет избежать потенциального мемори лика зазеваашемуся программисту, вызывающему твою функцию, но зато требует чуть большей сноровки.

Re: Не получается приведение

Добавлено: 09 апр 2014, 12:51
Сионист
Romeo писал(а):О, это шаг вперёд, ты всё-таки поверил, что std::wstring - это просто широкая строка и вызвал WinAPI функицю для перекодировки.
Шаг вперёд от чего?
Romeo писал(а):Давай начнём с того, что такое std::string и как работает её конструктор.
Дело не в конструкторе, обратным преобразованием я уже год, как гружу xml-UTF-8 файлы. Дело в том, что на вход он получает строку из одного терминального символа.
Romeo писал(а):Класс std::string - это, фактически, обёртка над старой доброй null terminated string, которая пришла к нам ещё из С.
Не путай стандартный std::string с моим TSring, стандарт не гарантирует даже непрерывности внутреннего представления, не говоря о терминальном символе и порядке символов в памяти. c_str() возвращает указатель на null-терминальную строку, но ни где не сказано, что это не копия и что строка не приводится к завёрнутой в объект нуль-терминальной хоть переворачиванием. Удобней всего завернуть гибрид нультерминальной строки с паскаль-строкой в объект, но стандарт реализацию не описывает.

Re: Не получается приведение

Добавлено: 09 апр 2014, 12:56
Сионист
Функция strlen, кототорая работает с входным буфером, как с null terminated строкой, вернёт длину ноль. Знаешь почему? Потому, что вместо узкой null terminated строки ты передал широкую двухбайтную строка, в которой латинские символы, а следовательно первый байт в каждом двубайтном символе - НУЛЕВОЙ.
То то

Код: Выделить всё

std::string                                 ToString                                  (const
                                                                                       std::wstring                     &s                 )
{
 char        *Buffer;
 size_t       BufferSize;
 std::string  Result="";
 if (!s.empty())
 {
  BufferSize=WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, s.c_str(), s.length(), NULL, 0, NULL, NULL);
  Buffer=new char[BufferSize+1];
  if (Buffer!=NULL)
  {
   WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, s.c_str(), s.length(), Buffer, BufferSize, NULL, NULL);
   Buffer[BufferSize]='\0';
   Result=Buffer;
   delete [] Buffer;
  }
 }
 return Result;
}
работает, а функция в стартовом посте этой темы не берёт даже латиницу. На дебаге бага в 8-й строке при измерении размера буфера самой апишной функцией перекодировки, а она то знает, что искать надо два ноля.

Re: Не получается приведение

Добавлено: 09 апр 2014, 14:01
Romeo
Не путай стандартный std::string с моим TSring
Да я как бы не путаю. Сложно спутать std::string с твоим TSring, если ты свой TSring в приведённом коде не используешь ни в одном месте :)
стандарт не гарантирует даже непрерывности внутреннего представления, не говоря о терминальном символе и порядке символов в памяти. c_str() возвращает указатель на null-терминальную строку, но ни где не сказано, что это не копия и что строка не приводится к завёрнутой в объект нуль-терминальной хоть переворачиванием. Удобней всего завернуть гибрид нультерминальной строки с паскаль-строкой в объект, но стандарт реализацию не описывает.
Это ты всё верно написал про стандарт, что нету гарантий, кроме последней части про "удобнее всего". Удобнее всего наверное было бы тебе. Но на всех компиляторах, на которых я расковыривал std::string в исходниках, внутри была почему-то обычная СИшная строка :)
работает, а функция в стартовом посте этой темы не берёт даже латиницу. На дебаге бага в 8-й строке при измерении размера буфера самой апишной функцией перекодировки, а она то знает, что искать надо два ноля.
Да, само собой API функция знает, что нужно искать два ноля, ведь она принимает широкую строку. Я тебе говорил что нельзя сконвертированный результат возвращать из функции, а не что нельзя передавать широкую строку в функцию конвертации. А заработало сейчас всё лишь потому, что ты сменил CP_UTF8 на CP_ACP и возвращаемый буфер перестал быть "широким". Ты не устранил проблему, а отсрочил её. Попробуй теперь кирилицу сконвертить :)

Re: Не получается приведение

Добавлено: 09 апр 2014, 14:13
Сионист
Да я как бы не путаю. Сложно спутать std::string с твоим TSring, если ты свой TSring в приведённом коде не используешь ни в одном месте
А что, такая реализация уже белещет сверхсложностью и без телепатии её не повторить?

Re: Не получается приведение

Добавлено: 09 апр 2014, 14:14
WinMain
Лучше используй готовые макросы ATL.

Код: Выделить всё

#include <atlconv.h>
#include <string>

std::string WcharToUtf8(LPCWSTR wszInputStr)
{
	USES_CONVERSION;
	LPCSTR output = W2CA_CP(wszInputStr, CP_UTF8); 
	// 
	return std::string(output);
}

int _tmain(int argc, _TCHAR* argv[])
{
	std::string utf8 =  WcharToUtf8(L"Всем привет!");

	// TODO: Дальнейший код...

	return 0;
}

Re: Не получается приведение

Добавлено: 09 апр 2014, 14:15
Сионист
Но на всех компиляторах, на которых я расковыривал std::string в исходниках, внутри была почему-то обычная СИшная строка
И в length strlen завёрнут? А ведь как просто перед самой строкой разместить количество символов, а сразу за ней терминальный ноль.

Re: Не получается приведение

Добавлено: 09 апр 2014, 14:17
Сионист
WinMain писал(а):Лучше используй готовые макросы ATL.
И чем же это макросы лучше функций? Да ещё нуль-терминальная строка в интерфейсе.

Re: Не получается приведение

Добавлено: 09 апр 2014, 14:21
Romeo
А что, такая реализация уже белещет сверхсложностью и без телепатии её не повторить?
Нет, не блещет и ничего сложного в ней нет. Но телепатия понадобится, чтобы использовать в коде класс, ни разу не написав в листинге его имя :) Повторюсь, ты использовал std::string, а не что-то другое. И с ним UTF-8 работать не будет.
И в length strlen завёрнут?
Нет, не завёрнут. Для длины отдельное поле класса предусмотрено. Я о том, что строка внутри обычная СИшная, а не сложная структура разрывных кусков, что позволяет стандарт.

WinMain, как я рад тебя видеть. Я уже устал с ним общаться :)