Защитные конструкции языка Object Pascal
Лабораторная работа №6
Обработка исключительных ситуаций
Задание:
На языке Delphi разработать приложение. Требования к приложению:
Часть «Калькулятор»:
§ Выполнение 4-х арифметических действий над действительными числами.
§ Вычисление 3-х математических функций.
§ Структурная обработка исключительных ситуаций с выводом информации об исключении.
§ Предусмотреть возможность ввода данных с клавиатуры (обработка нажатий цифровых клавиш и клавиш операций).
Часть «Вычисления выражений»:
§ Вычисление выражений. Константы и формулы задаются с помощью команды вида имя=выражение. Для вывода значения выражения используется команда PRINT «Подсказка», имя
§ Для реализации вычисления выражений использовать возможности Microsoft Excel. (С помощью функции CreateOleObject(“Excel.Application”) создать экземпляр приложения Excel. Перенести константы и формулы на рабочий лист, заменив символические имена адресами ячеек.)
var
WB,EX:OleVariant;
begin
try
EX := CreateOleObject('Excel.Application');
WB:=EX.Workbooks.Add($FFFFEFB9);
except
LogError('Проблемы с Excel.');
Exit;
end;
§ Структурная обработка исключительных ситуаций с выводом информации об исключении. Реализовать собственный класс исключения.
Содержание:
Понятие исключительной ситуации. 1
Класс Exception. 2
Инициализация исключений. 3
Защитные конструкции языка Object Pascal 4
Блок try..except 4
Блок try...finally. 5
Использование исключительных ситуаций. 6
Протоколирование исключительных ситуаций. 7
Понятие исключительной ситуации
Исключительная ситуация - нештатное событие в процессе выполнение программы, способное повлиять на дальнейшее выполнение программы.
Компилятор Delphi генерирует код, который перехватывает любое такое нештатное событие, сохраняет необходимые данные о состоянии программы, и выдает разработчику. Что можно выдать в объектно-ориентированном языке программирования? Конечно же, объект. С точки зрения Object Pascal исключительная ситуация — это объект.
Вы можете получить и обработать этот объект, предусмотрев в программе специальную языковую конструкцию (try…except). Если такая конструкция не предусмотрена, все равно исключение будет обработано — в недрах библиотеки VCL есть соответствующие обработчики, окружающие все потенциально опасные места.
Класс Exception
Исключительные ситуации отличаются классом. В системном модуле Delphi 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); overload;
constructor CreateRes(ResStringRec: PResStringRec); overload;
constructor CreateResFmt(Ident: Integer; const Args: array of const); overload;
constructor CreateResFmt(ResStringRec: PResStringRec; const Args: array of const); overload;
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); overload;
constructor CreateResHelp(ResStringRec: PResStringRec; AHelpContext: Integer); overload;
constructor CreateResFmtHelp(ResStringRec: PResStringRec; const Args: array of const; AHelpContext: Integer); overload;
constructor CreateResFmtHelp(Ident: Integer; const Args: array of const;
AHelpContext: Integer); overload;
property HelpContext: Integer read FHelpContext write FHelpContext;
property Message: string read FMessage write FMessage;
end;
ExceptClass = class of Exception;
Как видно из приведенного описания класса Exception, у него имеется двенадцать конструкторов, позволяющих задействовать при создании объекта текстовые строки из ресурсов приложения (имя включает строку Res), форматирование текста (включает Fmt), связь с контекстом справочной системы (включает Help).
Конструкторы, в названии которых встречается подстрока Fmt, могут вставлять в формируемый текст сообщения об ошибке значения параметров, как это делает стандартная функция Format:
If MemSize > Limit then
raise EOutOfMemory.CreateFmt('Cannot allocate more than %d bytes',[Limit]);
Тип Exception порождает многочисленные дочерние типы, соответствующие часто встречающимся случаям ошибок ввода/вывода, распределения памяти и т. п.
Заметим, что тип Exception и его потомки представляют собой исключение из правила, предписывающего все объектные типы именовать с буквы Т. Потомки Exception начинаются с Е, например EZeroDivide.
Таблица 1.Исключительные ситуации целочисленной математики (порождены отEIntError)
Тип | Условие возникновения |
EDivByZero | Попытка деления на ноль (целое число) |
ERangeError | Число или выражение выходит за допустимый диапазон |
EIntOverflow | Целочисленное переполнение |
Таблица 2.Исключительные ситуации математики с плавающей точкой (порождены от EMathError)
Тип | Условие возникновения |
EInvalidOp | Неверная операция |
EZeroDivide | Попытка деления на ноль |
EOverflow | Переполнение с плавающей точкой |
EUnderflow | Исчезновение порядка |
EInvalidArgument | Неверный аргумент математических функций |
Инициализация исключений
Вы можете самостоятельно инициировать исключительную ситуацию при выполнении тех или иных действий. Но, хотя синтаксис конструктора объекта Exception похож на конструкторы всех других объектов, создается он по- особенному.
Для этого используется оператор raise, за которым в качестве параметра должен идти экземпляр объекта типа Exception. Обычно сразу за оператором следует конструктор класса ИС:
raise EMathError.Create(' ') ;
но можно и разделить создание и возбуждение исключительной ситуации:
var E: EMathError;
Begin
E := EMathError.Create С');
raise E;
end;
Оператор raise передает созданную исключительную ситуацию ближайшему блоку try. .except (см. ниже).
if С = 0 then
raise EDivByZero.Create('Деление на ноль')
Else
А := В/С;
Самостоятельная инициализация ИС может пригодиться при программировании реакции приложения на ввод данных, для контроля значений переменных и т. д. В таких случаях желательно создавать собственные классы ИС, специально приспособленные для ваших нужд. Также полезно использовать специально спроектированные исключительные ситуации при создании собственных объектов и компонентов. Так, многие важнейшие классы VCL — списки, потоки, графические объекты — сигнализируют о своих (или ваших?) проблемах созданием соответствующей ИС — EListError, EInvalidGraphic, EPrinter и т. д.
Самый важный отличительный признак объекта Exception — это все же класс, к которому он принадлежит. Именно факт принадлежности возникшей ИС к тому или иному классу говорит о том, что случилось. Если же нужно детализировать проблему, можно присвоить значение свойству Message. Если и этого мало, можно добавить в объект новые поля. Так, в ИС EinOutError (ошибка ввода/вывода) есть поле ErrorCode, значение которого соответствует произошедшей ошибке — запрету записи, отсутствию или повреждению файла и т. д.
Try
FileOpen( 'ñ:\myfile.txt', fmOpenWrite);
Except
On E: EinOutError do
Case E.ErrorCode of
ERROR_FILE_NOT_FOUND {=2}: ShowMessage('Файл не найден!');
ERROR_ACCESS_DENIED {=5}: ShowMessage('Доступ запрещен!');
ERROR_DISK_FULL {=112}: ShowMessage ('Диск переполнен!') ;
end;
end;
Впрочем, ИС EInOutError возникают только тогда, когда установлена опция компилятора {$IOCHECKS ON} (или иначе {$I+}). В противном случае проверку переменной IOResult (известной еще по Turbo Pascal) нужно делать самостоятельно.
Защитные конструкции языка Object Pascal
Для работы с объектами исключительных ситуаций существуют специальные конструкции языка Object Pascal— блоки try…except и try…finally. Они контролируют выполнение операторов, помещенных внутри блока до ключевого слова except или finally. В случае возникновения исключительной ситуации штатное выполнение вашей программы немедленно прекращается, и управление передается операторам, идущим за указанными ключевыми словами. Если в вашей процедуре эти блоки отсутствуют, управление все равно будет передано ближайшему блоку, внутри которого возникла ситуация.
Хотя синтаксис двух видов блоков похож, но они принципиально отличаются назначением и решаемыми задачами.
Блок try..except
Для реакции на конкретный тип ситуации применяется блок try..except. Синтаксис его следующий:
Try
<Оператор>;
<Оператор>;
...
Except
On EException1 do
<Оператор>; {обработчик ИС типа EException1}
On EException2 do
<Оператор>;
...
Else
<0ператор> {обработчик прочих ИС}
end;
Выполнение блока начинается с секции try. При отсутствии исключительных ситуаций только она и выполняется. Секция except получает управление в случае возникновения ИС. После обработки происходит выход из защищенного блока, и управление обратно в секцию try не передается; выполняются операторы, стоящие после end.
Если вы хотите обработать любую ИС одинаково, независимо от ее класса, вы можете писать код прямо между операторами except и end. Но если обработка отличается, здесь можно применять набор директив on…do, определяющих реакцию приложения на определенную ситуацию. Каждая директива связывает ситуацию (on...), заданную своим именем класса, с группой операторов (do...).
U := 220.0;
R := 0;
Try
I := U / R;
Except
on EZeroDivide do MessageBox('Короткое замыкание!');
end;
В этом примере замена if…then на try…except, может быть, не дала очевидной экономии кода. Однако если при решении, допустим, вычислительной задачи проверять на возможное деление на ноль приходится не один, а множество раз, то выигрыш от нового подхода неоспорим - достаточно одного блока try…except на все вычисления.
При возникновении ИС директивы просматриваются последовательно, в порядке их описания. Каждый тип исключительной ситуации, описанный после ключевого слова on, обрабатывается именно этим блоком: только то, что предусмотрено в нем, и будет являться реакцией на данную ситуацию.
Если при этом обработчик родительского класса стоит перед дочерним, последний никогда не получит управления.
Try
i:=l;j:=0;
k:=i div j;
Except
on EIntError do ShowMessage('IntError');
on EDivByZero do ShowMessage('DivByZero');
end;
В этом примере, хотя в действительности будет иметь место деление на ноль (EDivByZero), вы увидите сообщение, соответствующее родительскому классу EIntError. Но стоит поменять две конструкции on…do местами, и все придет в норму.
Если возникла ситуация, не определенная ни в одной из директив, выполняются те операторы, которые стоят после ключевого слова else. Если и их нет, то ИС считается не обработанной и будет передана на следующий уровень обработки. Этим следующим уровнем может быть другой оператор try..except, который содержит в себе данный блок.
Блок try...finally
Параллельно с блоком try..except в языке существует и try. .finally. Он соответствует случаю, когда необходимо возвратить выделенные программе ресурсы даже в случае аварийной ситуации. Синтаксис блока try..finally таков:
Try
<Оператор>
<Оператор>
...
Finally
<Оператор>
...
end;
Смысл этой конструкции можно описать одним предложением: операторы, стоящие после finally, выполняются всегда.
Следующие за try операторы исполняются в обычном порядке. Если за это время не возникло никаких ИС, далее следуют те операторы, которые стоят после finally. В случае, если между try и finally произошла ИС, управление немедленно передается на операторы после finally, которые называются кодом очистки. Допустим, вы поместили после try операторы, которые должны выделить вам ресурсы системы (дескрипторы блоков памяти, файлов, контекстов устройств и т. п.). Тогда операторы, освобождающие их, следует поместить после finally, и ресурсы будут освобождены в любом случае. Блок try...finally, как можно догадаться, еще называется блоком защиты ресурсов.
Важно обратить внимание на такой факт: данная конструкция ничего не делает с самим объектом — исключительной ситуацией. Задача try...finally — только прореагировать на факт нештатного поведения программы и проделать определенные действия. Сама же ИС продолжает "путешествие" и вопрос ее обработки остается на повестке дня.
Блоки защиты ресурсов и обработчики ИС, как и другие блоки, могут быть вложенными. В этом простейшем примере каждый вид ресурсов системы защищается в отдельном блоке:
Try
AllocatelstResource;
Try
Allocate2ndResource;
SolveProblem;
Finally
Free2ndResource;
end;
Finally
FreelstResource;
end;
Можно также вкладывать обработчики друг в друга, предусмотрев в каждом специфическую реакцию на ту или иную ошибку:
var i,j,k : Integer;
Begin
i := Round(Random);
j := 1 - i;
Try
k := 1 div i;
Try
k := 1 div j;
Except
On EDivByZero do
ShowMessage('Вариант 1: j=0');
end;
Except
On EDivByZero do
ShowMessage('Вариант 2: i=0');
end;
end;
Но все же идеально правильный случай — это сочетание блоков двух типов. В один из них помещается общее (освобождение ресурсов в finally), в другой - особенное (конкретная реакция внутри except).