- •Обработка исключительных ситуаций в Delphi
- •1 Обработка исключительных ситуаций
- •2 Модель исключительных ситуаций в Delphi
- •3 Синтаксис обработки исключительных ситуаций
- •4 Примеры обработки исключительных ситуаций
- •5 Вызов исключительной ситуации
- •6 Доступ к экземпляру объекта exception
- •7 Процедура Assert
- •8 Предопределенные обработчики исключительных ситуаций
2 Модель исключительных ситуаций в Delphi
Модель исключительных ситуаций в Delphi является невозобновляемой (non-resumable). При возникновении исключительной ситуации уже невозможно вернуться в точку, где она возникла, для продолжения выполнения программы (это позволяет сделать возобновляемая (resumable) модель). Невозобновляемые исключительные ситуации не сохраняют стек в поисках обработчика; в возобновляемой модели необходимо сохранять стек, состояние регистров процессора в точке возникновения ошибки и выполнять поиск обработчика и его выполнение в отдельном стеке. Возобновляемую систему обработки исключительных ситуаций гораздо труднее создать и применять, нежели невозобновляемую.
3 Синтаксис обработки исключительных ситуаций
В Delphi существует два типа обработчиков исключительных ситуаций:
try..except..end;
try..finally..end;
После зарезервированного слова try начинается участок кода, исключительные ситуации, возникающие в котором, могут быть обработаны. После зарезервированного слова except и до зарезервированного слова end содержится код, который вызывается при возникновении исключительной ситуации. После зарезервированного слова finally и до зарезервированного слова end содержится код, который вызывается как при возникновении исключительной ситуации, так и при нормальном достижении этой точки программы..
Первый тип используется для обработки именно и только исключительных ситуаций. Его синтаксис:
try
{Код программы в котором могут возникать исключения};
...
except
on Exception1 do Statement1; {обработка исключения конкретного типа}
on Exception2 do Statement2;
...
else
Statements; {обработка исключений всех остальных типов}
end;
Для уверенности в том, что ресурсы, занятые вашим приложением, освободятся в любом случае, Вы можете использовать конструкцию второго типа. Код, расположенный в части finally, выполняется в любом случае, даже если возникает исключительная ситуация. Соответствующий синтаксис:
try
{Код программы в котором могут возникать исключения};
...
finally
Statements; {Код программы который выполняется в любом случае, даже если возникло исключение}
end;
4 Примеры обработки исключительных ситуаций
Ниже приведены процедуры A,B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Delphi:
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
Процедура 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 вызывает исключение, программа сразу переходит на обработчик ошибок в процедуре 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 вне блока try, то предполагаем, что 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 можно использовать внутри обработчика исключительной ситуации.