Профессиональная разработка приложений с помощью Delphi 5
Часть 5. Исключительные ситуации и отладка приложений в Delphi 5
Исключительные ситуации и отладка приложений в Delphi 5
Работа с исключениями в блоке try…except…end
Обработчик исключений Application.HandleException
Опции проекта, влияющие на отладку приложений
Новые возможности отладки в Delphi 5
Breakpoint actions, breakpoint groups: новые свойства точек прерывания
Исключительные ситуации и отладка приложений в Delphi 5
При выполнении приложения часто приходится сталкиваться с ошибками, которые могут возникать как из-за невнимательности программиста (не выполнил какую-либо проверку), так и по независящим от него обстоятельствам (происходит запись в файл, а на диске кончилось место). В таких случаях говорят, что при выполнении кода возникла исключительная ситуация.
В зависимости от обстоятельств приложение должно по-разному реагировать в ответ на возникновение исключительной ситуации: в одних случаях оно может их «не замечать», то есть не показывать пользователю, а в других — оно просто обязано прервать свою работу. Обязательное требование — пользователь должен иметь возможность спасти несохраненные ранее данные. Приложение, которое ведет себя устойчиво в «плохих» условиях (если мало системных ресурсов, недостаточно места на диске или если пользователь совершает ошибки), именуется робастным. Робастное приложение можно создать только на языке программирования, который поддерживает обработку исключений. Предшественник Delphi — Borland Pascal — не поддерживал обработку исключений, но уже первая версия Delphi ее поддерживала.
Исключения как классы
При возникновении исключительной ситуации происходит генерация объекта, который описывает данное исключение. Этот объект (экземпляр класса) несет в себе информацию о типе исключения, например: деление на ноль, недостаточность системных ресурсов, переполнение переменной. Информация о типе исключения содержится в имени класса, то есть для исключений разного типа создаются экземпляры различных классов.
Кроме того, объект содержит текстовое описание исключения. Все исключения являются потомками класса Exception, у которого имеется свойство Message. В этом свойстве и содержится текстовая информация об исключении.
Помимо этой информации можно узнать адрес исключения, вызвав метод ExceptAddr. Если в приложении в момент вызова этого метода отсутствует объект исключения, то возвращается nil. Поэтому данный метод вызывают обычно в том месте приложения, где определенно присутствует объект Exception (except..end; Application.OnException).
Класс Exception интересен также тем, что он имеет восемь конструкторов. Можно создать объект из ресурсов, с параметрами, с информацией о том, к какому разделу помощи следует обращаться и т.д. Список классов, описывающих исключения, можно увидеть в Object Browser.
Распространение исключений
Рассмотрим следующий фрагмент кода: при щелчке кнопки вызывается метод OnClick, который, в свою очередь, вызывает метод Method1, а из него вызывается метод Method2. При нормальной работе приложения оно выполняет все методы, как это показано на рис. 1.
Синим цветом отмечен поток исполняемых команд процессору, лиловым — выход из вызываемого метода. Если же, например, в Method2 происходит исключение, то генерируется объект, являющийся экземпляром класса — потомка Exception, а дальнейшее выполнение кода прерывается (рис. 2):
На рис. 2 красным цветом показаны команды процессору, которые не были вызваны вследствие возникновения исключительной ситуации. Такая схема поведения верна для любого приложения: исключение попадает на уровень TApplication, затем пользователю показывается сообщение и разрушается объект исключения. Если, например, Method2 реализован в DLL, то приведенная на рис. 2 диаграмма все равно будет соблюдаться. Но поскольку исключения — это объекты, а реализация объектов является зависимой от языка, то при использовании DLL требуется особая осторожность. Даже если DLL реализована в Delphi (то есть структура объектов одинакова), то из-за разных менеджеров памяти исключение не может быть корректно разрушено в приложении. В этом случае немедленно возникает новое исключение и на пользователя обрушивается волна ошибок. Поэтому при реализации DLL все экспонируемые методы должны иметь ловушку исключения try…except…end, и ни в коем случае нельзя перевозбуждать это исключение вызовом метода Raise. В элементах управления ActiveX данная проблема решается при помощи директивы safecall, которая неявно устанавливает ловушку исключения на каждый экспонируемый метод.
Защищенные блоки
Delphi содержит два типа защищенных блоков, которые позволяют изменить описанное выше распространение исключения, — try…except…end и try…finally…end. В специальной литературе часто пишут, что отличие этих блоков друг от друга заключается в следующем: код между finally и end выполняется всегда, а код между except…end — только при наличии исключения. Однако это не совсем так.
Создадим новый проект, поместим на него компоненты TEdit и TButton и в обработчике события OnClick компонента TButton напишем небольшой код:
procedure TForm1.Button2Click(Sender: TObject); var N:integer; begin try ShowMessage('Entry Point'); N:=StrToInt(Edit1.Text); Caption:=IntToStr(N+1); ShowMessage('End block'); finally ShowMessage('Protected code'); end; ShowMessage('Continue'); end;
На рис. 3 ниже показан порядок появления диалогов при отсутствии исключения (верхний ряд; TEdit.Text – целое число) и при наличии (нижний ряд):
Диаграмма распространения исключения при наличии защищенного блока try…finally для примера с вызовом нескольких методов выглядит так, как показано на рис. 4:
Как и ожидалось, код между finally и end выполнился и при наличии исключения. Именно из-за обязательного выполнения кода в эту область помещают методы, связанные с возвращением ресурсов системе, а блок try…finally…end называется блоком защиты ресурсов.
Теперь в примере выше заменим слово finally на слово except и вновь исследуем вызов данной процедуры при отсутствии (верхний ряд) и наличии (нижний ряд) исключения (рис. 5):
Как и следовало ожидать, при отсутствии исключения код между except и end не выполняется, а при наличии — выполняется. Однако коренное отличие данного блока от блока try…finally…end — это продолжение выполнения кода после произошедшего исключения. Диаграмма выполнения приложения при наличии исключения в этом случае выглядит следующим образом (рис. 6):
По этой причине блок try…except…end именуется ловушкой исключения — он не позволяет исключению прервать выполнение вызванного метода.
И наконец, по поводу защищенных блоков можно сделать следующее замечание: по крайней мере в Delphi 3 нельзя было вставлять оператор Exit внутри защищенного блока. Это неминуемо приводило к исключению, причем в точке, далеко отстоящей от выхода из метода. Данная ошибка очень трудно поддается обнаружению.
Работа с исключениями в блоке try…except…end
Анализировать исключение необходимо в тех местах приложения, где присутствует объект — экземпляр потомка Exception. В Delphi 5 можно локализовать три такие области:
- Блок try…except…end между except и end.
- Application.OnException.
- По-видимому, имеется возможность реализовать специальные методы, которые будут вызываться всякий раз, когда происходит исключение операционной системы при выполнении приложений.
Поскольку исключение по возможности следует анализировать на максимально низком уровне, то в основном анализ исключений осуществляется в блоке try…except…end. Операторы между except и end выполняются только при наличии исключения, поэтому для этого места блока предусмотрен специальный синтаксис для анализа исключений.
Как уже упоминалось, имя класса исключения несет в себе информацию о его типе. Для анализа типа исключений в блоке try…except…end используется следующий синтаксис:
function TForm1.SaveFile(FN:string):integer; {Returns: 0 - saved OK; 1 – programmer has to repeat call of the procedure 2 - All bad, data are lost...} begin Result:=2; try {Save to file} Result:=0; except On E:EOutOfMemory do begin if MessageDlg(E.Message+'. Close another application. Retry saving?', mtError,[mbYes,mbNo],0)=mrYes then Result:=1; end; On E:EInOutError do begin if MessageDlg(E.Message+'. Free disk space. Retry save?', mtError,[mbYes,mbNo],0)=mrYes then Result:=1; end; On E:Exception do begin MessageDlg('You have lost data due to '+E.Message,mtError, [mbOK], 0); end; On E:EMathError do ShowMessage('You will never see this message'); end; end;
Оператор On E:OutOfMemory do… выполняется только в том случае, если объект исключения является классом EOutOfMemory или его потомком. После оператора do c переменной E можно обращаться как с экземпляром класса. Например, как в данном случае, — извлечь текстовое сообщение и показать в диалоге.
Если же класс исключения не относится к EOutOfMemory, то проверяется его принадлежность к классу EInOutError. Если же исключение не относится к данному классу, то проверяется на принадлежность к классу Exception. Поскольку любое исключение является потомком Exception, то данный код будет выполняться всегда, кроме классов EOutOfMemory и EInOutError, для которых будут выполняться описанные выше коды. Поэтому если используют оператор:
On E:Exception do begin
то его всегда помещают в конце кода. В частности, в данном примере вы никогда не увидите диалога с надписью 'You will never see this message' — это сообщение будет обработано ранее в секции On E:Exception do…
По причине того, что исключения анализируются по имени класса, исключения, создаваемые программистом, должны иметь свой уникальный класс. Но об этом мы поговорим в следующем разделе.
Директива Raise
Этот метод возбуждает исключение, то есть создает объект — экземпляр потомка Exception. В литературе различают использование метода Raise в коде приложения и в секции except..end. В коде приложения синтаксис метода raise выглядит, например, следующим образом:
Raise EConvertError.Create(‘Illegal value’);
После директивы указывается конструктор класса — потомка Exception. Немного по-другому используется этот метод в секции except..end — там просто вызывается эта директива:
Raise;
Однако, несмотря на кажущееся различие, смысл этой директивы один и тот же, а именно — генерация исключения. Следует вспомнить, что в секции except…end уже имеется объект Exception. Поэтому не требуется конструировать объект — он уже есть.
Raise вне блока try…except используется для генерации исключений, созданных программистами. И хотя можно вызвать конструктор класса Exception, в силу ранее объясненных причин это не делается. Поэтому программист обязан объявлять свои классы для собственных исключений:
EMyException=class(Exception)
end;
Чаще всего никакого кода больше не требуется. Это один из немногих случаев, когда необходимо просто объявить новый класс безо всяких изменений в переменных или методах. После объявления новый класс может быть использован для генерации исключений:
Raise EMyException.Create(‘Something bad happens…’);
Обработчик исключений Application.HandleException
Метод Application.HandleException принимает все исключения, для которых не расставлены ловушки в приложении. Если отсутствует обработчик события Application.OnException, то указанный метод показывает пользователю сообщение об ошибке и разрушает объект-исключение. Если имеется обработчик события Application.HandleException, то вместо показа диалога вызывается этот метод.
Обработчик события Application.OnException необходимо использовать в тех случаях, когда невозможно перехватить исключение ранее или когда такой перехват сопровождается написанием большого количества кода в разных частях приложения. В качестве параметров [U3] метод принимает Sender — экземпляр класса, где произошло исключение, и E:exception. Их можно проанализировать и выполнить соответствующие действия.
Удачным примером использования обработчика Application.OnException является работа с компонентом TOleContainer. Поставьте его на форму, запустите приложение на выполнение и дважды щелкните по OleContainer. Немедленно возникает исключение (рис. 7):
хотя представляется разумным при активации пустого OLE-контейнера вместо исключения вывести диалог для вставки OLE-объекта. OleContainer имеет метод DoVerb, который может вызываться различными способами: при получении фокуса ввода или двойном щелчке мыши. Замена исключения на диалог вставки объекта была бы обычным делом, если бы метод DoVerb был виртуальным или динамическим. В этом случае можно было бы создать класс-потомок и в нем переопределить метод DoVerb:
type TMyOleContainer=class(TOleContainer) public procedure DoVerb(Verb: Integer); override; end; … procedure TMyOleContainer.DoVerb(Verb:integer); begin try inherited DoVerb(Verb); except InsertObjectDialog; end; end;
Но метод DoVerb объявлен как статический – и сделать ничего нельзя. В данном случае можно написать обработчик события Application.OnException:
procedure TForm1.ShowException(Sender:TObject; E:exception); begin if (Sender is TOleContainer) and (E is EOleError) then TOleContainer(Sender).InsertObjectDialog else MessageDlg(E.Message,mtError,[mbOK],0); end; procedure TForm1.FormCreate(Sender: TObject); begin Application.OnException:=ShowException; end;
Вместо приведенного выше назначения события OnException при создании главной формы приложения в Delphi 5 можно воспользоваться компонентом ApplicationEvents. Легко убедиться, что если на форму поместить два компонента TOLEContainer, то диалог будет корректно вызываться для того из них, который был активирован.
Данный пример хорошо показывает также и то, как не надо писать распространяемые компоненты. Если какой-либо метод вызывается автоматически, в ответ на реакцию от потока сообщений Windows, и в нем могут произойти исключения, то он должен быть объявлен виртуальным или динамическим. Программист, который будет использовать данный компонент, может придумать что-нибудь получше, чем показывать пользователю диалоговое окно с сообщением об исключении.
Команда Assert
Этот метод в качестве параметра принимает значение логической переменной, которое всегда должно быть равно True. Синтаксис команды:
Assert(Table1.Active);
Если значение логической переменной равно False, то происходит остановка приложения с диагностикой Assertion Failure. Метод Assert следует использовать там, где значение логического выражения ни при каких обстоятельствах не должно равняться False. Возникающая при нарушении логического выражения диагностика напоминает вывод отладчиков некоторых трансляторов (рис. 8):
Широкое использование этого метода настоятельно рекомендуется в таких приложениях, которые ориентированы на коммерческое распространение. При этом бета-тестеры программы могут просто позвонить в головную организацию и сообщить, в каком модуле и в какой строке кода произошла ошибка. Чтобы отключить все проверки Assert в проекте, достаточно изменить один параметр в опциях.
Защита ресурсов в приложениях
При выполнении приложения часто необходимо брать ресурсы у операционной системы – создать экземпляр класса, выделить память, открыть файл и т.д. По правилам программирования в Windows, ресурсы следует возвратить операционной системе, если они больше не требуются для выполнения приложения. Мы уже знаем, что для защиты ресурсов необходимо использовать защищенный блок try..finally..end. Но если ресурсы не были зарезервированы, а программист пытается их освободить, то при этом также произойдут исключения, которых легко избежать, если придерживаться простых правил, например:
procedure TForm1.Button3Click(Sender: TObject); var P:pointer; N:integer; begin try N:=StrToInt(Edit1.Text); GetMem(P,N); {Some operations} finally FreeMem(P); end; end;
С виду все правильно: резервируются ресурсы GetMem, а их освобождение происходит в защищенном блоке. Но если исключение произойдет ранее — при конвертации строки в целое число, то память не будет выделена. При этом значение указателя P является случайным — так называемый дикий указатель (wild pointer). При вызове оператора FreeMem произойдет уже повторное исключение EAccessViolation. Поэтому перед резервированием системных ресурсов ссылки на них необходимо всегда инициализировать пустыми значениями, а перед освобождением проверять их. Корректный код для примера выше выглядит следующим образом:
procedure TForm1.Button3Click(Sender: TObject); var P:pointer; N:integer; begin P:=nil; try N:=StrToInt(Edit1.Text); GetMem(P,N); {Some operations} finally if Assigned(P) then FreeMem(P); end; end;
Для защиты файлов необходимо использовать директивы TFileRec и TTextRec:
procedure TForm1.Button4Click(Sender: TObject); var FText:TextFile; FBin:File; begin TTextRec(FText).Mode:=fmClosed; {Data initialization} TFileRec(FBin).Mode:=fmClosed; try {DoSomething1} AssignFile(FText,'C:\TEST.TXT'); Rewrite(FText); {DoSomething2} AssignFile(FBin,'C:\TEST.BIN'); Rewrite(FBin,1); {DoSomething3} finally if TTextRec(FText).Mode<>fmClosed then CloseFile(FText); if TFileRec(FBin).Mode<>fmClosed then CloseFile(FBin); end; end;
Опции проекта, влияющие на отладку приложений
Опции, которые будут описаны ниже, доступны из команды меню Projects/Options, закладка Compiler. Их грамотное использование позволяет существенно упростить отладку приложений и избежать возможных ошибок.
- Range Checking. При включенной этой опции проверяются диапазоны допустимых
значений целочисленных переменных, и если во время выполнения какая-либо переменная
выходит за пределы допустимых значений, то генерируется исключение. Рассмотрим
следующий фрагмент кода:
procedure TForm1.Button1Click(Sender: TObject); var B1,B2,B3:byte; begin ... B1:=200; B2:=200; B3:=B1+B2; ... end;
Диапазон допустимых значений для переменной типа byte 0..255, а в результате выполнения кода получим значение 400. При включенной опции Range Checking произойдет исключение, и программист легко обнаружит эту ошибку и внесет необходимые изменения в код. При отключенной же опции Range Checking исключения не будет, а значение переменной B3 будет содержать 8 самых младших битов результата.
Но главное, что данная опция проверяет диапазоны допустимых значений при работе с массивами. Рассмотрим следующий фрагмент кода:
procedure TForm1.Button1Click(Sender: TObject); var A:array[1..10] of integer; I,B:integer; begin ... B:=0; for I:=1 to 15 do begin if I<15 then B:=B+A[I] else A[I]:=B; end; ... end;
В цикле происходит чтение данных из элементов массива A[11]..A[14], память под хранение которых не была зарезервирована в стеке. С включенной опцией Range Checking немедленно происходит исключение, а с отключенной опцией код выполняется и происходит чтение данных из области памяти, занятой другими переменными. Ясно, что результат такого расчета является абсолютно бессмысленным. Но самые большие неприятности ожидают нас в данном коде при последнем проходе цикла (I=15). В этом случае данные записываются в несуществующий 15-й элемент массива, и эта команда будет успешно выполнена при отключенной опции Range Checking! Записанное значение изменяет значения других переменных и даже может попасть в область исполняемого кода. При этом приложение работает вполне нормально до тех пор, пока не происходит обращение к данной области памяти, а это может быть логически далеко от места возникновения ошибки. По указанной причине обнаружение таких ошибок очень затруднительно — участок кода, где она проявляется, уже может быть идеально отлажен.
Казалось бы, необходимо все время пользоваться включенной опцией Range Checking. Однако ее использование заметно увеличивает размер исполняемого файла, а если используются только целочисленные операции, то время выполнения замедляется более чем в 10 раз! Поэтому ее настоятельно рекомендуется использовать при создании и отладке приложений, а при передаче проекта конечному пользователю ее необходимо отключать.
- Overflow Checking. При включенной опции проверяются значения переменных с плавающей запятой на диапазон допустимых значений. Если, например, возвести в квадрат переменную, значение которой 1E20, а результат присвоить переменной типа single, то при включенной опции произойдет исключение, так как максимальное значение переменной типа single 3.4E38. Как и в предыдущем случае, при отладке приложения необходимо включать эту опцию, а при передаче приложения конечному пользователю — отключать.
- I/O checking. Borland Pascal — предшественник Delphi — не
поддерживал обработку исключительных ситуаций, и при программировании на этом
языке необходимо было делать массу проверок, перед тем как выполнить какое-либо
действие. Однако существует класс ошибок, потенциальное возникновение которых
часто невозможно обнаружить посредством проверок, — это ошибки ввода-вывода.
Например, никакая проверка не защитит от ситуации, когда пользователь во время
записи данных на дискету вынимает ее из дисковода. Поэтому в Borland Pascal
имелась функция IOResult, которая возвращала переменную типа integer. Если
не было ошибок ввода-вывода с момента предыдущего обращения к этой функции,
то возвращаемое значение равнялось 0. При наличии же ошибок возвращался ненулевой
результат. При возникновении ошибки ввода-вывода все операции по вводу-выводу
блокировались до вызова функции IOResult. Рассмотрим следующий фрагмент кода:
procedure TForm1.Button1Click(Sender: TObject); var F1,F2,F3:TextFile; S:string; begin IOResult; AssignFile(F1,'J:\TEST.TXT'); Rewrite(F1); Writeln(F1,'Test'); CloseFile(F1); AssignFile(F2,'C:\TEST.TXT'); Reset(F2); Readln(F2,S); CloseFile(F2); IOResult; AssignFile(F3,'C:\TEST.TXT'); Reset(F3); Readln(F3,S); CloseFile(F3); end;
Предположим, что устройство J: — это лазерный диск, на который нельзя записывать. Поэтому при выполнении оператора Rewrite(F1) происходит ошибка ввода-вывода, а значит, дальнейшие операции по вводу-выводу блокируются. Если файл C:\TEST.TXT существует и содержит значимую информацию, то команда Readln(F2,S) ничего не прочтет из этого файла, и только после вызова IOResult команда Readln(F3,S) вернет первую строку из файла C:\TEST.TXT.
Такой метод анализа ошибок ввода-вывода и сейчас применим в Delphi при отключенной опции I/O checking. При включенной же опции в случае ошибки ввода-вывода генерируется исключение, которое можно обнаружить и проанализировать. Этот метод — более прогрессивный, но многие программисты, работавшие ранее с Pascal, предпочитают вызов IOResult. В заключение следует добавить, что если выполняется групповая разработка проекта, то у всех участников эта опция должна быть либо включена, либо выключена.
- Complete Boolean Evaluation. Рассмотрим, каким образом в VCL Delphi
определяется наличие непустого списка, например, ширин столбцов в TStringGrid:
if Assigned(FWidths) and (FWidths.Count>0) then…
Данный код скомпилирован в режиме с отключенной опцией Complete Boolean Evaluation. В этом случае первоначально производится проверка истинности выражения Assigned(FWidths). Если оно истинно, то далее проверяется следующее условие после and, а при ложном значении дальнейшая проверка прекращается. Если же включена опция Complete Boolean Evaluation, то проверка происходит даже тогда, когда первая часть выражения равна False. При этом происходит попытка обращения к объекту с указателем nil, что немедленно приводит к генерации исключения.
Здесь существует категорическая рекомендация писать код, который работает с отключенной опцией Complete Boolean Evaluation. Написание такого кода означает, что если, например, происходит обращение к функции, которая возвращает результат типа Boolean, и ее необходимо всегда вызывать (кроме результата функция может выполнять и другие необходимые процедуры), то вызов этой функции должен быть первым после оператора if. Соответственно при получении кода со стороны (например, распространяемого как компонента) необходимо потратить некоторое время для выяснения, включить или выключить при компиляции кода опцию Complete Boolean Evaluation, и при необходимости внести требующиеся поправки.
- Debug Information. Отладчиком Delphi можно пользоваться только если данная опция будет включена. Однако при ее использовании увеличивается размер окончательного *.exe файла, хотя на время выполнения операций эта опция не влияет. Ее рекомендуется отключать перед передачей приложения конечному пользователю.
- Assertions. Эта опция включает или отключает описанную выше команду Assert. Поэтому данной командой можно смело пользоваться в коде повсеместно: для ее удаления не требуется убирать строки из кода, а достаточно лишь отключить эту опцию.
Отладка удаленных приложений
Для тестирования удаленного отладчика необходимо два компьютера, в которых:
- Имеется легальный IP-адрес. Желательно проверить связь утилитой Ping.
- Разрешен полный доступ (Share) к дискам.
Компьютер-сервер: запускается удаленный отладчик. Перед этим его отдельно следует установить с диска Delphi 5. Удаленный отладчик запускается при помощи команд: Start/Program Files/Borland Remote Debugger/Remote Debugger.
Компьютер-клиент: создается простейшее приложение, в котором могут происходить исключения или ставится точка прерывания. Перед компиляцией проекта выполняются следующие операции:
- Project/Options/Linker/Include Remote Debug — ставится флаг.
- Run/Parameters/Remote:
а) Remote Path — путь к приложению, как его «видит» сервер, то есть локальная директория на сервере, например D:\Project1.exe;
б) Remote Host — имя компьютера или его IP-адрес, например KLASEC04 или 10.10.10.64;
с) ставится флаг Debug Project On Remote Machine.
- Project/Options/Directories/Output Directory устанавливается путь к серверу, например \\KLASEC04\D.
Следует обратить внимание, что в подпункте 2а и пункте 3 устанавливаются ссылки на одну и ту же директорию, только в одном случае путь записывается с точки зрения локальной директории сервера, а в другом — с точки зрения «гостя».
После этого можно начинать тестирование удаленного отладчика.
Отладка нескольких проектов
Для отладки нескольких проектов необходимо, чтобы проекты были объединены в одну группу, причем они должны быть связаны, то есть выполняться одновременно. Пример такой ситуации — проект, компилируемый в приложение, и проект, компилируемый в DLL, причем приложение вызывает эту DLL. При запуске проектной группы можно поставить точки прерывания как в *.exe, так и в *.dll.
Новые возможности отладки в Delphi 5
В интегрированном отладчике Delphi появилось немало новых возможностей, которые имеет смысл рассмотреть более подробно.
Breakpoint actions, breakpoint groups: новые свойства точек прерывания
В прежних версиях Delphi точки прерывания были предназначены только для остановки процесса выполнения в режиме отладки. В Delphi 5 можно указать, какие именно действия (breakpoint actions) следует выполнить в момент достижения точки остановки: приостановить выполнение (как в прежних версиях Delphi); добавить текстовое сообщение в log-файл для регистрации событий отладчика (event log); записать в log-файл результат вычисления выражения, содержащего переменные отлаживаемого процесса (или вычислить выражение и результат никуда не записывать); сделать доступной или недоступной группу точек прерывания (о группах будет сказано ниже). Можно также одновременно выполнить несколько действий в одной точке прерывания (рис. 9).
Свойства точки прерывания теперь отображаются во всплывающем окне при подведении курсора мыши к выбранной точке.
Отметим, что точки прерывания могут быть объединены в группы, определенные пользователем, и эти группы могут быть активированы или деактивированы с помощью действия какой-либо другой точки прерывания.
В Delphi 5 появилась возможность ставить точки прерывания не только на выполнение какого-либо кода, но и на изменение данных. Рассмотрим фрагмент кода:
var K:integer=0; procedure TForm1.Button1Click(Sender: TObject); begin K:=K+1; Caption:=IntToStr(K); end; procedure TForm1.Button2Click(Sender: TObject); begin K:=K+2; Caption:=IntToStr(K); end;
Здесь объявлена глобальная переменная K. Предположим, что эта переменная может модифицироваться из различных методов данного проекта и что в результате запущенного процесса K имеет «нелегальное» значение. При традиционной отладке необходимо разыскать в коде все места, где происходит изменение этой переменной, и поставить там точки прерывания. Если таких мест — целая сотня, то программиста ожидает большая работа. В Delphi 5 можно поступить следующим образом:
- Запустить приложение из-под среды разработки.
- Поставить курсор в строку “K:integer=0;” и вызвать команду меню Run/Add Breakpoint/Data Breakpoint.
- В появляющемся диалоге в контроль Address ввести имя переменной K.
После выполнения этой операции появится точка прерывания при изменении данных. В данном проекте при нажатии первой кнопки управление процессом будет передано отладчику и курсор отладчика будет находиться в строке K:=K+1;. При нажатии же второй кнопки управление также передается отладчику, причем курсор будет находиться в строке K:=K+2.
Отметим, что точки прерывания на изменения данных могут быть установлены только после того, как процесс бы запущен, — их нельзя установить на этапе разработки. По окончании работы процесса точки прерывания на изменение данных становятся недействительными, то есть при следующем запуске они не срабатывают. При каждом новом запуске процесса их необходимо устанавливать заново, что создает некоторые неудобства при работе с точками прерываний на изменение данных.
Другие полезные нововведения
С помощью пункта меню Run/[U5] Attach to Process можно начать отлаживать любой из уже запущенных процессов, в том числе не имеющий отношения к Delphi. Процесс, подлежащий отладке, можно выбрать из соответствующего диалога (рис. 10):
С помощью выбора пункта меню Run/Run Until Return в процессе пошаговой отладки процедуры или функции можно заставить отладчик выполнить оставшийся код процедуры в нормальном режиме, что обеспечит немалую экономию времени.
При отладке многопоточных приложений можно устанавливать разные опции отладчика для различных потоков.
Помимо окна просмотра CPU, в Delphi 5 имеется также окно просмотра FPU (Floating Point Unit), позволяющее получить информацию о регистрах FPU, флагах и др.
Отметим также, что среда разработки Delphi 5 поддерживает операции drag-and-drop во время отладки. Например, из редактора кода можно перенести выражение в окно Watch List, после чего это выражение останется в соответствующем списке. Можно перенести данное выражение в Debug Inspector или на панель, содержащую дамп памяти в окне CPU, и получить его адрес.
К диалоговой панели Evaluate/Modify добавлено несколько новых кнопок, позволяющих загрузить выражение в Debug Inspector, вычислить или изменить его, поместить в Watch List (рис. 11):
Отметим также, что список исключений, которые можно игнорировать при использовании Delphi, пополнился исключениями, связанными с использованием библиотек ADO, внутренними исключениями VisiBroker, а также пользовательскими исключениями.
Заключение
Эта публикация завершает цикл статей, посвященных созданию профессиональных приложений с использованием компилятора Inprise Delphi. Данный цикл не претендует на полноту. Так, при создании приложений, работающих с базами данных, необходимы детальные знания механизма доступа к данным, при создании проектов для обработки результатов научных исследований — знания алгоритмов для работ с матрицами и т.д. Но, на наш взгляд, свободное владение представленным в данном цикле материалом необходимо любому программисту.
КомпьютерПресс 5'2001