Под силовой отладкой (brute-force
debugging), отладкой "в лоб", понимаются методы отладки, основанные не
на возможностях отладчиков, а на трюках, родословная которых, пожалуй, восходит
к временам Атанасова и Лебедева, создававших первые ЭВМ по обе стороны
океана. При разработке программ
часто нет необходимости в полной отладке, просто хочется убедиться в том, что
какая-либо функция работает так, а не иначе (я весьма часто попадаю в подобные
ситуации, когда использую малознакомые функции API или плохо или вовсе
недокументированные методы объектов, и мне надо провести эксперимент, чтобы
выяснить, так ли я представляю работу функции). В этих случаях проще забыть об отладчике и просто
добавить пару строк кода для вывода информации. Для этого есть много путей, и о
некоторых из них будет рассказано ниже.
ПРЕДОСТЕРЕЖЕНИЕ: Большинство таких
методов - из серии "быстро и грязно", и я бы не рекомендовал заменять
ими описанные ранее методы тестирования и отладки.
Вывод отладочной информации в
форме. Один из способов
вывода такой информации - ее вывод непосредственно в форме. Обычно проще всего
создать компонент TLabel или подобный ему для непосредственного вывода
информации. В таком случае выведенная информация не потеряется даже при
перерисовке формы. Посмотрите на
описания функций ExtractFileDir и ExtractFilePath в справочной системе Delphi 4.
Я не берусь точно судить по документации о различии между этими функциями, но я
знаю, что мне делать. Я создаю новое приложение (выбрав пункт меню File/New
Application) и помещаю в главную форму элемент TButton и два элемента TLabel
(форма будет выглядеть так, как на рис. 2.20). Дважды щелкните на кнопке TButton и добавьте код к
обработчику события OnClick.
procedure
TFormI.ButtonlClick(Sender: TObject); begin Labell.Caption:=
ExtractFileDir(Application.ExeName); Label2.Caption:=
ExtractFilePath(Application.ExeName); end;
(Application. ExeName возвращает полное имя
файла приложения). Нажмите клавишу <F9> для компиляции и запуска приложения и щелкните на
кнопке. Теперь вам должно быть ясно, чем различаются эти две функции.
Недавно у меня возникла проблема с чужой DLL,
исходного кода которой я, естественно, не имел. Странным было то, что эта DLL
выцелела при загрузке и не освобождала большой фрагмент виртуальной памяти. Я
создал маленькое приложение, в котором после каждого щелчка на кнопке
сообщалось, сколько виртуальной памяти свободно. Мне хотелось сохранять
предыдущие результаты, а потому, я использовал элемент управления TMemo и
добавлял в него новые строки с результатами. Чтобы посмотреть, как это делается, создадим новое
приложение и разместим в форме элементы управления TMemo и TButton (и не забудем
установить значение свойства TMemo.ScrollBars равным ssVertical). Ваша форма будет выглядеть
так, как на рис. 2.21.
Рис. 2.20. Вывод отладочной
информации с использованием элемента управления TLabel.
Рис. 2.21. Вывод отладочной
информации в элемент TMemo
В обработчик события OnClick добавьте следующий код.
procedure
TFormI.ButtonlClick(Sender: TObject); var MemStat: TMemoryStatus;
begin VirtualAlloc(nil, 1000000, MEM_RESERVE,
PAGE_READWRITE);// 1 MemStat.dwLength:= SizeOf(TMemoryStatus); //
2
GlobalMemoryStatus(MemStat); // 3
Memol.Lines.Add(IntToStr(MemStat.dwAvailVirtual)); // 4
end;
Не беспокойтесь о деталях вызова API-функции
VirtualAlloc в строке 1. Здесь ее вызов требует от операционной системы
зарезервировать миллион байтов памяти для дальнейшего использования. API-функция
GlobalMemoryStatus возвращает информацию об использовании памяти приложением и
системой в целом. Информация возвращается в переменной MemStat, представляющей
собой запись типа TMemoryStatus. Перед вызовом GlobalMemoryStatus вы передаете
системе информацию о размере структуры, как в строке 2, а затем вызываете
функцию (строка 3) и выводите информацию в
TMemo в строке 4. Скомпилируйте и
запустите программу, щелкните несколько раз на кнопке - и увидите, что
виртуальная память уменьшается примерно на один мегабайт при каждом щелчке, как
и ожидалось. На рис. 2.23 показана форма после нескольких щелчков на
кнопке. Используя этот метод (без
вызова VirtualAlloc), я выяснил, что на самом деле DLL затребовала около 60
Мбайт (!) виртуальной памяти при загрузке и не освободила ее. Даже притом, что
Windows 95 предоставляет каждому приложению
двухгигабайтовое адресное пространство, потерю 60 Мбайт сложно
проигнорировать...
Рис. 2.22. Вывод в элемент
TMemo информации о количестве доступной виртуальной памяти.
ShowMessage Кроме вывода информации в форму, можно
воспользоваться модальным диалоговым окном. Принципиальное отличие этого метода,
в первую очередь, состоит в том, что модальное диалоговое окно останавливает
выполнение программы, пока вы его не закроете. Таким образом, у вас
имеется достаточно времени, чтобы прочесть и
осмыслить полученную информацию.
Процедура ShowMessage (из модуля Dialogs) идеально подходит для этой цели Она
позволяет вывести строку любой длины в простом модальном диалоговом окне. Вам
только следует создать строку для вывода и передать ее процедуре (можно также
использовать MessageDIg, но в нем слишком много шашечек и бантиков, которые
требуют немалых усилий для достижения того же эффекта). ShowMessage получает в качестве параметра одну
строку, для создания которой я предпочитаю использовать функцию Format, она
идеально подходит для этого, будучи одновременно простым и мощным инструментом в
умелых руках. Рассмотрим простой
пример. Используем этот метод для вывода информации, получаемой от уже
использовавшейся функции GlobalMemoryStatus. Создадим новое приложение и поместим TButton в
основную форму. Обработчик события OnClick будет выглядеть следующим
образом.
procedure
TFormI.ButtonlClick(Sender: TObject); var MemStat: TMemoryStatus;
begin MemStat.dwLength:=
SizeOf(TMemoryStatus); GlobalMemoryStatus(MemStat); with MemStat do
ShowMessage(Format('Memory load: %d%%'#13 + 'Total physical: %d'#13+'Available
physical: %d'#13 + 'Total page file: %d'#13 + 'Available page file: %d'ftl3
+ 'Total
virtual: %d'#13 + 'Available virtual: %d', [dwMemoryLoad, dwTotalPhys,
dwAvailPhys, dwTotalPageFile, dwAvailPageFile, dwTotalVirtual,
dwAvailVirtual]));
end;
Заметьте, что я внес в строку несколько
символов #13 (ASCII-символ возврата каретки). Это позволяет разбить строку при
выводе на несколько строк, что существенно облегчает чтение информации. На рис
2.23 показано, что получится после запуска программы и щелчка на кнопке.
Судя по результатам Memory load и Available
physical, представленным на рисунке, мне стоит всерьез подумать о наращивании
памяти своего компьютера.
Рис 2.23 Использование функции
ShowMessage для вывода отладочной информации.
Вывод на консоль
Еще один способ вывода отладочной информации- вывод на консоль с использованием
процедур Write и WriteLn. Вы можете конвертировать проект в консольное
приложение, например, выбрав соответствующую опцию (команду Project/Options,
вкладку Linker и опцию Generate Console Application) или поместив директиву
$APPTYPE CONSOLE в главный DPR-файл. Учитывая, что ваше приложение- не
консольное, воспользуйтесь возможностями условной компиляции и используйте
директиву $APPTYPE как показано ниже:
{$ifdef Debug}
{$APPTYPE CONSOLE}
{$endif}
Теперь вывод на консоль будет осуществляться
только в отладочной версии вашего приложения. Если вы попытались использовать функцию Write или
WriteLn и получили сообщение об ошибке I/O Еггог, значит, вы забыли сделать
проект консольным приложением.
Обратите внимание, что здесь применяется тот же код, что и раньше, но теперь мы
используем вывод на консоль вместо ShowMessage. Убедитесь, что вы создаете
консольное приложение, и измените обработчик так, как показано ниже.
procedure
TFormI.ButtonlClick(Sender: T0bject); var MemStat: TMemoryStatus;
begin MemStat.dwLength:=
SizeOf(TMemoryStatus); GlobalMemoryStatus(MemStat); with MemStat do begin WriteLn(Format('Memory load:
%d%%',[dwMemoryLoad])); WriteLn(Format('Total physical:
%d',[dwTotalPhys])); WriteLn(Format('Available physical:
%d',[dwAvailPhys])); WriteLn(Format('Total page file:
%d',[dwTotalPageFile])); WriteLn(Format('Available page file:
%d',[dwAvailPageFile])); WriteLn(Format('Total virtual:
%d',[dwTotalVirtual])); WriteLn(Format('Available virtual:
%d',[dwAvailVirtual])); end; end;
Результат показан на рис. 2.24.
Рис. 2.24. Использование
консоли для вывода отладочной информации.
Опытные пользователи Pascal заметят, что функция Format использовалась
там, где это не было необходимо (WriteLn имеет свои возможности форматирования).
Однако я везде использую Format как мощный инструмент; кроме того, используя
везде одну лишь функцию Format, я избавляюсь от необходимости помнить два набора
правил форматирования.
Запись в Log-файл
Запись отладочной информации в файл протокола (Log-файл) существенно отличается
от предыдущих приемов записи, так как это уже нельзя назвать "быстро и
грязно". Это отличная технология, которую можно использовать в любом
приложении. Запись в файл протокола
выполняется так же, как и вывод на консоль, но вместо WriteLn (. . . )
используется WriteLn (LogFile, . . . ), где LogFile - имя файловой переменной
типа TextFile. Надо также не забывать открывать этот файл в начале работы
приложения и закрывать - в конце. Проще всего этого добиться, поместив
соответствующий код в свой модуль, который благодаря возможности условной
компиляции подключается только в отладочной версии вашей программы.
Листинг 2.1. Модуль протоколирования
отладочной информации.
unit uLoq; interface procedure Log(S: Strings-implementation
uses Windows,
SysUtils;
var LogFile:
TextFile;
LogCriticalSection: TRtlCriticalSection; procedure Log(S: String);
var SystemTime: TSystemTime;
FileTime:
TFileTime;
begin
GetSystemTime (SystemTime) ; SystemTimeToFileTime(SystemTime, FileTime) ;
EnterCriticalSection(LogCriticalSection); WriteLn(LogFile, Format('%s %.8x%.8x
%5',
[FormatDateTime('yy.mm.dd hh.inm.ss'. Now), FileTime.dwHighDateTime,
FileTime.dwLowDateTime, S])) ; LeaveCriticalSection(LogCriticalSection) ;
end; procedure Startup; var FileName: String;
begin
InitializeCriticalSection(LogCriticalSection); FileName := Format("Log file for
%s at %s.txf, [ParamStr(O), DateTimeToStr(Now)]) ; while Pos(':', FileName) 0
do
FileName[Pos(':', FileName)] := '.'; while Pos('/', FileName) 0
do
FileName[Pos('/', FileName)] := '-'; while Pos('\', FileName) 0
do
FileName[Pos('\', FileName)] := '.'; AssignFile(LogFile,
FileName);
Rewrite(LogFile) ;
end; procedure
Shutdown;
begin
CloseFile(LogFile) ; DeleteCriticalSection(LogCriticalSection) ;
end; initialization Startup;
finalization
Shutdown; end.
Этот модуль сам создает, открывает и
закрывает файл протокола. Имя файла создается с учетом имени приложения и
текущих даты и времени, что исключает возможность записи информации поверх
существующего файла. Для использования модуля условно включите его, как показано
ниже.
unit MyUnit; interface uses ($ifdef Debug}
uLog, {$endif) Windows, Messages, SysUtils, Classes, . .
.
Затем используйте его приблизительно
так.
{$ifdef Debug)
Log(Format('Entering the Foo
procedure; Bar = %d',[Bar])); {$endif}
He забывайте размещать вызов между
директивами условной компиляции, иначе при компиляции коммерческой версии
возникнет ошибка. Модуль uLog
обладает двумя интересными и полезными свойствами. Во-первых, каждая запись в
файл предваряется информацией о дате, времени и шестнадцатеричным числом,
соответствующим системному времени в миллисекундах. Эта информация может быть
весьма полезной, особенно когда вы хотите отследить последовательность событий в
приложении. Во-вторых, модуль использует критические разделы (critical section),
что обеспечивает доступ к файлу только одной подзадачи в один момент
времени. На рис. 2.25 показан
типичный файл протокола в программе Notepad.
Рис. 2.25. Пример отладочного
файла протокола
Как правильно использовать файл протокола? Какую информацию в него
записывать? Сколько программистов, столько и ответов на эти вопросы. Лично я
предпочитаю придерживаться золотой середины между "записывай все" и
"записывай только то, что отлаживаешь".
|