Заметки
Куда выводить отладочные сообщения?

:: Меню ::
:: На главную ::
:: FAQ ::
:: Заметки ::
:: Практика ::
:: Win API ::
:: Проекты ::
:: Скачать ::
:: Секреты ::
:: Ссылки ::

:: Сервис ::
:: Написать ::

:: MVP ::

:: RSS ::

Яндекс.Метрика


В процессе разработки часто нет необходимости в полной отладке, просто хочется убедиться в том, что какая-либо функция или участок кода работает так, а не иначе. В таких ситуациях проще забыть об отладчике и просто добавить пару строк кода для вывода информации. Для этого есть много путей, некоторые из которых будут рассмотрены кратко (в силу их очевидности), а на одном остановимся более подробно.

Вывод отладочной информации на форме

В этом случае на форму приходится специально добавлять компоненты для вывода отладочной информации (Memo, Label и т.п.). Когда все начинает работать как надо, эти компоненты уже не нужны, и они просто удаляются (вместе с кодом, выводящим отладочную информацию).

Неудобство очевидно – в процессе разработки необходимость добавлять/удалять компоненты может возникать неоднократно.

ShowMessage

Этот вариант заключается в использовании модального диалогового окна. Принципиальное отличие этого метода состоит в том, что модальное диалоговое окно останавливает выполнение программы до тех пор, пока не будет закрыто.

С одной стороны у нас имеется достаточно времени, чтобы прочесть и осмыслить полученную информацию, с другой при многократном вызове (например, при использовании потоков) может быть создано много окон, которые не только будут мешать друг другу, но их еще нужно будет постоянно закрыть, что не очень удобно.

Запись в Log-файл

Запись отладочной информации в файл существенно отличается от предыдущих способов, так как мы не увидим отладочное сообщение непосредственно в момент его возникновения. Файл нужно постоянно открывать, чтобы видеть новые записи, что может доставлять существенные неудобства.

Вывод на консоль

Самый удобный (с моей точки зрения) способ, лишенный всех описанных выше недостатков – отдельное окно, нет “лишних” компонентов на форме, не прерывается работа приложения. Само окно создается очень просто – вызовом функции AllocConsole, а вывод на консоль осуществляется с использованием процедур Write и Writeln.

И вот тут мы подходим к самому интересному.

Отладка с помощью Process Monitor


Process Monitor – утилита от Марка Руссиновича. Мало кто знает, что Process Monitor можно использовать в качестве отладочного инструмента. Process Monitor поддерживает API, позволяющим разработчикам создавать события вывода отладчика, добавляемые в поток событий с пользовательским комментарием. Например, можно включать пользовательский вывод отладчика в трассировку при вызове и завершении функций, чтобы сопоставить эти события с активностью файловой системы, реестра и другими событиями. Любые процессы, даже работающие с низкими привилегиями, могут передать через драйвер Process Monitor UNICODE строку длиной до 2048 знаков. Разберемся, как это делается, и начнем с настройки фильтров Process Monitor.


Здесь нужно добавить условие "Operation", связку "is" со значением "Debug Output Profiling".


Для просмотра событий вывода отладчика надо активировать переключатель "Show Profiling Events" ("Показать события трассировки") на панели инструментов, детализацию фильтров по другим параметрам лучше отключить.


Пример взаимодействия с Process Monitor приведен в книге Марка Руссиновича и Аарона Маргозиса "Утилиты Sysinternals. Справочник администратора" (2012 год). В оригинале код написан на C, однако его перевод на Delphi не вызывает никаких трудностей.

procedure DebugOut(Msg: string);
{$DEFINE USE_CONST_VALUE}

  {$IFNDEF USE_CONST_VALUE}
  function CTL_CODE(DeviceType, Func, Method, Access: Cardinal): Cardinal;
  begin
     Result := (DeviceType shl 16) or (Access shl 14) or (Func shl 2) or Method;
  end;
  {$ENDIF}

var
  hDevice: THandle;
  BytesReturned: DWORD;
  {$IFNDEF USE_CONST_VALUE}
  IOCTL_EXTERNAL_LOG_DEBUGOUT: Cardinal;
  {$ENDIF}
const
  FILE_DEVICE_PROCMON_LOG = $00009535;
  FUNC = $00000081;
  {$IFDEF USE_CONST_VALUE}
  IOCTL_EXTERNAL_LOG_DEBUGOUT = Cardinal((FILE_DEVICE_PROCMON_LOG shl 16) or
    (FILE_WRITE_ACCESS shl 14) or (FUNC shl 2) or METHOD_BUFFERED);
  {$ENDIF}
begin
   // Открыть драйвер Process Monitor
   hDevice := CreateFile(PChar('\\.\Global\ProcmonDebugLogger'),
      GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or
      FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil,
      OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

   if hDevice <> INVALID_HANDLE_VALUE then
   begin
      {$IFNDEF USE_CONST_VALUE}
      IOCTL_EXTERNAL_LOG_DEBUGOUT := CTL_CODE(FILE_DEVICE_PROCMON_LOG,
         FUNC, METHOD_BUFFERED, FILE_WRITE_ACCESS);
      {$ENDIF}
      // Отправить сообщение драйверу Process Monitor
      DeviceIoControl(hDevice, IOCTL_EXTERNAL_LOG_DEBUGOUT,
         PChar(Msg), Length(Msg) * SizeOf(Char), nil, 0, BytesReturned, nil);
      CloseHandle(hDevice);
   end;
end;

Так как пример демонстрационный я решил показать разные подходы к формированию I/O control code (IOCTL), и совместить их в одной процедуре. Какой из подходов будет использован, зависит от директивы USE_CONST_VALUE. Если директива объявлена, значение константы вычисляется на этапе компиляции приложения, в противном случае значение каждый раз генерируется в runtime. Разумеется, в реальном приложении нужно использовать какой-нибудь один из подходов (на своё усмотрение).

Для версий Delphi ниже 2009 (не поддерживающих Unicode) процедура будет немного отличаться в части определения типов.

procedure DebugOut(Msg: WideString);

  {...}

var
  {...}
const
  {...}
begin
   hDevice := CreateFileW(PWideChar(WideString('\\.\Global\ProcmonDebugLogger')), ...);

   if hDevice <> INVALID_HANDLE_VALUE then
   begin
      {...}

      DeviceIoControl(hDevice, IOCTL_EXTERNAL_LOG_DEBUGOUT,
         PWideChar(Msg), Length(Msg) * SizeOf(WideChar), nil, 0, BytesReturned, nil);
      CloseHandle(hDevice);
   end;
end;

На этом все, удобной отладки!

.: Пример к данной заметке :.


При использовании материала - ссылка на сайт обязательна