    ГЛАВА  5 

    Изучите ошибки выполнения программы 

    Здесь речь идет об ошибках, возникающих при'Жтолнении программы, и о том, 
как их превратить в исключительные ей"" туации. 

    Исключительные ситуации 

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

    Классы исключительных ситуаций 

    Ознакомьтесь с классами исключительных ситуаций, в том числе с теми, 
которые создадите вы сами. 

    begin 
    sStrVal := 'Delphi Rock '; 
    cVal := CharFromString(sStrVal,12); 
    if cVal <> #0 then 
    begin 
    if cVal <> 's' then 
    _if_ InsertChar ( ' s ' , sStrVal, 12) then 
    ShowMessage(sStrVal) else 
    Exit; end 
    else Exit/end; 

    В процедуре UseFunctions используется несколько гипотетических функций. 
Функция CharFromString возвращает символ из указанной позиции строки или 0, 
если номер позиции больше длины строки. Функция InsertChar возвращает True, 
если удается поместить данный знак в указанную позицию указанной строки (иначе 
она возвращает False). Для полной обработки ош ибок в примере необходимо 
проверить значения, возвращаемые обеими функциями. Если происходит ошибка, 
процедура UseFunctions прекращает работу. Если же ошибки нет, выводится 
диалоговое окно с сообщением "Delphi Rock". 
    А что если функции CharFromString и InsertChar вместо возврата значений, 
свидетельствующих об ошибке, будут инициировать исключения? Тогда можно будет 
вывести обработку ошибок из основного кода, к тому же текст программы станет 
более понятным. Рассмотрим модифицированную процедуру UseFunctions. 

    procedure UseFunctions; var cVal: char; 
    sStrVal: string; begin try 
    sStrVal := 'Delphi Rock '; 
    cVal := CharFromString(sStrVal,12); 
    if cVal <> 's' then 
    InsertChar('s',sStrVal,12) ; ShowMessage(sStrVal); except Expend; end; 

    Поскольку теперь в функциях CharFromString и InsertChar применена 
обработка исключительных ситуаций, можно писать процедуру в предположении, что 
она будет выполняться сверху донизу без ошибки. Если произойдет ошибка, будет 
выполнен переход к оператору Exit. Обратите внимание, что в большинстве 
подпрограмм RTL Delphi применяются исключительные ситу ации, а не "магические" 
значения возвращаемых значений. Вообще-то, в Delphi трудно написать надежную 
программу, не применяя в той или иной форме обработки исключительных ситуаций. 
Даже подпрограммы, которые из Delphi обращаются к функциям интерфейса 
программирования приложений (Application Programming Interface— API) в OS, 
преобразуют ошибки в иск лючительные ситуации. К тому же в Delphi используется 
архитектура исключительных ситуаций Windows 95 и NT, что позволяет 
перехватывать ошибки, возникающие в самой OS или в используемых ею 
устройствах. В остальной части данной главы будут более подробно рассмотрены 
исключительные ситуации и их обработка. 

    Понятие исключительной ситуации 

    Исключительная ситуация — это изменение условий выполнения программы, 
вызывающее прерывание или полное прекращение выполнения. Она может произойти в 
любой момент. Источников исключительных ситуаций может быть сколько угодно, и 
все они прямо или косвенно связаны с кодом программы. Некоторые из возможных 
источников перечислены ниже. 
    •    Компоненты, включенные в программу. 
    •    Драйверы баз данных, например любые связи SQL, а также BDE и ODBC. 
    •    Операционная система. 
    •    Драйверы устройств, например видеодрайвер или коммуникационные 
драйверы Unimodem. 

    Глава 5. Обработка исключительных ситуаций 105 

    •   Сетевые динамические библиотеки, системы управления и антивирусные программы. 
    •    Собственные библиотеки RTL или VCL Delphi. 
    Когда возникает исключительная ситуация, то, независимо от ее источника, 
программа узнает о ней. Компилятор создает код, который знает, как прерывать 
выполнение программы. После прерывания стек вызывающего блока начинает 
"выталкивать" данные. В результате этого теряются параметры и локальные 
переменные, распределенные в стеке. 
    В стеке содержатся только локальные переменные и параметры, переданные 
функции. Если действует уста-новленное по умолчанию условие быстрого (или 
регистрового) вызова, некоторые параметры могут переда-*    ваться в 
расширенные регистры процессора. т,я>                                                                                                                         
   Если один из параметров функции — указатель на динамическую переменную, 
созданную с помощью процедуры New (), GetMem () или HeapAlloc (), то из стека 
будет "вытолкнуто" всего четыре байта, занимаемых самим указателем. Цоэтому 
следует всегда иметь наготове схему освобождения динамической памяти при 
возникновении исключительной ситуации. 
    Процесс "выталкивания" данных из стека продолжается до тех пор, пока не 
произойдет одно из двух событий. 
    1.   Выполнение программы дойдет до конструкции try. . . except. 
    2.   Управление будет передано глобальному обработчику исключительных ситуаций. 
    Оба варианта представляют собой то, что принято называть обработкой 
исключительных ситуаций. При компиляции программы компилятор генерирует 
определенный код. Этот процесс иногда называют магией компилятора, и часть 
этой магии состоит в том, чтобы в коде были фрагменты, предназначенные для 
обработки возникающих исключительных ситуаций, в том числе 
    и для управления стеком. Кроме того, создается объект обработки ошибки, 
остающийся в памяти до тех пор, пока он не будет оттуда удален. Процесс 
удаления объекта обработки ошибки называется обработчиком исключительной 
ситуации, а сам объект — объектом обработки исключительной ситуации. 
    Тем, кто знаком с конструкцией try. . .except, вероятно, известна и 
конструкция try. . . finally. Обратите внимание, что последняя не удаляет из 
памяти объект обработки исключительной ситуации. Отсюда следует, что не 
происходит и самой обработки. Различия между этими двумя конструкциями 
подробнее рассматриваются в следующих разделах данной главы. 

    Определение обработчика исключительных ситуаций по умолчанию 

    В любом проекте Delphi, основанном на VCL, имеется глобальная переменная 
Application. Это экземпляр класса TApplication, определенного в модуле Forms и 
объединяющего в себе все самосоздающиеся формы программы. Как почти во всех 
классах Delphi, в TApplication есть ряд методов и свойств, к которым можно 
обращаться во время выполнения программы. 
    При подключении к проекту модуля Forms косвенно подключается и модуль 
Controls, входящий в раздел Uses модуля Forms. Инициализационный раздел модуля 
Controls вызывает процедуру InitControls, которая инициализирует экземпляр 
Application. Исходя из этого, нет никакой необходимости программировать 
инициализацию и удаление экземпляра Application; дост аточно указать в разделе 
Uses модуль Forms. 
    Объект Application выполняет множество функций. Одна из них — обеспечение 
по умолчанию глобального механизма обработки исключительных ситуаций. Узнав, 
что произошло исключение, объект Application генерирует событие OnExcegtion. 
    Т~"~ ~ 
    ™ ~ 
    ' ~~ 

    Событие OnException 

    Вам, вероятно, уже доводилось видеть объект Application в 
инициализационном разделе DPR-файла. В новом проекте по умолчанию 
устанавливается оператор вызова метода Initialize. Этот метод применяется для 
установки автоматической регистрации и дерегистрации объекта Automation OLE 
(Object Linking and Embedding — связывание и 

    106 Часть I. Основные положения 

    внедрение объектов) в программах, использующих средства OLE. Более 
подробно об OLE и Automation рассказывается в главе 18, "Работа с OLE, серверы 
документов и элементы управления ActiveX". Если средстаа_ОЬЕ_не применяются, 
этот onepaTjpjj\jojaw_y.3ajTHTbtПосле метода Initialize вызывается метод 
CreateForms, который создает основную "$орму1Гсвязыва ет ее с образцом объекта 
Application. Каким же образом объект Application обрабатывает исключительные 
ситуации? Для этого у него есть свойство OnException. 
    Когда на каком-либо уровне программы возникает исключительная ситуация, 
объект Application выполняет метод HandleException (если, конечно, не 
определен другой процесс обработки исключительной ситуации). Метод 
Нап-dleException, в свою очередь, выполняет весь код, определяемый свойством 
OnException. Если же этому свойству код не присвоен, вызывается метод 
ShowException. Последний обрабатывает исключительные ситуации по умолчанию в 
случаях, когда генерируется событие OnEcxeption. 
    Если код для свойства OnEcxeption задан, он должен содержаться внутри 
метода. Формат этого метода должен соответствовать типу TExceptionEvent. В 
модуле Forms этот тип объявлен следующим образом: 
    TExceptionEvent  = procedure   (Sender:   TObject;   EInstance:   
Exception)   of object; Другими словами, тип TExceptionEvent — это процедура, 
являющаяся объектом. 

    Назначение свойству Application.OnException собственного обработчика 

    Для того чтобы назначить свойству OnEcxeption объекта Application 
собственного обработчика, надо выполнить несколько действий. Во-первых, 
создать новый проект. К счастью, все элементы обработчика исключительных 
ситуаций можно разместить в DPR-файле проекта. Доступ к DPR-файлу 
осуществляется с помощью команды Project Source меню View. Тексты програ мм, 
показанные в листинге 5.1 и других листингах данной главы, можно найти на 
дополнительно распространяемой дискете, в каталоге \UDELPHI3\CHP5\. 

    Листинг 5.1. \UDELPHI3\CHP5\PROJECT1. DPR. Назначение глобального 
обработчика исключительных ситуаций свойству OnException в DPR-файле 

    program Project!; uses 
    SysUtils, 
    Forms, 
    Unitl in 'Unitl.pas' {Forml}; 
    type 
    TGlobalExHandler = class public 
    procedure HandlerProc(Sender: TObject; EInstance: Exception); end; 
    procedure TGlobalExHandler.HandlerProc(Sender: TObject; EInstance: 
Exception); begin 
    {Здесь происходит обработка исключительной ситуации.} end; 
    var 
    GlobalExHandler: TGlobalExHandler; 
    {$R *.RES} 
    begin 
    GlobalExHandler := TGlobalExHandler.Create; 
    Application.OnException := GlobalExHandler.HandlerProc; 
    Application.Initialize; 
    Application.CreateForm(TForml, Forml); 
    Application.Run; end. 

    Глава 5. Обработка исключительных ситуаций 107 

    В начале листинга 5.1 происходит подключение в разделе Uses модуля 
SysUtils. Этот модуль содержит объявление класса Exception, а также нового 
класса TGlobalExHandler с единственным методом HandlerProc, присваиваемым 
свойству OnException. Как уже было сказано, метод HandlerProc, по существу, не 
выполняет никаких действий. Однако его можно заменить кодом, разработанным для 
конкретной программы. Предположим, код, определенный для HandlerProc, 
оценивает ошибку как фатальную. Тогда вызывается процедура, которая 
освобождает всю глобально распределенную память, закрывает файлы и 
предупреждает пользователя об ошибке, — в общем, делает все, чтобы завершить 
выполнение программы подобающим образом. 
    За определением HandlerProc следует объявление глобальной переменной 
GlobalExHandler типа TGlobalExHandler. Значение этой переменной присваивается 
первым оператором выполняемого блока DPR-файла. После этого сам метод 
HandlerProc присваивается свойству Application. OnException. 
    Если во время выполнения программы возникает исключительная ситуация, не 
обрабатываемая никаким иным образом, инициализируется объект обработки 
исключительной ситуации, который остается в памяти до тех пор, пока не будет 
вызван метод HandleException объекта Application. Этот метод, в свою очередь, 
вызывает выполнение кода, присвоенного свойству Application.OnEcxeption. 
В предыдущем примере свойству Application. OnEcxeption был присвоен метод 
GlobalExHandler. HandlerProc. Именно он и будет выполнен. 

    На заметку 

    Формат метода, присваиваемого свойству OnEcxeption, должен соответствовать 
типу 

    TException-Event: TExceptionEvent  =  procedure   (Sender:   TObject; ^                      
    EInstance:   Exception)   of object; 

    Второй параметр — это экземпляр объекта обработки исключения типа 
Exception или одного из его потомков. С помощью параметра EInstance можно 
получить доступ к членам объекта обработки исключительных ситуаций, 
объявленных как public или published. Из этих членов можно получить 
дополнительную информацию о самой исключительной ситуации. Лучший пример 
    — свойство Message. С его помощью можно получить строку сообщения об 
ошибке, связанную с данной исключительной ситуацией, например: 
    procedure  TGlobalExHandler.HandlerProc(Sender:   TObject;   EInstance: 
Exception); begin 
    MessageDlg(EInstance.Message,   mtlnformation,    [mbOK],   0); {В разделе 
 Uses  надо подключить  модуль   Dialogs.} end; 
    В Delphi определены две конструкции try для обработки исключительных 
ситуаций в том месте, где они возникают. С их помощью можно определить 
источник ошибки и точный ее тип, в также получить адрес инициализированного 
экземпляра объекта обработки исключительной ситуации. 

    Программирование исключительных ситуаций 

    Встроенный процесс обработки исключительных ситуаций с обращением к методу 
ShowException состоит в открытии диалогового окна с сообщением о типе 
возникшей исключительной ситуации. Дрогтзаммная.обработка исключительных 
ситуаций должна уметь закрывать файлы, освобождать динамически распределенную 
память и вызывать все^е^-допрад^шш?^р5цёдурь1дёйнициа лизации. 
    Для профаммной обработки исключительных ситуаций можно уст^новить_новь1й 
обработчик по умолчанию. Крометого,, для обработки исключительных ситуаций в 
Delphi определены две конструкции try. Если при их использовании возникает 
исключительная ситуация, с которой не справляется программный код, можно 
вызвать обработчика по умолчанию. 

    Отладка обработчиков исключительных ситуаций в IDE 

    Интегрированная среда разработчика (Integrated Development Environment— 
IDE) Delphi содержит мощный набор средств отладки. Можно включать или 
отключать встроенный отладчик, вводить в проект информацию для отладки, 
выполнять пошаговую трассировку, устанавливать точки прерывания, проверять 
значения переменных и вычислять значения выражения. По умолч анию, если 
исключительная ситуация возникает при работе в IDE, указывается строка 
исходного текста, в которой произошла ошибка. 

    108 Часть I. Основные положения 

    Иногда встроенный отладчик не может определить строку программы, вызвавшую 
ошибку. Выполнение программы, как обычно, прекратится, но строка-виновница не 
будет выделена. Обычно из сложившейся ситуации можно выйти, отключив опцию 
Optimizations (режимы оптимизации) и включив Stack Frames (кадры стека). Эти 
установки задаются во вкладке Compiler диало гового окна Project Options (оно 
открывается с помощью команды Project^Options). 

    На заметку 
    По умолчанию в Delphi принята система вызова процедур и функций Fastball 
(быстрый вызов), или регистровая система. Это оптимальная система, поскольку в 
ней, где это возможно, для.передачи параметров используются расширенные 
р^егщрры процессора. Данный метод эффективнее примененйя^егековых протоколов, 
таких как StdCall и Ра5саТПВсхёмё"Та51-Са11 ино гда даже для подпрограммы не 
надо создавать кадр стека. Впрочем, отказ от кадра может привести к некоторым 
несуразностям. Встроенный отладчик, "выталкивая" из стека кадры, добирается до 
источника ошибки (без них он этого сделать не сможет). 

    Вь1деле^ше_строки программы, в которой возникла исключительная ситуация, 
очень важно для отладки проекта. Однако при этом затр^дщется процесс отладки 
самого обработчика исключительных ситуаций. Если исключительная ситуация 
возникает при заданном глобальном обработчйТ^ёТвстроённый отладчик вообще 
прекращает выполнение программы до тех пор, пока вст упит в действие 
обработчик, который и надо было отладить. При возобновлении прерванного 
выполнения программы с помощью клавиши <F9> возникнет пауза, прерываемая 
нажатием любой клавиши. Для отладки o6pa6(3T4jiKajicj<mc^^ 
°1^^ч^ьЯпН212.Р™^~9?1^2?ё?Н2Г (прерывать выполнение после исключения). 
    1.   В меню IDE выберите команду Tools^Options, и откроется диалоговое 
окно Environment Options. 
    2.   Выберите вкладку Preferences. 
    3.   Отключите опцию Break On Exception. 

    Конструкции try 

    В Delphi определены две конструкции для обработки локальных исключительных 
ситуаций. Это try. . . finally и try. . .except. Они совершенно аналогичны по 
синтаксису, но различаются по назначению. Рассмотрим для начала конструкцию 
try. . . finally. 

    try 
    {Здесь операторы.} finally 
    {И здесь операторы, end; 
    } 

    Если в любом из операторов, размещенных между try и finally, возникает 
исключительная ситуация, выполнение передается первому оператору, следующему 
за командой finally, после чего выполняются все операторы до заключительного end;. 
    Подобным образом действует и конструкция try. . . except. Ниже приведен 
простейший пример. 

    try 

    {Здесь операторы.} except 
    {И здесь операторы.} end; 

    Как и в предыдущей конструкции, если между командами try и except 
возникает исключительная ситуация, выполнение передается операторам, 
заключенным между except и end;. 

    Назначение конструкции try...finally 

    Конструкция try. . . finally применяется для довольно длительного 
перехвата исключительной ситуации, чтобы выполнить все полагающиеся перед 
завершением программы действия, прежде чем управление будет передано 
глобальному обработчику исключительных ситуаций или следующему уровню 
обработкТГошибки. Эта конструкция не удаляет экземг1л^а^бъ^кта*о6"рабо т?и 
исключительной ситуации и, таким образом, не обрабатывает ошибки. 
    Рассмотрим случай динамического распределения памяти. Распределенному 
блоку памяти назначается указатель внутри подпрограммы, с помощью которого и 
происходит обращение к нему. Предположим, что во время одного из таких 
обращений происходит переполнение значения. Возникнет исключительная ситуация, 
которая прервет выполнение программы в данной строке 
    . Произойдет возврат из подпрограммы, в которой объявлен указатель, с 
освобождением стека и потерей содержащихся в нем локальных переменных. Будут 
удалены распределенные в стеке четыре байта указателя. В 

    Глава 5. Обработка исключительных ситуаций 109 

    результате связь с распределенным блоком памяти оказывается утерянной, и 
не остается никаких средств для того, чтобы удалить его из памяти. Результат— 
уменьшение объема доступной памяти. В следующем примере показано, как данная 
проблема решается в конструкции try". . . finally. 

    procedure TForml.ButtonlClick(Sender: TObject); 
    var pVar: PChar; 
    begin 
    GetMemfpVar, 10); 
    try 
    StrCopy(pVar, 'В этой строке больше 10 символов!'); 
    finally 
    FreeMemfpVar, 10); 
    end; end; 

    Прежде всего, объявляется переменная pVar. Затем с помощью процедуры 
GetMem из RTL этой переменной выделяется блок памяти объемом 10 байт. После 
команды try вызывается RTL-функция StrCopy для копирования строки в 
распределенный блок памяти. Длина копируемой строки явно больше 10 символов, 
что приводит к попытке записи в неразрешенный участок памят и. Вследствие 
этого в RTL возникает исключительная ситуация. Инициализируется образец 
объекта обработки исключительной ситуации типа EAccessViolation — потомка 
класса Exception. Когда возникает исключительная ситуация, выполнение 
передается оператору вызова процедуры FreeMem, освобождающей область памяти, 
предназначенную для переменной pVar. После выполнения процедуры FreeMem 
обработчик исключительной ситуации продолжает освобождать стек, останавливаясь 
на каждом уровне вложения конструкций try. . . finally,jio-_ ка не достигнет 
глобального обрабогчщса или конструкции try. . .except, обрабатывающей данную 
исключительную ситуацию. 

    Назначение конструкции try...exception 

    Конструкция try. . .exception применяется для перехвата исключительной 
ситуации с последующей возможной обработкой, предусматривающей освобождение 
экземпляра обработки исключительной ситуации, после чего выполнение программы 
продолжается, как_обычно. В этой конструкции есть необязательный 
дополнительный элемент, с помощью которого можно определить фактический тип 
возникшей исключительной ситуации. Элемент вида on... do подробнее 
рассматривается в следующем разделе. 

    procedure TForml.ButtonlClick(Sender:   TObject); 
    var  iCtr:   integer; 
    begin 
    for  iCtr   := Ord('A')   to  Ord('Z')   do try 
    ChDir(Char(iCtr)+':\DUKE3D'); 
    MessageDlg('\DUKE3D найдена  на  диске   '+ Char(iCtr), mtlnformation,    
[mbOK],   0); Break; except MessageDlg('\DUKE3D не  найдена  на  диске   '+ Char(iCtr), 
    mtError,    [mbOK],   Ob-end; end; 

    В данном примере для перебора всех возможных имен дисков от А до Z 
применяется конструкция f or. . . do. В каждом цикле происходит обращение к 
процедуре ChDir из RTL с параметром, содержащим имена очередного диска и 
каталога DUKE3D. Если данный каталог не найден на очередном диске, при 
выполнении процедуры ChDir возникает исключительная ситуация т ипа 
EInOutError. Выполнение передается первому оператору, следующему за командой 
except. В данном примере это приводит к выводу сообщения о том, что на текущем 
диске нет заданного каталога.^Есди-ацтроце-дуре 
ChDi^_JOJДиклa_pшибкa^лeдyкж^иe за ней операторы вызова процедур MessageDlg и 
Break не^вылолщцотся. ЁслТгже каталог найден, их выполнение при водит к выходу 
из цикла и нормальному возврату из метода. 
    Обработка ошибок в операторах except ставит перед компилятором задачу 
разрешения исключительных ситуаций. Другими словами, обработку исключительной 
ситуации можно не доводить до конца. Именно в этом состоит основное различие 
между конструкциями try. . . except и try. . . finally. В последней 
исключительная ситуация продолжает выгружать стек до тех пор, пока выполнение 
не перейдет на первый уровень вложенности операторов except или глобальный обработчик. 

    110 Часть I. Основные положения 


    Назначение конструкции on...do 

    В конструкции try. . . except содержится необязательный элемент, с помощью 
которого можно точно определить тип исключительной ситуации и узнать адрес 
образца объекта его обработки, получив тем самым доступ к членам, объявленным 
как public и published. Формат этого элемента — on. . . do. 
    Конструкция on... do допустима только между командой except и 
заключительным end;, как в следующем примере. 

    try 
    (Операторы.} except 
    on E:   Exception do 
    {Оператор,   возможно,   составной.} else 
    {Оператор,   возможно,   составной.} end; 

    Конструкция on... do применяется в случаях, когда дальнейшие действия 
зависят от типа исключительной ситуации. Кроме того, ее можно использовать для 
инициализации неожиданных исключительных ситуаций при обработке ожидаемых. 
Рассмотрим следующий пример. 

    procedure TForml.Button2Click(Sender: TObject); var pVal: PChar; begin try 
    GetMem(pVal,sizeof(Stream.Memory^)); 
    StrLCopy(pVal,Stream.Memory,sizeof(Stream.Memory*)+1); except 
    On E: EOutOfMemory do 
    LowMemTerminate; On E: EAccessViolation do begin 
    if MessageDlg(E.Message*': Завершить программу?', 
    mtError,[mbYes,mbNo],0) = mrYes then Application.Terminate end else 
    Raise; end; end; 

    В данном примере для переменной pVal распределяется объем памяти, 
достаточный для записи гипотетической глобальной переменной Stream.Memory*. 
Затем с помощью функции StrLCopy в эту область памяти копируется содержимое 
Stream.Memory*. Здесь есть два фрагмента, в которых может возникнуть 
исключительная ситуация. При слишком большом объеме переменной Stream.Memory* 
в процедуре GetMem может произойти ошибка и возникнуть исключительная ситуация 
EAccessViolation. Если эта процедура закончилась нормально, то ошибка 
произойдет в функции StrLCopy, поскольку объем копируемых данных на 1 байт 
больше объема распределенной памяти. В этом случае возникает исключительная 
ситуация EAccessViolation. Любая из этих исключительных ситуаций обрабатывается. 
    В данном примере использованы две конструкции on... do в стиле, 
напоминающем конструкцию case. . .of. Возникшая исключительная ситуация 
проверяется на принадлежность типу EOutOfMemory. Если экземпляр объекта 
обработки исключительной ситуации не относится к этому типу, выполняется 
проверка на его принадлежность типу EAccessViolation. Если он не при надлежит 
и этому типу, выполняется оператор, следующий за зарезервированным словом 
else. Затем исключительная ситуация вновь инициируется оператором Raise и 
перемещается вверх по стеку ("выталкивая" данные) до глобального обработчика 
объекта Application. 
    Идентификатор Е, следующий за зарезервированным словом On, — это ссылка на 
экземпляр объекта обработки исключительной ситуации, предоставляющая доступ ко 
всем членам этого объекта, объявленным как public и published. Именно это и 
происходит в операторе вызова функции MessageDlg. Вызывать метод Free не 
следует, поскольку это происходит автоматическ и. 
    Цепочку конструкций on... do можно заменить единственной такой 
конструкцией с динамической проверкой. Рассмотрим следующий пример. 

    procedure  TForml.ButtonSClick(Sender:   TObject); 
    var pVal:   PChar; 
    begin try 


    Глава 5. Обработка исключительных ситуаций 111 

       GetMem(pVal,sizeof(Stream.MemoryA)); 
       StrLCopy(pVal,Stream.Memory,sizeof(Stream.MemoryA)+1); 
    except On E: Exception do begin 
       if E is EOutOfMemory then 
          LowMemTerminate; 
          if E is EAccessViolation 
          then 
            begin 
               if MessageDlg(E.Message+': Terminate the program?', mtError,[mbYes,mbNo],0) = mrYes 
               then 
                 Application.Terminate; 
            end 
          else 
            Raise; 
          end; 
       end; 
     end; 

    В данном примере один раз использовалась конструкция On E: Exception, что 
можно перевести как "для исключительных ситуаций любого типа". Затем 
переменная Е проверяется в операции is на принадлежность одному из двух 
предполагаемых типов исключительных ситуаций. Если исключительная ситуация не 
принадлежит ни одному из этих типов или потомку одного и з них, выполняется 
оператор, следующий за командой else. Оператор Raise переводит обработку 
исключительной ситуации на новый уровень. 

    Вложение блоков try 

    В Delphi блоки try можно размещать один в другом. При этом и внешний, и 
внутренний блоки могут быть любого из двух видов. Единственное условие — 
внутренний блок должен полностью размещаться во внешнем между командами try и 
finally (except). Ниже приведен пример правильного вложения. 

    try 
    {операторы} 
    try 
    {операторы} 
    finally 
    {операторы} 
    end; 
    {операторы} except 
    {операторы} end; 

    Возможен и обратный порядок. 

    try 
    {операторы} 
    try 
    {операторы} 
    except 
    {операторы} 
    end; 
    {операторы} finally 
    {операторы} end; 

    Два предыдущих примера выполнены правильно, поскольку внутренний блок 
целиком помещается во внешнем. Ниже приведен пример неправильного вложения. 

    try 
    {операторы} try 
    {операторы} except 
    {операторы} 


    112 Часть I. Основные положения 


    finally 
    {операторы} end; 
    {операторы} 
    end; 

    Нетрудно заметить, что в данном примере допущена ошибка, даже если 
компилятор ее пропустит. В листинге 5.2 приведен простой пример вложения 
конструкций try. Здесь конструкция try. . . finally располагается внутри 
конструкции try...except. 

    Листинг 5.2. \UDELPHI3\CHP5\UNIT1. PAS. Вложение конструкции try. . . 
finally в конструкцию try. . . except 

    procedure TForml.Button4Click(Sender:   TObject); var sVar:   string; 
    pVar:   PChar; begin try 
    GetMem(pVar,10); try 
    StrCopy(pVar,'ListBox'); with  ListBoxl.Items  do begin 
    AddCLinel  -  index   0'); Add('Line2  -  index   1'); end; 
    sVar   :=  ListBoxl.Items[2]; 
    {Дополнительные  операторы с  переменными  sVar и pVar.} finally 
    FreeMemfpVar,10); end; except 
    on  EStringListError  do 
    MessageDlg('Выход  за  пределы списка',   mtError,    [mbOk],0); on 
EAccessViolation do 
    MessageDlg('Недостаточно распределенной памяти',   mtError,[mbOk],   0); else 
    Raise; end; end; 

    В процедуре, представленной в листинге 5.2, для переменной pVar 
распределяется 10 байт памяти. Затем в эту память с помощью функции StrCopy 
копируется строка 'ListBox'. После этого вносится две строки в окно ListBoxl, 
после чего с помощью свойства Items класса TListBox одна из этих строк 
присваивается переменной sVar. Однако здесь допущена ошибка: нумерация строк в 
списке начинается с нуля и заканчивается единицей, тогда как массиву Items 
присваивается элемент с индексом 2, т.е. несуществующий третий: 

    sVar   :=  ListBoxl.Items[2]; 

    Результат — исключительная ситуация EStringList. Поскольку исключительная 
ситуация возникает внутри конструкции try. . . finally, вызов процедуры 
FreeMem происходит даже в случае исключительной ситуации. В результате вызова 
освобождается память, распределенная переменной pVar. Поскольку конструкция 
try. . . finally фактически не обрабатывает исклю чительную ситуацию, 
происходит переход на следующий уровень обработки, в данном случае — в блок 
try. . . except. В конструкции on. . . do выполняется поиск исключительной 
ситуации EStringListError, затем вызывается функция MessageDlg. 
    Кроме того, в примере осуществляется проверка на исключительную ситуацию 
типа EAccessViolation, поскольку, если бы оператор 

    sVar   :=  ListBoxl.Items[2]; 

    выполнился нормально, практически любой оператор с переменной pVar мог бы 
вызвать исключительную ситуацию EAccessViolation. 
    Проверку на эту исключительную ситуацию необходимо выполнять везде, где 
происходит обращение к процедуре GetMem. Но что если исключительная ситуация 
произойдет внутри самого обработчика исключительной ситуации? Что 

    Глава 5. Обработка исключительных ситуаций 113 

    если ошибка произойдет в обеих процедурах — GetMem и FreeMem? Если в 
процедуре GetMem ошибка происходит из-за недостатка свободной памяти, 
возникает исключительная ситуация EOutOfMemory. Среди операторов, следующих за 
командой finally, должен быть вызов процедуры FreeMem. Выполнение последней 
чревато возникновением еще одной ошибки (EInvalidPointe rOperation или, 
возможно, EAccessViolation) из-за попытки освобождения нераспределенной 
памяти. В этих условиях EAccessViolation— единственно возможный тип 
исключительной ситуации. В случае исключительной ситуации EOutOfResources 
выполнение программы придется прекратить. В подобной ситуации выполняется 
часть else элемента on. . .do, где происходит явный переход исключительной 
ситуации на новый уровень обработки (в данном случае — на уровень глобального 
обработчика) с помощью команды Raise. 
    В отличие от предыдущего листинга, в листинге 5.3 конструкция try...except 
помещается внутрь try...finally. 

    Листинг 5.3. \UDELPHI3\CHP5\EXCEPTU1. PAS. Вложение конструкции try. . . 
except j в конструкцию try. .. finally 

    procedure  TForml.ButtonSClick(Sender:   TObject); var  sVar:   string; 
    pVar:   PChar; begin 
    GetMem(pVar,10); try 
    StrCopy(pVar,'ListBox'); 
    (   Операторы с переменной pVar.   } 
    try 
    sVar := ListBoxl.Items[0]; except 
    on EStringListError do begin 
    ListBoxl.Items.Add('Iteml'); sVar := ListBoxl.Items[0]; end 
    else Raise; end; 
    { Операторы с переменной sVar. } finally 
    FreeMem(pVar,10); end; end; 

    В листинге 5.3 для переменной pVar распределяется 10 байт памяти. Его 
отличие от листинга 5.2 состоит в том, что все исключительные ситуации, 
возникающие в результате ошибок, обрабатываются глобальным обработчиком. С 
помощью функции StrCopy предпринимается попытка копирования строки в буфер, 
распределенный для переменной pVar. При неудачном копиро вании выполнение 
передается оператору вызова процедуры FreeMem, следующему за командой finally. 
После этого исключительная ситуация обрабатывается вверх по стеку. Если 
функция StrCopy выполнилась успешно, предпринимается попытка извлечь первую 
строку из окна ListBoxl. Если ListBoxl пуст, возникает исключительная ситуация 
EStringListError. В блоке on. . . do происходит обработка этой ошибки, в 
процессе чего в окно ListBoxl вносится строка, которая затем успешно из него 
извлекается. Обработка продолжается до вызова процедуры FreeMem. 
    Если выполнение оператора 

    sVar   :=  ListBoxl.Items[0]; 

    прошло без ошибки, но за ним следуют другие операторы, в них могут 
возникнуть исключительные ситуации иных типов. Мы рассмотрели исключительную 
ситуацию EStringListError. Исключительные ситуации других типов должны 
перехватываться и обрабатываться во внешнем блоке try. . .finally. В этом 
случае происходит вызов процедуры FreeMem и, следовательно, освобождение 
распределенной памяти. Обработка исключительной ситуации переходит на 
следующий уровень или передается глобальному обработчику. 

    114 Часть I. Основные положения 


    Вызов исключительных ситуаций 

    В данной главе часто встречалось зарезервированное слово Raise. Это 
команда, входящая в синтаксис программирования обработки исключительных 
ситуаций Delphi и вызывающая создание экземпляра объекта класса Exception или 
его потомка. 
    Raise удобна во многих случаях. Выше в данной главе использовалась 
гипотетическая функция InsertChar. Она могла бы иметь следующий вид. 

    function  InsertChar(cVal: char; var sVal: string; iPos: integer): boolean; 
    begin 
       Result   :=  False; 
       if  iPos  <=  Length(sVal)   then 
          begin 
             sVal[iPos] := cVal; 
             Result := True; 
          end; 
    end; 

    Итак, функция InsertChar возвращает True после успешного выполнения и 
False — в случае ошибки. Ее можно вставить в любой фрагмент программы, в 
котором выполнение операции оценивается по возвращаемому значению. Однако эту 
функцию можно переделать, что&ьмуэезультате ^эшибки возникала исключительная 
ситуация, обрабатываемая затем в конструлйцидХд;^, 
    После модификации фЗ'недйТХпзЪгЕСКаг может принятГслёдующий'видГ1" """ 

    procedure InsertChar(cVal: char; var sVal: string; iPos: integer); 
    begin 
       if  iPos > Length(sVal) then 
          Raise  Exception.Create('Превышение  длины'); 
       sVal[iPos] :=  cVal; 
    end; 

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

    sVal[iPos] :=  cVal; 

    выполнен не будет. 
    Самое интересное в данном примере то, что в результате получилась 
модельраннего возврата без обращения к процедуре Exit из RTL (которая, однако, 
ничего не может сообщить об ошибке) До сих пор нет единого мнения о том, 
является ли применение раннего возврата признаком хорошего стиля 
программирования. Все же в некоторых случаях это средство оказывае тся весьма 
полезным (в частности, при обработке некоторых событий VCL, таких как событие 
ОпВе-f orePost, прекращающее заполнение записей в случае неправильной 
адресации). Кроме того, ранние возвраты необходимы дляреализации State-логики 
и кода State Machine, применяемых в экспертных системах. 
    Синтаксис оператора Raise таков:                                           

    Raise <Instance>; 

    Команда Raise действует наподобие динамически инициализируемой переменной, 
которая, однако, указывает компилятору, что в данном месте необходимо прервать 
выполнение иначать "выталкивание" данных из стека. Рассмотрим следующий оператор: 

    Buttonl   := TButton.Create(Self); Он выполняет динамическую инициализацию 
объекта Buttonl. А теперь сравним его со следующим оператором: 

    Raise  Exception.Create('Произошла  ошибка'); 

    Оба оператора очень похожи, но во втором нет операции присваивания. Однако 
основное различие состоит в том, как эти операторы действуют. Авторы вовсе не 
полагают, что при установке кнопки должен происходить ранний возврат с 
OESD^l^iBJ^SHffil^SJ3^^^ глобального обработчика исключительных ситуаций. 
Конечно же, если установка кнопки не удастся, будет создан экземпляр объекта 
обработки исключительных ситуаций, и это будет сделано оператором подобного вида. 

    Глава 5. Обработка исключительных, ситуаций 115 


    Взаимоисключения 

    В архитектуре обработки исключительных ситуаций Delphi есть еще одно 
средство — мьютекс. Это любой экземпляр объекта обработки исключительных 
ситуаций, инициализированный из класса EAbort или одного из его потомков. 
Обработка такой исключительной ситуации не поднимается выше первого уровня. 
Исключительную ситуацию EAbort можно применять для вызова раннего возврата, 
чтобы сократить обработку. Рассмотрим следующий пример. 

    var  Delphi:   string; 
    procedure  SetDelphiVariable(sValue:   string); begin 
    if Delphi =  sValue  then Raise  EAbort.Create(''); 
    if   (Length(sValue)   >  0)   and   (Length(sValue)   <  50)   then 
    Delphi   :=  sValue; end; 
    procedure TForml.Button6Click(Sender:   TObject); begin 
    SetDelphiVariable('Anders  Hejlsberg1); end; 

    Метод Button6Click вызывает процедуру SetDelphiVariable, задав ей 
строковый параметр. Назначение этой процедуры— оценить переменную sValue, 
предшествующую присвоению ее значения глобальной переменной Delphi. Если 
результат оценки положителен, присвоение выполняется. Если переменная Delphi 
содержит то же значение, что и sValue, оценка и присвоение значения становятся 
лишними. Ситуация исправляется путем проверки равенства значений переменных 
Delphi и sValue. Если они равны, инициализируется исключительная ситуация 
EAbort, вызывающая выход из процедуры без какой-либо дополнительной обработки. 
    Обратите внимание, что вызов процедуры SetDelphiVariable из 
Button6Clickjae заключен^блок try. Если необходимо довести исключительную 
ситуацию до глобального обработчика, присвоенного свойству"Application. 
OnException, делать это надо без исключительной ситуации EAbort. Однако, если 
вызов процедуры SetDel-phiVajriable поместить в блок try, операто ры, 
следующие за командой except или finally, будут выполнены, как-обычно. И, 
конечно, обработку исключительной ситуации можно перевести на следующий уровень. 

    Базовый класс исключительных ситуаций 

    Мы уже не однажды упоминали класс Exception из модуля SysUtils. Пришло 
время на него взглянуть. Выше была рассмотрена конструкция on. . . do, в 
которой присутствовал идентификатор Е, обеспечивавший доступ к объекту 
обработки исключительных ситуаций. Зачем нужен этот доступ? Затем, что объект 
— это экземпляр класса, и доступ к нему — это доступ и к его членам, 
объявленным как public и published. Объявление класса Exception в модуле 
SysUtils представлено в листинге 5.4. 

    Листинг 5.4. \UDELPHI3\CHP5\SYSUTILS. PAS. Все исключительные ситуации - 
потомки класса Exception 

    Exception = class(TObject) private 
    FMessage: string; 
    FHelpContext: Integer; public 
    constructor Create(const Msg: string); 
    constructor CreateFmt(const Msg: string; const Args: array of const); 
    constructor CreateRes(Ident: Integer); 
    constructor CreateResFmt(Ident: Integer; const Args: array of const); 
    constructor CreateHelp(const Msg: string; AHelpContext: Integer); 
    constructor CreateFmtHelp(const Msg: string; const Args: array of const; 
AHelpContext: Integer); 
    constructor CreateResHelp(Ident: Integer; AHelpContext: Integer); 
    constructor CreateResFmtHelp(Ident: Integer; const Args: array of const; 


    116 Часть I. Основные положения 

    AHelpContext:   Integer); 
    property HelpContext:   Integer  read  FHelpContext write  FHelpContext; 
property Message:   string  read  FMessage  write  FMessage; end; 

    Класс Exception-— базовый для всех типов исключительных ситуаций. Его 
потомками являются классы EAbort, EAccessViolation, EInvalidPointerOperation и 
многие другие. Этот класс содержит основные средства всех классов 
исключительных ситуаций, реализованные в ряде конструкторов. Он может 
содержать строку, доступ к которой возможен через свойство Messa 
    ge, объявленное как public. Если исключительная ситуация возникает во 
время отладки и свойству Jiel_?Context присвоен идентификатор^ 
контекста_с^а11кТ1, 1С^^ информациюIBi справо_ч,ной системе. ——— —~, ..... 

    Конструкторы класса Exception 

    В классе Exception содержится восемь статических конструкторов. Их удобно 
применять для инициализации исключительных ситуаций разных типов. У каждого 
конструктора есть свой набор параметров, но все они имеют одно назначение: 
инициализировать объект, записав в него строку, описывающую исключительную 
ситуацию. Это именно та строка, которую можно пол учить из свойства Message. И 
именно она выводится на экран, когда выполнение программы прерывается 
исключительной ситуацией в режиме отладки. Наконец, эту строку можно увидеть в 
отсутствие какой бы то ни было исключительной ситуации при вызове метода 
Application. ShowException. 
    Чаще всего применяется конструктор Create. У него один строковый параметр. 
До сих пор упоминался именно он. У конструктора CreateFmt тоже есть строковый 
параметр, но строка в данном случае должна содержать спецификаторы формата; 
кроме того, добавлен открытый массив параметров, присваиваемых каждому 
спецификатору. Конструктору CreateRes в качестве параметра нужен некоторый 
идентификатор. По этому идентификатору извлекается строка из строкового 
ресурса, помещенного компилятором в выполняемый файл. Это удобно в случаях, 
когда надо ограничить число файлов, используемых программой. В остальных 
конструкторах сочетаются характеристики тех, которые мы только что рассмотрели. 

    Создание типов исключительных ситуаций 

    Все исключительные ситуации являются потомками базового класса Exception, 
поэтому создать и инициализировать в программе новый класс не составляет 
труда. Вот две причины, по которым новый класс исключительных ситуаций следует 
делать потомком класса Exception. 
    •    Возможность использования средств класса Exception. 
    •    Наличие механизма динамической проверки типа. 
    Каждый конструктор класса Exception должен был бы содержать по умолчанию 
параметр ErrorCode (код ошибки). Таким образом, при ссылке на экземпляр 
объекта обработки исключительных ситуаций можно было бы сразу получать 
информацию об ошибке, не прибегая к динамической проверке типа, которую 
приходится выполнять в любом коде, присоединенном к свойству 
    Application. OnException. Многие программисты, чтобы избежать громоздкой 
динамической проверки типа, проверяют строку в свойстве Message, как показано ниже. 

    procedure  TGlobalExHandler.HandlerProc(Sender:   TObject;   EInstance:   
Exception); begin 
    if pos('EOutOfResource',   EInstance.Message)   >  0  then 
    {Ошибка EOutOfResource.   Дальше  следуют  операторы обработки.} end; 

    Если известны коды ошибок, численные номера исключительных ситуаций удобно 
объявлять в виде констант и тип исключительной ситуации можно определять в 
операторе case. Рассмотрим следующий пример. 

    procedure TGlobalExHandler.   HandlerProc(Sender:   TObject;   EInstance:  
 Exception); begin 
    if  Sender  is  EMyComponentError  then case   (Sender  as  
TComponent).Tag  of 
    clnvalidValue:   {Соответствующие  операторы.} cRangeError     :   
{Соответствующие  операторы.} else begin 
    ShowMessage('Фатальная  ошибка.   Выполнение  прекращено') 


    Глава 5. Обработка исключительны* ситуаций 117 


    Application.Terminate; end; end; end; 

    В данном примере выполняется одна динамическая проверка на принадлежность 
исключительной ситуации типу EMyComponentException или его потомку. Если 
результат проверки положителен, выполняется блок case. . .of. в котором 
определяются дальнейшие действия в зависимости от значения свойства ErrorCode. 
Если это значение неизвестно, на экран выводится со общение для пользователя, 
после чего выполнение программы завершается. В листинге 5.5 показано, как 
может выглядеть новый класс EMyComponentError. 
    f" Листинг 5.5. \UDELPHI3\CHP5\PROJECT1 .-DPR. Расширение возможностей 
базового класса Exception 

    type 
    EMyComponentError = class(Exception) private 
    FErrorCode: integer; public 
    property ErrorCode: integer read FErrorCode write FErrorCode; 
    constructor Create (const Msg: string; ErrCode: integer); end; 
    implementation 
    constructor EMyComponentError. Create(const Msg: string; ErrCode: 
integer); begin 
    Inherited Create(Msg); 
    FErrorCode := ErrCode;; end; 
    end. 

    Здесь создается новый тип исключительной ситуации с именем 
EMyComponentError, непосредственно наследующей класс Exception. В новом классе 
вводится свойство ErrorCode с непосредственным считыванием и записью значения 
переменной FErrorCode. Кроме того, определен новый статический конструктор 
Create с одним дополнительным параметром (по сравнению со своим 
предшественником). Этот параметр — код ошибки, который можно будет определить 
через свойство ErrorCode. Инициализация исключительной ситуации нового типа 
выглядит приблизительно так. 

    Raise EMyComponentError.Create( 
    'Выход за диапазон допустимых  значений',   cRangeError); 

    Можно создать и класс с динамическим определением источника ошибки. 
Рассмотрим пример. 

    procedure TGlobalExHandler.HandlerProc(Sender: TObject; EInstance: 
Exception); begin 
    if Sender is EMyComponentError then begin 
    if Sender is EMyCompoRangeError then 
    (Соответствующие операторы.} else if Sender is EMyCompoInvalidValue then 
    {Соответствующие операторы.} end; else 
    begin 
      ShowMessage('Фатальная ошибка. Выполнение прекращено') Application.Terminate; 
    end; 
end; 


    Часть I. Основные положения 

    В данном примере сначала выполняется проверка исключительной ситуации на 
принадлежность типу ЕМуСотро-nentError. В случае положительного результата 
осуществляется дополнительная проверка для уточнения типа. Здесь происходит 
сравнение с двумя вновь созданными типами: 

    type 

    EMyCompoRangeError = class(EMyComponentError); 
    EMyCompoInvalidValue = class(EMyComponentError); 

    В отличие от класса EMyComponentError, классы EMyCompoRangeError и 
EMyCompoInvalidValue не являются непосредственными потомками базового класса 
Exception. Их предок— класс EMyComponentError. Это позволяет начать проверку с 
базового типа, и, если результат этой проверки равен False, нет надобности 
проверять конкретные типы EMyCompoRangeError и EMyCompoInvalidValue. 

    Резюме 

    В данной главе рассматривались теория обработки исключительных ситуаций в 
Delphi, конструкторы объектов обработки исключительных ситуаций и их 
применение. Вы наверняка уже поняли, что для создания надежных и "живучих" 
программ не обойтись без рассмотренных здесь конструкций try. . . except и 
try. . . finally. Создание типов исключительных ситуаций , как правило, 
связано с созданием новых компонентов. Однако, независимо от этого, во всех 
проектах необходимо строить свой глобальный обработчик исключительных ситуаций. 

    Глава 5. Обработка исключительных ситуаций 119 

