вторник, 1 июня 2010 г.

Запись строк в реестр и Delphi

   Программируя в Delphi очень желательно записывать строковые данные в реестр в соответствии с документацией.



Набросаем такое тестовое приложение:
program Project20;

{$APPTYPE CONSOLE}

usesWindows, SysUtils;

var
  regValue, regData: WideString;
  cbSize, LastError: DWORD;
begin
  regValue := 'StringValue';
  regData := 'ab';
  
  cbSize := Length(regData) * SizeOf(WideChar);
  Writeln(Format('RegSetValueExW: size = %d bytes', [cbSize]));
  LastError := RegSetValueExW(HKEY_CURRENT_USER, PWideChar(regValue), 0, REG_SZ, PWideChar(regData), cbSize);
  if (LastError = ERROR_SUCCESS) then
    begin
      cbSize := 0;
      LastError := RegQueryValueExW(HKEY_CURRENT_USER, PWideChar(regValue), nil, nil, nil, @cbSize);
      Writeln(Format('RegQueryValueExW: size = %d bytes', [cbSize]));

      RegDeleteValueW(HKEY_CURRENT_USER, PWideChar(regValue));
    end
end.


   По правде говоря вышеприведенный пример показывает как не надо программировать. Но об этом чуть позже.


   Запустив программу на исполнение, получим такой вывод:
C:\>Project20.exe
RegSetValueExW: size = 4 bytes
RegQueryValueExW: size = 6 bytes

   Т.е. мы попросили записать 4 байта, а реально записано было 6! Мистика...
   На самом мы и должны были передать 6 функции RegSetValueExW в качестве значения параметра cbData - так написано в документации (MSDN)
...
cbData [in] 

   The size of the information pointed to by the lpData parameter, in bytes. 
   If the data is of type REG_SZ, REG_EXPAND_SZ, or REG_MULTI_SZ, cbData must 
   include the size of the terminating null character or characters.
...

   Но данный пример демонстрирует, что в некоторых случаях система может в некотором смысле "игнорировать" значение параметра cbData. В ходе небольшого "расследования" выяснилось, что это - дело рук RegSetValueExW. Ниже приведен ее псевдо-код (Delphi), которые поясняет суть происходящего (+ комментарии разработчика, найденные в старых исходниках Windows):
...
  //
  // Special hack to help out all the idiots who
  // believe the length of a NULL terminated string is
  // strlen(foo) instead of strlen(foo) + 1.
  //
  lpString := PWideChar(lpData);
  StringLength := cbData div SizeOf(WideChar);
  if (((dwType = REG_SZ) or (dwType = REG_EXPAND_SZ) or (dwType = REG_MULTI_SZ)) and
    (StringLength > 0) and (lpString[pred(StringLength)] <> #0)) then
    //
    // Do this under an exception handler in case the last
    // little bit crosses a page boundary.
    //
    try 
      if (lpString[StringLength] = #0) then
        inc(cbData, SizeOf(WideChar))        // increase string length to account for NULL terminator
    except
      // guess they really really did not want a NULL terminator
    end
...

   А если вспомнить, что строковые типы в Delphi (String/WideString) для совместимости с API всегда завершаются нуль-терминатором, то все становится на свои места :) Условие (lpString[StringLength] = #0) выполняется и система "искусственно" увеличивает cbData.


В реализации RegQueryValueExW тоже есть "Special hack"...
//
  // Special hack to help out all the idiots who
  // believe the length of a NULL terminated string is
  // strlen(foo) instead of strlen(foo) + 1.
  // If the last character of the buffer is not a NULL
  // and there is enough space left in the caller's buffer,
  // slap a NULL in there to prevent him from going nuts
  // trying to do a strlen().
  //

Комментариев нет:

Отправить комментарий