Мастера DELPHI, Delphi programming community Рейтинг@Mail.ru Титульная страница Поиск, карта сайта Написать письмо 
| Новости |
Новости сайта
Поиск |
Поиск по лучшим сайтам о Delphi
FAQ |
Огромная база часто задаваемых вопросов и, конечно же, ответы к ним ;)
Статьи |
Подборка статей на самые разные темы. Все о DELPHI
Книги |
Новинки книжного рынка
Новости VCL
Обзор свежих компонент со всего мира, по-русски!
|
| Форумы
Здесь вы можете задать свой вопрос и наверняка получите ответ
| ЧАТ |
Место для общения :)
Орешник
Коллекция курьезных вопросов из форумов
Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / rsdn.org

 
Чтобы не потерять эту дискуссию, сделайте закладку « предыдущая ветвь | форум | следующая ветвь »
Страницы: 1

Ошибка в UTF8_2KOLWideString [Delphi, Windows]


QAZ ©   (19.05.18 20:00

в вызываемой MultiByteToWideChar указано параметром L-1, то есть длинна без нуля в конце, а должно быть длинна с нулем, либо -1, иначе возвращает буфер без нуля в конце
function UTF8_2KOLWideString( const s: AnsiString ): KOLWideString;  //взята из kol.pas с исправлением ошибки
var Buffer: PWideChar;
   L: Integer;
begin
 L := Length( s ) + 1;
 GetMem( Buffer, L * 2 );                          
MultiByteToWideChar( CP_UTF8, 0, PAnsiChar( s ), L{-1},//тут ошибка, должна быть длина строки вместе с нулем в конце, либо -1
   Buffer, L );
 Result := Buffer;
 FreeMem( Buffer );
end;


QAZ ©   (20.05.18 14:39[1]

думаю такой вариант будет идеальным, никаких лишних телодвижений, работа чисто по длинам
function UTF8_2KOLWideString( const s: AnsiString ): KOLWideString;  //взята из kol.pas с исправлением ошибки отсутствия завершающего 0
var Buffer: PWideChar;
   L: Integer;
begin
 L := Length( s );
 GetMem( Buffer, L * 2 );
 L := MultiByteToWideChar( CP_UTF8, 0, PAnsiChar( s ), L , Buffer, L );
 SetString(Result,Buffer,L);
 FreeMem( Buffer );
end;


Netspirit   (22.05.18 14:19[2]

Два раза перемещение данных - сначала в Buffer (по MultiByteToWideChar), потом в Result (по SetString). И да, а с чего вы взяли что размер буфера будет Length(s)*2?

Правильное преобразование:
function UTF8_2KOLWideString(const S: AnsiString): KOLWideString;
var
 L, DestL: DWord;
begin  
 Result := '';
 if S = '' then Exit;
 L := Length(S);
 
 // Сначала определение длины результата
 DestL := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, nil, 0);
 if DestL = 0 then Exit;
 
 // Собственно, преобразование
 SetLength(Result, DestL); // DestL - в WideChar's
 MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, Pointer(Result), DestL);
end;

(Result будет null-терминированным)


QAZ ©   (24.05.18 19:28[3]


> Netspirit

размер буфера максимальный, что очевидно
не уверен что двойное перемещение и одна конвертация хуже, чем двойная конвертация и одно перемещение + лишние If и присвоение
ведь получение размера эта та же конвертация только впустую


Netspirit   (29.05.18 11:51[4]


> размер буфера максимальный, что очевидно

Да нет, в случае UTF-8 - не очевидно. Так как один символ может иметь  размер и 3 и 4 байта. А в результирующей WideString один символ может быть представлен 2-мя WideChar (4 байта).
В исходном решении буфер рассчитывается максимум только на 2-байтные символы UTF-8.


> хуже, чем двойная конвертация

Производительность можно сравнить, конечно, но не думаю что при подсчете длины там идёт "полная конвертация" (получение кода Unicode символа и запись его в поданный нами буфер - потому что мы никакого буфера не подали). Идёт перебор байтов, проверка начальных битов для определения длины символа и перескок через нужное количество байт.


Netspirit   (29.05.18 11:59[5]

Хотя, да, не так понял. Даже если будут символы по 3-4 байта это просто повлияет на то, что будет выделен буфер в 2 раза больше чем нужно.
Тогда, вероятно, можно и не делать Length(S)*2, достаточно и Length(S) - расчёт на максимальное коичество символов в исходной строке - когда все символы 1-байтные.


Netspirit   (29.05.18 12:04[6]


> можно и не делать Length(S)*2

... если в качестве буфера использовать тот же Result и SetLength() перед и после конвертации.

function UTF8_2KOLWideString(const S: AnsiString): KOLWideString;
var
 L: DWord;
begin  
 Result := '';
 if S = '' then Exit;
 L := Length(S);
 
 SetLength(Result, L); // L - в WideChar's з запасом
 L := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, Pointer(Result), L);
 SetLength(Result, L); // Точная длина Result
end;


QAZ ©   (29.05.18 22:11[7]


> Netspirit   (29.05.18 12:04) [6]

второй  SetLength это не тупо замена циферок в заголовке строки, а создание новой строки и копирование туда текущей до заданной длинны :)


Netspirit   (30.05.18 10:44[8]

Конечно. 2 перемещения, как не крути.


Netspirit   (30.05.18 12:34[9]

Вот пример теста производительности, если кому интересно:

program ConvBench;

{$APPTYPE CONSOLE}

uses
 Windows,
 SysUtils;

function UTF8ToWStr1(const S: AnsiString): WideString;
var
 Buffer: PWideChar;
 L: Integer;
begin
 L := Length(S);
 GetMem(Buffer, L * 2);
 L := MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(S), L, Buffer, L);
 SetString(Result, Buffer, L);
 FreeMem(Buffer);
end;

function UTF8ToWStr2(const S: AnsiString): WideString;
var
 L: DWord;
begin
 Result := '';
 if S = '' then Exit;
 L := Length(S);

 SetLength(Result, L);
 L := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, Pointer(Result), L);
 SetLength(Result, L);
end;

function UTF8ToWStr3(const S: AnsiString): WideString;
var
 L, DestL: DWord;
begin  
 Result := '';
 if S = '' then Exit;
 L := Length(S);

 DestL := MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, nil, 0);
 if DestL = 0 then Exit;

 SetLength(Result, DestL);
 MultiByteToWideChar(CP_UTF8, 0, Pointer(S), L, Pointer(Result), DestL);
end;

const
 SAMPLE_SIZE = 30*1024*1024;
 CYCLES = 100;

procedure RunBenchmark;
var
 AStr: AnsiString;
 WStr: WideString;
 T1, T2, T3: DWORD;
 I: Integer;
begin
 //Заполняем образец символом 'Э' в UTF-8 ($D0 $AD)
 SetLength(AStr, SAMPLE_SIZE);

 I := 1;
 while I < SAMPLE_SIZE do
 begin
   AStr[I] := #$D0;
   Inc(I);
   AStr[I] := #$AD;
   Inc(I);
 end;

 Writeln('Benchmark started. Please, wait...');

 T1 := GetTickCount;
 for I := 1 to CYCLES do
 begin
   WStr := UTF8ToWStr1(AStr);
 end;

 T2 := GetTickCount;
 T1 := T2 - T1;

 for I := 1 to CYCLES do
 begin
   WStr := UTF8ToWStr2(AStr);
 end;
 T3 := GetTickCount;
 T2 := T3 - T2;

 for I := 1 to CYCLES do
 begin
   WStr := UTF8ToWStr3(AStr);
 end;
 T3 := GetTickCount - T3;

 Writeln('Benchmark finished.');
 Writeln('Bytes processed: ', FloatToStr(SAMPLE_SIZE / 1024 / 1024 * CYCLES), ' MBytes');
 Writeln('');
 Writeln('Results');
 Writeln('UTF8ToWStr1: '+ IntToStr(T1));
 Writeln('UTF8ToWStr2: '+ IntToStr(T2));
 Writeln('UTF8ToWStr3: '+ IntToStr(T3));

 Writeln('');
 Writeln('Press "Enter" to close.');

 Readln(AStr);
end;

begin
 RunBenchmark;
end.


hcode   (01.06.18 00:03[10]

> Netspirit:= красавчик!


Netspirit   (01.06.18 11:38[11]

Спасибо ;-)

По результатам теста - мое первое предложение с предварительным определением длины результата (дважды вызов MultiByteToWideChar, функция UTF8ToWStr3) на Windows XP где-то в 2 раза медленнее, чем существующие варианты. На Windows 7 результаты практически идентичны. Может, зависит не от ОС, а от оборудования, но на Windows XP было тоже достаточно мощное железо, хоть и устаревшее.
Функция UTF8ToWStr2() немного обгоняет UTF8ToWStr1(), так что можно остановиться на ней.
Если есть необходимость экономить память при работе с большими строками UTF-8, тогда есть смысл использовать UTF8ToWStr3().


Dimaxx ©   (01.06.18 14:06[12]

Тогда ввести в КОЛ варианты под условную компиляцию - $UTF8Convert_LOWMEM для UTF8ToWStr (для 3) и UTF8ToWStr (для 2) по дефолту.


Страницы: 1 версия для печати

Написать ответ

Ваше имя (регистрация  E-mail 







Разрешается использование тегов форматирования текста:
<b>жирный</b> <i>наклонный</i> <u>подчеркнутый</u>,
а для выделения текста программ, используйте <code> ... </code>
и не забывайте закрывать теги! </b></i></u></code> :)


Наверх

  Рейтинг@Mail.ru     Титульная страница Поиск, карта сайта Написать письмо