|
|
Книги-onlineEvent Oriented Programming. 32 урока по Delphi Урок 9 : Обработка исключительных
ситуаций в Delphi
Содержание урока 9:
При традиционной обработке ошибок, ошибки, обнаруженные в процедуре обычно передаются наружу (в вызывавшую процедуру) в виде возвращаемого значения функции, параметров или глобальных переменных (флажков). Каждая вызывающая процедура должна проверять результат вызова на наличие ошибки и выполнять соответствующие действия. Часто, это просто выход еще выше, в более верхнюю вызывающую процедуру и т.д. : функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B, B проверяет возвращаемый код, видит, что возникла ошибка и возвращает код ошибки в A, A проверяет возвращаемый код и выдает сообщение об ошибке либо решает сделать что-нибудь еще, раз первая попытка не удалась. Такая “пожарная бригада” для обработки ошибок трудоемка, требует написания большого количества кода в котором можно легко ошибиться и который трудно отлаживать. Одна ошибка в коде программы или переприсвоение в цепочке возвращаемых значений может привести к тому, что нельзя будет связать ошибочное состояние с положением дел во внешнем мире. Результатом будет ненормальное поведение программы, потеря данных или ресурсов, или крах системы. Структурная обработка исключительной ситуации замещает ручную обработку ошибок автоматической, сгенерированной компилятором системой уведомления. В приведенном выше примере, процедура A установила бы “охрану” со связанным обработчиком ошибки на фрагмент кода, в котором вызывается B. B просто вызывает C. Когда C обнаруживает ошибку, то создает (raise) исключительную ситуацию. Специальный код, сгенерированный компилятором и встроенный в Run-Time Library (RTL) начинает поиск обработчика данной исключительной ситуации. При поиске “защищенного” участка кода используется информация, сохраненная в стеке. В процедурах C и B нет такого участка, а в A - есть. Если один из обработчиков ошибок, которые используются в A, подходит по типу для возникшей в C исключительной ситуации, то программа переходит на его выполнение. При этом, область стека, используемая в B и C, очищается; выполнение этих процедур прекращается. Если в A нет подходящего обработчика, то поиск продолжается в более верхнем уровне, и так может идти, пока поиск не достигнет подходящего обработчика ошибок среди используемых по умолчанию обработчиков в RTL. Обработчики ошибок из RTL только показывают сообщение об ошибке и форсированно прекращают выполнение программы. Любая исключительная ситуация, которая осталась необработанной, приведет к прекращению выполнения приложения. Без проверки возвращаемого кода после каждого вызова подпрограммы, код программы должен быть более простым, а скомпилированный код - более быстрым. При наличии исключительных ситуаций подпрограмма B не должна содержать дополнительный код для проверки возвращаемого результата и передачи его в A. B ничего не должна делать для передачи исключительной ситуации, возникшей в C, в процедуру A - встроенная система обработки исключительных ситуаций делает всю работу. Данная система называется структурной, поскольку обработка ошибок определяется областью “защищенного” кода; такие области могут быть вложенными. Выполнение программы не может перейти на произвольный участок кода; выполнение программы может перейти только на обработчик исключительной ситуации активной программы.
Первый тип используется для
обработки исключительных ситуаций. Его синтаксис:
try Statement 1; Statement 2; ... except on Exception1 do Statement; on Exception2 do Statement; ... else Statements; {default exception-handler} end; Для уверенности в том, что ресурсы, занятые вашим приложением, освободятся в любом случае, Вы можете использовать конструкцию второго типа. Код, расположенный в части finally, выполняется в любом случае, даже если возникает исключительная ситуация. Соответствующий синтаксис: try Statement1; Statement2; ... finally Statements; { These statements always execute } end;
type ESampleError = class(Exception); var ErrorCondition: Boolean; procedure C; begin writeln('Enter C'); if (ErrorCondition) then begin writeln('Raising exception in C'); raise ESampleError.Create('Error!'); end; writeln('Exit C'); end; procedure B; begin writeln('enter B'); C; writeln('exit B'); end; procedure A; begin writeln('Enter A'); try writeln('Enter A''s try block'); B; writeln('After B call'); except on ESampleError do writeln('Inside A''s ESampleError handler'); on ESomethingElse do writeln('Inside A''s ESomethingElse handler'); end; writeln('Exit A'); end; begin writeln('begin main'); ErrorCondition := True; A; writeln('end main'); end. При ErrorCondition = True программа выдаст: begin main Enter A Enter A's try block enter B Enter C Raising exception in C Inside A's ESampleError handler Exit A end main Возможно вас удивила декларация типа 'ESampleError =class' вместо '=object'; это еще одно новое расширение языка. Delphi вводит новую модель объектов, доступную через декларацию типа '=class'. Описание новой объектной модели дается в других уроках. Здесь же достаточно сказать, что исключительные ситуации (exceptions) являются классами, частью новой объектной модели. Процедура C проверяет наличие ошибки (в нашем случае это значение глобальной переменной) и, если она есть (а это так), C вызывает(raise) исключительную ситуацию класса ESampleError. Процедура A помещает часть кода в блок try..except. Первая часть этого блока содержит часть кода, аналогично конструкции begin..end. Эта часть кода завершается ключевым словом except, далее следует один или более обработчиков исключительных ситуаций on xxxx do yyyy, далее может быть включен необязательный блок else, вся конструкция заканчивается end;. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on xxxx do yyyy), после резервного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Если возникшая исключительная ситуация подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO - все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого. Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C. После того, как найден подходящий обработчик ошибки, поиск оканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try..except (в примере - writeln('Exit A')). Конструкция try..except подходит, если известно, какой тип ошибок нужно обрабатывать в конкретной ситуации. Но что делать, если требуется выполнить некоторые действия в любом случае, произошла ошибка или нет? Это тот случай, когда понадобится конструкция try..finally. Рассмотрим модифицированную процедуру B: procedure NewB; var P: Pointer; begin writeln('enter B'); GetMem(P, 1000); C; FreeMem(P, 1000); writeln('exit B'); end; Если C вызывает исключительную ситуацию, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например: procedure NewB; var P: Pointer; begin writeln('enter NewB'); GetMem(P, 1000); try writeln('enter NewB''s try block'); C; writeln('end of NewB''s try block'); finally writeln('inside NewB''s finally block'); FreeMem(P, 1000); end; writeln('exit NewB'); end; Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом: begin main Enter A Enter A's try block enter NewB enter NewB's try block Enter C Raising exception in C inside NewB's finally block Inside A's ESampleError handler Exit A end main Код в блоке finally выполнится при любой ошибке, возникшей в соответствующем блоке try. Он же выполнится и в том случае, если ошибки не возникло. В любом случае память будет освобождена. Если возникла ошибка, то сначала выполняется блок finally, затем начинается поиск подходящего обработчика. В штатной ситуации, после блока finally программа переходит на следующее предложение после блока. Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать exception EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory. А что, если требуется в B распределить 4 области памяти по схеме все-или-ничего? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так: procedure NewB; var p,q,r,s: Pointer; begin writeln('enter B'); P := nil; Q := nil; R := nil; S := nil; try writeln('enter B''s try block'); GetMem(P, 1000); GetMem(Q, 1000); GetMem(R, 1000); GetMem(S, 1000); C; writeln('end of B''s try block'); finally writeln('inside B''s finally block'); if P <> nil then FreeMem(P, 1000); if Q <> nil then FreeMem(Q, 1000); if R <> nil then FreeMem(R, 1000); if S <> nil then FreeMem(S, 1000); end; writeln('exit B'); end; Установив сперва указатели в NIL, далее можно определить, успешно ли прошел вызов GetMem. Оба типа конструкции try можно использовать в любом месте, допускается вложенность любой глубины. Исключительную ситуацию можно вызывать внутри обработчика ошибки, конструкцию try можно использовать внутри обработчика исключительной ситуации.
raise ESampleError.Create(‘Error!’); После ключевого слова raise следует код, аналогичный тому, что используется для создания нового экземпляра класса. Действительно, в момент вызова исключительной ситуации создается экземпляр указанного класса; данный экземпляр существует до момента окончания обработки исключительной ситуации и затем автоматически уничтожается. Вся информация, которую нужно сообщить в обработчик ошибки передается в объект через его конструктор в момент создания. Почти все существующие классы
исключительных ситуаций являются наследниками базового класса Exception
и не содержат новых свойств или методов. Класс
Exception имеет несколько конструкторов,
какой из них конкретно использовать - зависит от задачи. Описание класса
Exception можно найти в on-line
Help.
Как уже говорилось, при вызове исключительной ситуации (raise) автоматически создается экземпляр соответствующего класса, который и содержит информацию об ошибке. Весь вопрос в том, как в обработчике данной ситуации получить доступ к этому объекту. Рассмотрим модифицированную процедуру A в нашем примере: procedure NewA; begin writeln('Enter A'); try writeln('Enter A''s try block'); B; writeln('After B call'); except on E: ESampleError do writeln(E.Message); on ESomethingElse do writeln('Inside A''s ESomethingElse handler'); end; writeln('Exit A'); end; Здесь все изменения внесены в строку on ESE: ESampleError do writeln(ESE.Message); Пример демонстрирует еще одно новшество в языке Object Pascal - создание локальной переменной. В нашем примере локальной переменной является ESE - это тот самый экземпляр класса ESampleError, который был создан в процедуре C в момент вызова исключительного состояния. Переменная ESE доступна только внутри блока do. Свойство Message объекта ESE содержит сообщение, которое было передано в конструктор Create в процедуре C. Есть еще один способ доступа к экземпляру exception - использовать функцию ExceptionObject: on ESampleError do writeln(ESampleError(ExceptionObject).Message);
begin raise EAbort.CreateRes(SOperationAborted) at ReturnAddr; end;
2) когда имя компоненты не уникально или не допустимо.
try Table1.Active := True; {Пытаемся открыть таблицу} Break; { Если нет ошибки - прерваем цикл} except on EDatabaseError do {Если нажата OK - повторяем попытку открытия Table1} if MessageDlg('Не могу открыть Table1', mtError, [mbOK, mbCancel], 0) <> mrOK then raise; end; until False;
private FErrors: TList; function GetError(Index: Integer): TDBError; function GetErrorCount: Integer; public constructor Create(ErrorCode: DBIResult); destructor Destroy; property ErrorCount: Integer; property Errors[Index: Integer]: TDBError; end; Особенно важны два свойства класса EDBEngineError : Errors - список всех ошибок, находящихся в стеке ошибок BDE. Индекс первой ошибки 0; ErrorCount - количество ошибок в стеке. Объекты, содержащиеся в Errors, имеют тип TDBError. Доступные свойства класса TDBError: ErrorCode - код ошибки, возвращаемый Borland Database Engine; Category - категория ошибки, описанной в ErrorCode; SubCode - ‘субкод’ ошибки из ErrorCode; NativeError - ошибка, возвращаемая сервером БД. Если NativeError 0, то ошибка в ErrorCode не от сервера; Message - сообщение, переданное сервером, если NativeError не равно 0; сообщение BDE - в противном случае.
Внимание! Если у вас не получилось найти нужную информацию, используйте рубрикатор или воспользуйтесь поиском . книги по программированию исходники компоненты шаблоны сайтов C++ PHP Delphi скачать |
|