Конференция "KOL" » Ошибка в UTF8_2KOLWideString [Delphi, Windows]
 
  • QAZ © (19.05.18 20:00) [0]
    в вызываемой 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) по дефолту.
 
Конференция "KOL" » Ошибка в UTF8_2KOLWideString [Delphi, Windows]
Есть новые Нет новых   [118625   +12][b:0][p:0.002]