Конференция "WinAPI" » Работа с REG_MULTI_SZ [D7, WinXP]
 
  • ak-el-a (30.11.12 21:50) [0]
    Доброго времени суток!
    Мне нужно работать с набором строк - записать в реестр и считать из реестра.
    В случае чтения - у меня получилось.
    Делаю я это с помощью процедуры
    ReadREG_MULTI_SZ .
    procedure ReadREG_MULTI_SZ(const CurrentKey: HKey; const Subkey, ValueName: string;
      Strings: TStrings);
    var
      valueType: DWORD;
      valueLen: DWORD;
      p, buffer: PChar;
      key: HKEY;
    begin
      // Clear TStrings
     // TStrings leeren
     Strings.Clear;
      // open the specified key
     // CurrentKey Schlussel offnen
     if RegOpenKeyEx(CurrentKey,
                      PChar(Subkey),
                      0, KEY_READ, key) = ERROR_SUCCESS then
      begin
        // retrieve the type and data for a specified value name
       // Den Typ und Wert des Eintrags Ermitteln.
       SetLastError(RegQueryValueEx(key,
                     PChar(ValueName),
                     nil,
                     @valueType,
                     nil,
                     @valueLen));
        if GetLastError = ERROR_SUCCESS then
          if valueType = REG_MULTI_SZ then
          begin
            GetMem(buffer, valueLen);
            try
              // receive the value's data (in an array).
             // Ein Array von Null-terminierten Strings
             // wird zuruckgegeben
             RegQueryValueEx(key,
                              PChar(ValueName),
                              nil,
                              nil,
                              PBYTE(buffer),
                              @valueLen);
              // Add values to stringlist
             // Werte in String Liste einfugen
             p := buffer;
              while p^ <> #0 do
              begin
                Strings.Add(p);
                Inc(p, lstrlen(p) + 1)
              end
            finally
              FreeMem(buffer)
            end
          end
          else
            raise ERegistryException.Create('Stringlist expected/ String Liste erwartet...')
        else
          raise ERegistryException.Create('Cannot Read MULTI_SZ Value/'+
            'Kann den MULTI_SZ Wert nicht lesen...');
      end;
    end;
    А вот записать набор строк корректно у меня не получается. В результате преобразования
    преобразование строк в PCHAR:
    procedure ...
    var

     Buffer: Pointer;
     BufSize: DWORD;
     i, j, k: Integer;
     s: string;
     p: PChar;
    begin

     {подготовим буфер к записи}
     BufSize := 0;
     for i := 0 to list.Count - 1 do
       inc(BufSize, Length(list[i]) + 1);
     inc(BufSize);
     GetMem(Buffer, BufSize);
     k := 0;
     p := Buffer;
     for i := 0 to list.Count - 1 do
     begin
       s := list[i];
       for j := 0 to Length(s) - 1 do
       begin
         p[k] := s[j + 1];
         inc(k);
       end;
       p[k] := chr(0);
       inc(k);
     end;
     p[k] := chr(0);

    end;
    получаю не набор строк, а одну строку и дальше набор невразумительных символов.
    Поскажите как корректно записать Tstrings в параметр REG_MULTI_SZ регистра.
  • DVM © (30.11.12 21:57) [1]


    unit uRegistryEx;

    {$WARN SYMBOL_PLATFORM OFF}

    interface

    {$INCLUDE DEFINES.INC}

    {$IFNDEF MSWINDOWS}
    These classes are only usable in Windows!!!
    {$ENDIF}

    uses
     {$IFDEF DELPHIXE2_UP}
     Winapi.Windows, System.SysUtils, System.Classes,
     System.Win.Registry, System.RTLConsts;
     {$ELSE}
     Windows, SysUtils, Classes, Registry, RTLConsts;
     {$ENDIF}

    const
     HKEY_CURRENT_USER_LOCAL_SETTINGS = HKEY(Integer($80000007));

    type

     TRegistryEx = class(TRegistry)
     public
       procedure ReadMultiLine(const AName: string; Lines: TStrings);
       procedure WriteMultiLine(const AName: string; Lines: TStrings);
     end;

    implementation

    const
     CRLF = #13#10;
     ZeroChar = #0;

    { TRegistryEx }

    procedure TRegistryEx.ReadMultiLine(const AName: string; Lines: TStrings);
    var
     P: integer;
     Str: string;
     Size: integer;
    begin
     Size := GetDataSize(AName);
     if (Size > 0) then
       begin
         SetLength(Str, Size div SizeOf(Char));
         ReadBinaryData(AName, Str[1], Size);
         repeat
           P := Pos(ZeroChar, Str);
           if P <> 0 then
             begin
               Delete(Str, P, 1);
               Insert(CRLF, Str, P);
             end;
         until P = 0;
         Lines.Text := Str;
       end;
    end;

    //------------------------------------------------------------------------------

    procedure TRegistryEx.WriteMultiLine(const AName: string; Lines: TStrings);
    var
     i: integer;
     Str: string;
    begin
     Str := '';
     for I := 0 to Lines.Count - 1 do
       if Length(Lines[I]) <> 0 then
         Str := Str + Lines[I] + ZeroChar;
     if not CheckResult(RegSetValueEx(CurrentKey, PChar(AName), 0, REG_MULTI_SZ, @Str[1],
       Length(Str) * SizeOf(Char))) then
       raise ERegistryException.CreateResFmt(@SRegSetDataFailed, [AName]);
    end;

    end.


  • Игорь Шевченко © (30.11.12 22:52) [2]

    >     if P <> 0 then
    >          begin
    >            Delete(Str, P, 1);
    >            Insert(CRLF, Str, P);
    >          end;


    сильно
  • DVM © (30.11.12 23:22) [3]

    > Игорь Шевченко ©   (30.11.12 22:52) [2]


    > сильно

    Да, коряво как то, я и сам знаю.
    Да StringReplace туда вставить и все. Или просто разбить по нулям и сразу добавить строки в список.
  • Ваще имя (02.12.12 17:25) [4]

    > Да StringReplace туда вставить и все.

    Еще хлеще.

    Вот источник кода: http://www.swissdelphicenter.ch/torry/printcode.php?id=1431

    А извлечение строк из блока MULTI_SZ выглядит так (копируется в соответствии с http://gnu.org/licenses/gpl.html):

    procedure _MultiSzToStrings(MultiSz: PChar; Strings: TStrings);
    var
     P: PChar;
     S: string;
    begin
     P := MultiSz;
     while P^ <> #0 do
     begin
       S := P;
       Strings.Add(S);
       Inc(P, Length(S) + 1);
     end;
    end;

  • DVM © (02.12.12 18:10) [5]

    > Ваще имя   (02.12.12 17:25) [4]


    > while P^ <> #0 do
    >  begin
    >    S := P;
    >    Strings.Add(S);
    >    Inc(P, Length(S) + 1);
    >  end;

    ну я ж и говорю:


    > Или просто разбить по нулям и сразу добавить строки в список.


    Вообще, в виду того, что в реестре хранятся крайне малые строковые блоки, метод разбивки строки особой роли не играет, все равно будет быстро.
  • DVM © (02.12.12 18:25) [6]

    > Ваще имя   (02.12.12 17:25) [4]


    >  P := MultiSz;
    >  while P^ <> #0 do
    >  begin
    >    S := P;
    >    Strings.Add(S);
    >    Inc(P, Length(S) + 1);
    >  end;

    Вообще этот метод нельзя считать безопасным на 100%. Если в конце не окажется двух #0 то некоторых случаях уйдем в бесконечность, в лучшем случае получим мусор:


    var
     s: string;
     sl: TStringList;
    begin
     sl := TStringList.Create;
     try
       s := '123456789';
       _MultiSzToStrings(PChar(s), sl);
       mmo1.Lines.Assign(sl);
     finally
       sl.Free;
     end;
    end;

  • Ваще имя (02.12.12 18:50) [7]

    > Вообще этот метод нельзя считать безопасным на 100%

    Как и любую другую операцию со строками, завершенными \нулем, естественно.

    Соответствует спецификации, а за корректность входных данных отвечает вызывающий.
  • DVM © (02.12.12 18:56) [8]

    > Ваще имя   (02.12.12 18:50) [7]


    > Как и любую другую операцию со строками, завершенными \нулем,
    >  естественно.

    в приведенный метод надо лишь добавить параметр Limit, значение которого мы можем получить из Tregistry.GetDataSize, и ограничить длину этим значением. Или использовать строки, как приведено выше, там Pos не выйдет за длину строки.
    Не вижу кстати ничего зазорного в однократном вызове StringReplace в данном случае, наезд на нее непонятен. Мы ж не гигабайты перемалывать будем.
  • DVM © (02.12.12 19:00) [9]

    > Ваще имя   (02.12.12 18:50) [7]


    > Соответствует спецификации, а за корректность входных данных
    > отвечает вызывающий.

    Да, но в реестр без проблем можно записать строку не имеющую в конце двух #0. Большинство уязвимостей в ПО  именно из таких ситуаций растут. Недаром MS понаделала функций для работы со строками, везде в которых добавлены лимиты.
  • Ваще имя (02.12.12 19:38) [10]

    > в приведенный метод надо лишь добавить параметр Limit

    Можно и добавить, но что делать с последней строкой, если мы ждем четко определенного формата MULTI_SZ, а нам передали какую-то фигню?


    > Мы ж не гигабайты перемалывать будем.

    Вариант с Insert и Delete как раз перемалывает данные просто так.


    > Большинство уязвимостей в ПО  именно из таких ситуаций растут

    Фактически, той уязвимости с перезаписью адреса возврата здесь нет. Возможны переполнение кучи и ошибка сегментации при чтении.
  • DVM © (02.12.12 20:06) [11]

    > Ваще имя   (02.12.12 19:38) [10]


    > Можно и добавить, но что делать с последней строкой, если
    > мы ждем четко определенного формата MULTI_SZ, а нам передали
    > какую-то фигню?

    Что нам передали, то и считаем, дальше Limit не выйдем. Код конечно надо будет подправить чуток. Размер данных у нас же всегда есть и он корректный, т.к. определяется без оглядки на #0.


    > Вариант с Insert и Delete как раз перемалывает данные просто
    > так.

    согласен, не лучший вариант.
  • Ваще имя (02.12.12 20:51) [12]

    > Размер данных у нас же всегда есть

    В том то и дело, что этот тип данных используется не только в реестре. И не всегда предоставляется длина блока.
    Таким образом, проверку надо делать вне процедуры, хотя, конечно L-версию написать не помешает.
 
Конференция "WinAPI" » Работа с REG_MULTI_SZ [D7, WinXP]
Есть новые Нет новых   [134430   +2][b:0][p:0.003]