Except — Ключевое слово Delphi

Содержание

Блок try..except

Для реакции на конкретный тип ситуации применяется блок try..except. Синтаксис его следующий:

on EExceptionl do ;

on EException2 do ;

Выполнение блока начинается с секции try . При отсутствии исключительных ситуаций только она и выполняется. Секция except получает управление в случае возникновения ИС. После обработки происходит выход из защищенного блока, и управление обратно в секцию try не передается; выполняются операторы, стоящие после end .

Если вы хотите обработать любую ИС одинаково, независимо от ее класса, вы можете писать код прямо между операторами except и end . Но если обработка отличается, здесь можно применять набор директив on. .do, определяющих реакцию приложения на определенную ситуацию. Каждая директива связывает ситуацию (on. ) , заданную своим именем класса, с группой операторов (do. ) .

on EZeroDivide do MessageBox(‘Короткое замыкание!’);

В этом примере замена if. .then на try. .except, может быть, не дала очевидной экономии кода. Однако если при решении, допустим, вычислительной задачи проверять на возможное деление на ноль приходится не один, а множество раз, то выигрыш от нового подхода неоспорим — достаточно одного блока try. .except на все вычисления.

При возникновении ИС директивы просматриваются последовательно, в порядке их описания. Каждый тип исключительной ситуации, описанный после ключевого слова on , обрабатывается именно этим блоком: только то, что предусмотрено в нем, и будет являться реакцией на данную ситуацию.

Если при этом обработчик родительского класса стоит перед дочерним, последний никогда не получит управления.

on EIntError do ShowMessage(‘IntError’);

on EDivByZero do ShowMessage(‘DivByZero’);

В этом примере, хотя в действительности будет иметь место деление на ноль (EDivByZero) , вы увидите сообщение, соответствующее родительскому классу EintError . Но стоит поменять две конструкции on. .do местами, и все придет в норму.

Если возникла ситуация, не определенная ни в одной из директив, выполняются те операторы, которые стоят после else . Если и их нет, то ИС считается не обработанной и будет передана на следующий уровень обработки. Этим следующим уровнем может быть другой оператор try..except, который содержит в себе данный блок.

Мы детально обсудили, что и как помещают в блок try. .except. Но в ряде случаев можно. не размещать там ничего. Пустой блок применяют, когда хотят просто проигнорировать возникновение некоторых ситуаций. Скажем, у вас в программе предусмотрена некая обработка данных, которая может завершиться неудачно, но это не повлияет на дальнейшую работу, и пользователь может об этом не знать. В этой ситуации вполне уместно изолировать ее в пустом блоке try..except. Важно только не поместить туда больше кода, чем нужно — иначе «с водой можно выплеснуть и ребенка».

Если вы не предусмотрели блоков обработки ИС в своем коде, это не должно привести к аварийному завершению всего приложения. Все места в VCL, где управление передается коду разработчика (в том числе, конечно, все обработчики событий всех компонентов), заключены в такие блоки. Но, увы, в Borland не знают о конкретных проблемах вашей программы, и максимум, что они могут сделать для вас, — это проинформировать о типе и месте возникновения ИС. Стандартная обработка подразумевает вывод на экран панели текстового сообщения (из свойства Exception.Message) с указанием типа ошибки. Можно получить и развернутую информацию с именем модуля и адреса, где она имела место (рис. 3.2).

Рис. 3.2. Типовое окно сообщения об ошибке Для этого нужно вызвать процедуру

procedure ShowException(ExceptObject: TObject; ExceptAddr: Pointer);

имеющуюся в модуле SYSUTILS.PAS.

Если предусмотренной вами обработки ИС недостаточно, то можно продолжить ее дальше программно при помощи оператора raise .

Этот оператор уже встречался нам при описании создания пользовательских ИС. Там за ним следовал вызов конструктора ИС. Здесь же конструктор опущен: возбуждаться будет уже существующий объект ИС, приведший нас в блок:

sl:= TStringList. Create;

В этом примере в случае возникновения исключительной ситуации созданный список строк должен быть уничтожен. Сама же обработка предоставляется «вышестоящим инстанциям».

НОВОСТИ ФОРУМА
Рыцари теории эфира
01.10.2020 — 05:20: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]69vJGqDENq4[/Youtube][/center]
[center]14:36[/center]
Osievskii Global News
29 сент. Отправлено 05:20, 01.10.2020 г.’ target=_top>Просвещение от Вячеслава Осиевского — Карим_Хайдаров.
30.09.2020 — 12:51: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Ok]376309070[/Ok][/center]
[center]11:03[/center] Отправлено 12:51, 30.09.2020 г.’ target=_top>Просвещение от Дэйвида Дюка — Карим_Хайдаров.
30.09.2020 — 11:53: ВОСПИТАНИЕ, ПРОСВЕЩЕНИЕ, ОБРАЗОВАНИЕ — Upbringing, Inlightening, Education ->
[center][Youtube]VVQv1EzDTtY[/Youtube][/center]
[center]10:43[/center]

интервью Раввина Борода https://cursorinfo.co.il/all-news/rav.
мой телеграмм https://t.me/peshekhonovandrei
мой твиттер https://twitter.com/Andrey54708595
мой инстаграм https://www.instagram.com/andreipeshekhonow/

[b]Мой комментарий:
Андрей спрашивает: Краснодарская синагога — это что, военный объект?
— Да, военный, потому что имеет разрешение от Росатома на манипуляции с радиоактивными веществами, а также иными веществами, опасными в отношении массового поражения. Именно это было выявлено группой краснодарцев во главе с Мариной Мелиховой.

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

[center][Youtube]CLegyQkMkyw[/Youtube][/center]
[center]10:22 [/center]

Доминико Риккарди: Россию ждёт страшное будущее (хотелки ЦРУ):
https://tainy.net/22686-predskazaniya-dominika-rikardi-o-budushhem-rossii-sdelannye-v-2000-godu.html

Завещание Алена Даллеса / Разработка ЦРУ (запрещено к ознакомлению Роскомнадзором = Жид-над-рус-надзором)
http://av-inf.blogspot.com/2013/12/dalles.html

[center][b]Сон разума народа России [/center]

Иллюстрированный самоучитель по Delphi 7 для профессионалов

Защитные конструкции языка Object Pascal. Блок try..except.

Для работы с объектами исключительных ситуаций существуют специальные конструкции языка Object Pascal– блоки try, except и try..finally. Они контролируют выполнение операторов, помещенных внутри блока до ключевого слова except или finally. В случае возникновения исключительной ситуации штатное выполнение вашей программы немедленно прекращается, и управление передается операторам, идущим за указанными ключевыми словами. Если в вашей процедуре эти блоки отсутствуют, управление все равно будет передано ближайшему блоку, внутри которого возникла ситуация. А уж внутри VCL их предостаточно.

Хотя синтаксис двух видов блоков похож, но они принципиально отличаются назначением и решаемыми задачами. Поэтому вопрос, какой из них выбрать, не стоит: чтение нижеследующего материала, надеемся, убедит вас в пользе обоих.

Блок try..except

Для реакции на конкретный тип ситуации применяется блок try..except. Синтаксис его следующий:

Выполнение блока начинается с секции try. При отсутствии исключительных ситуаций только она и выполняется. Секция except получает управление в случае возникновения ИС. После обработки происходит выход из защищенного блока, и управление обратно в секцию try не передается; выполняются операторы, стоящие после end.

Если вы хотите обработать любую ИС одинаково, независимо от ее класса, вы можете писать код прямо между операторами except и end. Но если обработка отличается, здесь можно применять набор директив on..do, определяющих реакцию приложения на определенную ситуацию. Каждая директива связывает ситуацию (on…), заданную своим именем класса, с группой операторов (do…).

В этом примере замена if..then на try..except, может быть, не дала очевидной экономии кода. Однако если при решении, допустим, вычислительной задачи проверять на возможное деление на ноль приходится не один, а множество раз, то выигрыш от нового подхода неоспорим – достаточно одного блока try..except на все вычисления.

При возникновении ИС директивы просматриваются последовательно, в порядке их описания. Каждый тип исключительной ситуации, описанный после ключевого слова on, обрабатывается именно этим блоком: только то, что предусмотрено в нем, и будет являться реакцией на данную ситуацию.

Если при этом обработчик родительского класса стоит перед дочерним, последний никогда не получит управления.

В этом примере, хотя в действительности будет иметь место деление на ноль (EDivByZero), вы увидите сообщение, соответствующее родительскому классу EintError. Но стоит поменять две конструкции on..do местами, и все придет в норму.

Except — Ключевое слово Delphi

с помощью конструкции try .. exceprion можно экранировать ошибки, а как вывести сообщение об ошибке, скажем в Мемо?

Ну и ищи в хелпе по этим ключевым словам. Находится за секунды и с примером.

try
except On E:Exception
Memo1.Lines.Add (E.Message)
end

Че нить типа такого
try
.
except
on e:exception do
begin
MLog.Lines.Add(«Ошибка вышла «+#+e.Message);
raise Exception.Create(«Ошибка в разтаком-то модуле «+e.Message);
end;
end;

А On здесь зачем?


> А On здесь зачем?

Иначе undeclared identifier: «e»

> YurikGL © (14.06.08 19:40) [5]

Гы. и верно ведь. позор на мои седины. :o)


> Гы. и верно ведь. позор на мои седины. :o)

Вопрос, как к мастеру. насколько корректно
raise Exception.Create(«Ошибка в разтаком-то модуле «+e.Message);
в секции except?
Я использую эту конструкцию для «вытаскивания» всей цепочки ошибок. Т.е. если одна функция вызывает другую, та — третью и т.д. выйдет весь текст ошибки по цепочке.

> YurikGL © (14.06.08 20:49) [7]

Дык. а что ж тут некорректного? Все нормально.

Совет — посмотрите в сторону Assert, бывает очень полезно. Дело в том, что сообщение EAssertionFailed содержит имя модуля и номер строки. То есть, для локализации ошибки можно использовать что-то типа этого:
on E: Exception do
Assert(false, «Ошибка » + E.ClassName + «: » + E.Message);

можно и без on, через ExceptObject


> Юрий Зотов © (14.06.08 21:37) [8]

На E.ClassName ругается, зараза.


> Германн © (15.06.08 01:23) [10]
>
>
> > Юрий Зотов © (14.06.08 21:37) [8]
>
> На E.ClassName ругается, зараза.
>

Был не прав. Не ругается и не зараза.
:)

> Дык. а что ж тут некорректного? Все нормально.

Юрий, раньше Вы меня за такое ругали. А теперь это корректно? ;)

Сам обычно использую такую конструкцию:

function ReCreateEObject(E: Exception; const FuncName: string): Exception;
var
S: string;
begin
S := Format(«%s -> %s», [FuncName, E.Message]);
Result := ExceptClass(E.ClassType).Create(S);
end;

try
.
except
on E: Exception do
begin
.
raise ReCreateEObject(E, «MyFunc»);
end;
end;

> Loginov Dmitry © (15.06.08 10:45) [12]

1. За такое я ругать не мог (потому что это самый обычный способ «ручного» отслеживания и ругать тут не за что). А вот за что-то, хотя внешне и похожее, но по сути другое — мог.

2. Зачем пересоздавать объект исключения, если можно просто изменить его Message?


> Loginov Dmitry © (15.06.08 10:45) [12]

> Сам обычно использую такую конструкцию:

И сейчас будем ругать.
Хотя класс исключения будет тот же, но иная
дополнительная информация будет утеряна.

Сравни

try
raise TMyException.Create(«error»);
except
on E: TMyException do
begin
E.Message := Format(«reraise %s», [E.Message]);
raise;
end;
end;


Regards, LVT.

для системных исключений нельзая изменить текст. Изменение текста катит только для родных дельфийский эксепшенов.


> Loginov Dmitry © (15.06.08 17:42) [15]

> Изменение текста катит только для родных дельфийский эксепшенов.

В дельфи иных исключений и нет.

> В дельфи иных исключений и нет.

procedure TForm1.Button6Click(Sender: TObject);
var
a, b: double;
begin
a := 1;
b := 0;
try
a := a / b;
floattostr(a);
except
on E: Exception do
begin
E.Message := Format(«reraise %s», [E.Message]);
raise;
end;
end;
end;

Куда reraise девается? То-то же!


> Loginov Dmitry © (15.06.08 18:05) [17]

> Куда reraise девается? То-то же!

Ну и не надо никаких on E: Exception.
Если обработчику неизвестно исключение —
он _обязан_ его пропустить.

try except

Проблема с использованием оператора трей

Вот такой оператор нормально работал непосредственно когда я создавал процедуру и запускал ее на выполнение. Теперь когда вызов этой процедуры идет через другие процедуры вложенность порядка 3-х то в итоге получаю Фатал Эррор. Вот такая примерно структура при работе программы:

Также убирал //Exit думая что результаты выполнения операторов после except обнуляються когда выполняеться Exit.
И в серавно ошибка в этой строке.

Где я ошибаюсь? И надо ли делать проверку на исключение по всей иерархической цепочке или достаточно в первом этапе все проверить и спасет ли это от краха:

10.05.2008, 20:10 2

x:=ReadKoord(‘X’,tmp); //************* Вот здесь выводит ОШИБКУ
>>> stsum = ‘X’
try
Result:=StrToFloat(stsum); // >>>StrToFloat(‘X’);
except
ShowMessage(‘Ошибка при попытки преобразования в строке номер ……’);
Exit;

Естественно, ошибка. ‘X’ — это не валидное флоат валуе.

10.05.2008, 23:52 [ТС] 3

x:=ReadKoord(‘X’,tmp); //************* Вот здесь выводит ОШИБКУ
>>> stsum = ‘X’
try
Result:=StrToFloat(stsum); // >>>StrToFloat(‘X’);
except
ShowMessage(‘Ошибка при попытки преобразования в строке номер ……’);
Exit;

Естественно, ошибка. ‘X’ — это не валидное флоат валуе.

Нет сдесь stsum не равно икс ( stsum = ‘X’) так сделано что stsum сумирует символы после символа «Х» или «У» или любова другова пока эти символы пробел запятая точка числа одним словом выделяем координаты из подобной страки «X 25.25 DFU Y 25.74 M4»

У меня все работает нормально ошибок в коде нет если файл действительно евляеться файлом автокада или Тефлекса то все проходит замечательно. Я уже расматриваю исключительные ситуации когда формат файла не соответствует стандартному.
Тоесть допустик чудесным образом мы получаем такую строку
X 5,,52 или Y 7,5,528 или Y 85 34,18
Теперь
StrToFloat(5,,52) — Ошибка ; StrToFloat(85 34,18) — Ошибка ;

Если задать переменную tmp=»85 34,18″
try
Result:=StrToFloat(tmp);
except
ShowMessage(‘Ошибка при попытки преобразования в строке номер ……’);
Exit;
end;
и теперь непосредствено по клику кнопки Бутон1 вызвать обработчик то он выдаст исключительную ситуацию и предотвратит крах программы.
Теперь если я эту процедуру ReadKoord — (онализ отдельной строки) вызываю процедурой ReadNC1 (Чтение всего файла), которая всвою очередь вызываеться после закрытия диолога о выборе читаймого файла, То это исключительное событие не наступает и он мне показывает ошибку в строке Result:=StrToFloat(stsum);

Сейчас я зделал так и всеравно сначала идет ошибка потом если нажимаю «Продолжить» а не брейк только после этого вылазиет сообщение
ShowMessage(‘Ошибка при попытке преобразования символов [пробел, или пустое место] в действительное число, стоящих за строкой Х (Y));

Текст дословный;
try
Result:=StrToFloat(stsum);
except
begin
Result:=1;
ShowMessage(‘Ошибка при попытке преобразования символов ‘ +stsum +’ в действительное число, стоящих за строкой ‘ +subst);
//Exit;
end;
end;

Все верно только присвоения x:=ReadKoord(‘X’,tmp); >>> x:=’ ‘ или » а если точнее я даже не знаю что мы имеем на выходе функции ReadKoord в исключительной ситуации наступает раньше чем исключительная ситуация вот вчем праблема.

Должно быть «Ошибка преобразования» Присвоеть Result:=1; и двигаться дальше тоесть все должно сработать в исключительной ситуации кординатм Х У присвоеться значение 1 и завалить меня окошками ShowMessage. Закоментировал даже ShowMessage мол может непереходит к следующему шагу пока не закраю осталось только это и всеравно событие x:=ReadKoord наступает раньше чем исключительное.

try
Result:=StrToFloat(stsum);
except
begin
Result:=1;
end;
end;

Добавлено через 12 минут
try
Result:=StrToFloat(stsum);
except
Result:=33333;
end;

Величина 33333 заноситься в базу данных тоесть событие наступает но почемуто позже чем x:=ReadKoord(‘X’,tmp) = Result:=StrToFloat(stsum) = ‘ ‘ или » — Ошибка;

Программирование на языке Delphi

Глава 4. Исключительные ситуации и надежное программирование


Авторы: А.Н. Вальвачев
К.А. Сурков
Д.А. Сурков
Ю.М. Четырько

Опубликовано: 26.11.2005
Исправлено: 10.12.2020
Версия текста: 1.0

Когда программист после компиляции получает готовый к исполнению файл, он искренне верит, что программа будет работать именно так, как он хочет. Пока она в его заботливых руках, так оно обычно и бывает. Когда же программа попадает в более суровые условия — к новому пользователю и на другой компьютер — с ней может произойти все, что угодно. “Новый хозяин“ может вместо ожидаемых цифр ввести буквы, извлечь корень из отрицательного числа, делить на ноль и выполнять множество других необдуманных, часто случайных действий. Особенно это касается интерактивных (диалоговых) приложений, а таких — громадное большинство. Из этого следует, что программист должен организовать мощную оборону от всех посягательств на жизнедеятельность своей программы в процессе ее выполнения. О том, как это сделать, рассказывается в этой главе.

4.1. Ошибки и исключительные ситуации

Вы должны отдавать себе отчет в том, что в любом работающем приложении могут происходить ошибки. Причины этих ошибок бывают разными. Некоторые из них носят субъективный характер и вызваны неграмотными действиями программиста. Но существуют и объективные ошибки, их нельзя избежать при проектировании программы, но можно обнаружить во время ее работы. Примеров таких ошибок сколько угодно: недостаточный объем свободной памяти, отсутствие файла на диске, выход значений исходных данных из допустимого диапазона и т.д.

Хорошая программа должна справляться со своими ошибками и работать дальше, не зацикливаясь и не зависая ни при каких обстоятельствах. Для обработки ошибок можно, конечно, пытаться использовать структуры вида if then Exit. Однако в этом случае ваш стройный и красивый алгоритм решения основной задачи обрастет уродливыми проверками так, что через неделю вы сами в нем не разберетесь. Из этой почти тупиковой ситуации среда Delphi предлагает простой и элегантный выход — механизм обработки исключительных ситуаций.

Исключительная ситуация (exception) — это прерывание нормального хода работы программы из-за невозможности правильно выполнить последующие действия.

Представим, что подпрограмма выделяет область динамической памяти и загружает в нее содержимое некоторого файла. Если в системе окажется недостаточно памяти, то данные будет негде разместить и попытка загрузить файл приведет к ошибке. Скорее всего, вся программа будет аварийно завершена из-за того, что оператор загрузки данных обратится по недоступному для программы адресу. Как этого избежать? При обнаружении проблемы подпрограмма должна создать исключительную ситуацию — прервать нормальный ход своей работы и передать управление тем операторам, которые смогут обработать ошибку. Как правило, операторы обработки исключительных ситуаций находятся в одной из вызывающих подпрограмм.

Механизм обработки исключительных ситуаций лучше всего подходит для взаимодействия программы с библиотекой подпрограмм. Подпрограммы библиотеки обнаруживают ошибки, но в большинстве случаев не знают, как на них реагировать. Вызывающая программа, наоборот, знает, что делать при возникновении ошибок, но, как правило, не умеет их своевременно обнаруживать. Благодаря механизму обработки исключительных ситуаций обеспечивается связь между библиотекой и использующей ее программой при обработке ошибок.

Механизм обработки исключительных ситуаций довольно сложен в своей реализации, но для программиста он прост и прозрачен. Для его использования в язык Delphi введены специальные конструкции try . except . end , try . finally . end и оператор raise , рассмотренные в этой главе.

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

Исключительные ситуации в языке Delphi описываются классами. Каждый класс соответствует определенному типу исключительных ситуаций. Когда в программе возникает исключительная ситуация, создается объект соответствующего класса, который переносит информацию об этой ситуации из места возникновения в место обработки.

Классы исключительных ситуаций образуют иерархию, корнем которой является класс Exception . Класс Exception описывает самый общий тип исключительных ситуаций, а его наследники — конкретные виды таких ситуаций (таблица 4.1). Например, класс EOutOfMemory порожден от Exception и описывает ситуацию, когда свободная оперативная память исчерпана.

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

Класс исключительных ситуаций Описание
EAbort «Безмолвная» исключительная ситуация, используемая для выхода из нескольких уровней вложенных блоков или подпрограмм. При этом на экран не выдается никаких сообщений об ошибке. Для генерации исключительной ситуации класса EAbort нужно вызвать стандартную процедуру Abort.
EInOutError Ошибка доступа к файлу или устройству ввода-вывода. Код ошибки содержится в поле ErrorCode.
EExternal Исключительная ситуация, возникшая вне программы, например, в операционной системе.
EExternalException Исключительная ситуация, возникшая за пределами программы, например в DLL-библиотеке, разработанной на языке C++.
EHeapException Общий класс исключительных ситуаций, возникающих при работе с динамической памятью. Является базовым для классов EOutOfMemory и EInvalidPointer.Внимание! Создание исключительных ситуаций этого класса (и всех его потомков) полностью берет на себя среда Delphi, поэтому никогда не создавайте такие исключительные ситуации с помощью оператора raise .
EOutOfMemory Свободная оперативная память исчерпана (см. EHeadException).
EInvalidPointer Попытка освободить недействительный указатель (см. EHeadException). Обычно это означает, что указатель уже освобожден.
EIntError Общий класс исключительных ситуаций целочисленной арифметики, от которого порождены классы EDivByZero, ERangeError и EIntOverflow.
EDivByZero Попытка деления целого числа на нуль.
ERangeError Выход за границы диапазона целого числа или результата целочисленного выражения.
EIntOverflow Переполнение в результате целочисленной операции.
EMathError Общий класс исключительных ситуаций вещественной математики, от которого порождены классы EInvalidOp, EZeroDivide, EOverflow и EUnderflow.
EInvalidOp Неверный код операции вещественной математики.
EZeroDivide Попытка деления вещественного числа на нуль.
EOverflow Потеря старших разрядов вещественного числа в результате переполнения разрядной сетки.
EUnderflow Потеря младших разрядов вещественного числа в результате переполнения разрядной сетки.
EInvalidCast Неудачная попытка приведения объекта к другому классу с помощью оператора as .
EConvertError Ошибка преобразования данных с помощью функций IntToStr, StrToInt, StrToFloat, StrToDateTime.
EVariantError Невозможность преобразования варьируемой переменной из одного формата в другой.
EAccessViolation Приложение осуществило доступ к неверному адресу в памяти. Обычно это означает, что программа обратилась за данными по неинициализированному указателю.
EPrivilege Попытка выполнить привилегированную инструкцию процессора, на которую программа не имеет права.
EStackOverflow Стек приложения не может быть больше увеличен.
EControlC Во время работы консольного приложения пользователь нажал комбинацию клавиш Ctrl+C.
EAssertionFailed Возникает при вызове процедуры Assert, когда первый параметр равен значению False.
EPackageError Проблема во время загрузки и инициализации библиотеки компонентов.
EOSError Исключительная ситуация, возникшая в операционной системе.
Таблица 4.1. Классы исключительных ситуаций

Наследование классов позволяет создавать семейства родственных исключительных ситуаций. Примером такого семейства являются классы исключительных ситуаций вещественной математики, которые объявлены в модуле SysUtils следующим образом.

Класс исключительных ситуаций EMathError является базовым для классов EInvalidOp, EZeroDivide, EOverflow и EUnderflow, поэтому, обрабатывая исключительные ситуации класса EMathError, вы будете обрабатывать все ошибки вещественной математики, включая EInvalidOp, EZeroDivide, EOverflow и EUnderflow.

Нетрудно заметить, что имена классов исключений начинаются с буквы E (от слова Exception). Этого правила полезно придерживаться при объявлении собственных классов исключений, например:

Как описываются классы исключительных ситуаций понятно, рассмотрим теперь, как такие ситуации обрабатываются.

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


4.3.1. Создание исключительной ситуации

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

Данный оператор создает объект класса EOutOfMemory (класс ошибок исчерпания памяти) и прерывает нормальное выполнение программы. Вызывающие подпрограммы могут эту исключительную ситуацию перехватить и обработать. Для этого в них организуется так называемый защищенный блок :

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

Рисунок 4.1. Логика работы оператора try…except…end

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

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

4.3.2. Распознавание класса исключительной ситуации

Распознавание класса исключительной ситуации выполняется с помощью конструкций

которые записываются в секции обработки исключительной ситуации, например:

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

Обратите внимание, что порядок операторов on имеет значение, поскольку распознавание исключительных ситуаций должно происходить от частных классов к общим классам, иначе говоря, от потомков к предкам. С чем это связано? Сейчас поймете. Представьте, к чему приведет изменение порядка операторов on в примере выше, если принять во внимание, что класс EMathError является базовым для EZeroDivide . Ответ простой: обработчик EMathError будет поглощать все ошибки вещественной математики, в том числе EZeroDivide , в результате обработчик EZeroDivide никогда не выполнится.

На самом высоком уровне программы бывает необходимо перехватывать все исключительные ситуации, чтобы в случае какой-нибудь неучтенной ошибки корректно завершить приложение. Для этого применяется так называемый обработчик по умолчанию (default exception handler). Он записывается в секции except после всех операторов on и начинается ключевым словом else :

Учтите, что отсутствие части else соответствует записи else raise , которое нет смысла использовать явно. Мы со своей стороны вообще не советуем вам пользоваться обработкой исключительных ситуаций по умолчанию, поскольку все ваши приложения будут строиться, как правило, на основе библиотеки VCL, в которой обработка по умолчанию уже предусмотрена.

4.3.3. Пример обработки исключительной ситуации

В качестве примера обработки исключительной ситуации рассмотрим две функции: StringToCardinal и StringToCardinalDef.

Функция StringToCardinal выполняет преобразование строки в число с типом Cardinal. Если преобразование невозможно, функция создает исключительную ситуацию класса EConvertError.

Функция StringToCardinalDef также выполняет преобразование строки в число с типом Cardinal, но в отличие от функции StringToCardinal она не создает исключительную ситуацию. Вместо этого она позволяет задать значение, которое возвращается в случае неудачной попытки преобразования:

Для преобразования исходной строки в число используется определенная выше функция StringToCardinal. Если при преобразовании возникает исключительная ситуация, то она «поглощается» функцией StringToCardinalDef, которая в этом случае возвращает значение параметра Default. Если происходит какая-нибудь другая ошибка (не EConvertError), то управление передается внешнему блоку обработки исключительных ситуаций, из которого была вызвана функция StringToCardinalDef.

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

4.3.4. Возобновление исключительной ситуации

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

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

4.3.5. Доступ к объекту, описывающему исключительную ситуацию

При обработке исключительной ситуации может потребоваться доступ к объекту, описывающему эту ситуацию и содержащему код ошибки, текстовое описание ошибки и т.д. В этом случае используется расширенная запись оператора on :

Например, объект исключительной ситуации нужен для того, чтобы выдать пользователю сообщение об ошибке:

Переменная E — это объект исключительной ситуации, ShowMessage — процедура модуля DIALOGS, отображающая на экране небольшое окно с текстом и кнопкой OK. Свойство Message типа string определено в классе Exception , оно содержит текстовое описание ошибки. Исходное значение для текста сообщения указывается при конструировании объекта исключительной ситуации.

Обратите внимание, что после обработки исключительной ситуации освобождение соответствующего объекта выполняется автоматически, вам этого делать не надо.

4.4. Защита выделенных ресурсов от пропадания


4.4.1. Утечка ресурсов и защита от нее

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

Представьте ситуацию: подпрограмма распределяет некоторый ресурс, но исключительная ситуация прерывает ее выполнение, и ресурс остается не освобожденным. Даже подумать страшно, к чему может привести такая ошибка: утечка памяти, файловых дескрипторов, других ресурсов операционной системы. Следовательно, ресурсы нуждаются в защите от исключительных ситуаций. Для этого в среде Delphi предусмотрен еще один вариант защищенного блока:

Особенность этого блока состоит в том, что секция finally . end выполняется всегда независимо от того, происходит исключительная ситуация или нет. Если какой-либо оператор секции try . finally генерирует исключительную ситуацию, то сначала выполняется секция finally . end , называемая секцией завершения (освобождения ресурсов), а затем управление передается внешнему защищенному блоку. Если все защищаемые операторы выполняются без ошибок, то секция завершения тоже работает, но управление передается следующему за ней оператору. Обратите внимание, что секция finally . end не обрабатывает исключительную ситуацию, в ней нет ни средств ее обнаружения, ни средств доступа к объекту исключительной ситуации.

Рисунок 4.2. Логика работы оператора try…except…end

Блок try . finally . end обладает еще одной важной особенностью. Если он помещен в цикл, то вызов из защищенного блока процедуры Break с целью преждевременного выхода из цикла или процедуры Continue с целью перехода на следующую итерацию цикла сначала обеспечивает выполнение секции finally . end , а затем уже выполняется соответствующий переход. Это утверждение справедливо также и для процедуры Exit (выход из подпрограммы).

Как показывает практика, подпрограммы часто распределяют сразу несколько ресурсов и используют их вместе. В таких случаях применяются вложенные блоки try . finally . end :

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

4.5. Итоги

В этой главе вы узнали многое об исключительных ситуациях и способах борьбы с ними. Теперь ваши программы наверняка дадут достойный отпор не только самому неотесанному пользователю, но и его деревянному компьютеру. Это, кстати говоря, одно из необходимых качеств, которые позволят отнести вашу программу к классу хороших. Позволим себе также напомнить, что здесь были рассмотрены только ошибки времени выполнения, поэтому не забудьте прочитать гл.10, где рассказано о борьбе с логическими ошибками.

Блог GunSmoker-а

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

27 апреля 2010 г.

Новый класс Exception в Delphi 2009 и выше

Начиная с Delphi 2009, класс Exception, который был неизменным с самого первого выпуска Delphi, получил новые поля и свойства. А значит — и новые возможности. Поэтому, я хотел бы сделать обзор новых возможностей и показать, как их можно использовать.

Итак, раньше Exception был очень простым объектом всего с двумя свойствами:
По сути, нам доступно только текстовое описание исключения (HelpContext, который должен содержать ID темы в справке, на практике не используется). Разумеется, мы можем объявить свой пользовательский класс, в котором мы можем добавить какие угодно свойства, но разве не было бы замечательно, если бы штатные исключения предоставляли бы чуть больше возможностей? Например, информацию о предыдущем исключении?

Так или иначе, но в Delphi 2009 класс Exception был (наконец-то!) расширен и обзавёлся такими свойствами:
Все новые свойства принадлежат одной из двух новых возможностей:

  • Поддержке вложенных исключений
  • Поддержке диагностики исключений

Давайте разберёмся с ними по очереди.

Вложенные исключения

Вложенное исключение (его ещё называют chained-исключение) — это ситуация, когда у вас возникает новое исключение в момент обработки какого-либо исключения (в блоках finally или except). Если у вас нет поддержки вложенных исключений (как в Delphi до 2009), то вы теряете исходное исключение, оставляя только самое последнее. Иногда, это то, что вы хотите сделать, иногда — нет.

Два примера. Первый:
Мне кажется, что пример достаточно прозрачен. Мы генерируем ошибку верхнего уровня (ESomeClassSaveError) по ошибке низкого уровня (это может быть тривиальный EStreamError из-за нехватки места или же index out of range из-за повреждений внутреннего состояния объекта). В любом случае, пользователь получит доступное описание ситуации — что и было нашей целью. Обратите внимание, что информация о исходной проблеме утеряна. Исключение более высокого уровня скрыло предыдущее. В этом случае мы возбудили исключение сами, намеренно. В следующем примере это будет неожиданно.

Пример два:
В данном примере, мы уже ничего не планируем. У нас появляется второе исключение в деструкторе (что есть плохая практика). Это какой-то тривиальный access violation из-за того, что мы не ожидали каких-либо условий. Хотя в этом случае второе исключение тоже скрывает первое, но ценная информация может теряться, а может и нет — смотря по тому, в чём же конкретно был баг в этой ситуации.

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

В новом классе Exception (да уж, это заняло немало времени, но надо же было потратить его на введение во вложенные исключения — многие просто не знакомы с этим понятием) у нас появились свойства InnerException и BaseException. Оба эти свойства устанавливаются (управляются) автоматически модулем SysUtils. Вы можете их читать и использовать. InnerException предоставляет вам вложенное исключение. BaseException — самое первое исключение, с которого и началась цепочка исключений. Если исключений в цепочке два, то InnerException равно BaseException. Если исключение всего одно, то оба свойства равны nil.

По-умолчанию, вложенные исключения не запоминаются. Чтобы сохранить вложенное исключение, вам нужно возбудить его через Exception.RaiseOuterException (стиль Delphi) или Exception.ThrowOuterException (стиль C++ Builder). Например:
После выполнения этого примера мы получим исключение класса ESomeClassSaveError, у которого в InnerException будет сидеть конкретная ошибка сохранения в поток (EStreamError или что там у нас было).

Во втором примере (с деструктором) — поскольку RaiseOuterException не используется, то InnerException будет nil.

Как связана поддержка вложенных исключений с показом сообщений? Ну, свойство Message неизменно — это свойство только текущего исключения. Поэтому, любой код, который не в курсе про вложенные исключения, будет показывать только сообщение (единственное) для последнего исключения. А вот метод ToString класса Exception покажет вам всю цепочку вызовов — по исключению на строчку (понятно, что в случае единственного исключения, ToString равно Message). С другой стороны, несколько странно выглядит показ сообщения в Application.ShowException: этот метод показывает сообщение от BaseException — вероятно, это не то, что вы бы хотели (в нашем примере выше мы хотели показать ‘Ошибка сохранения в поток’). Поэтому, я подозреваю, что вы захотите сделать свой обработчик Application.OnException, чтобы изменить это поведение. Например:
Далее, лично мне не очень понятно, почему разработчики Delphi не сделали авто-захват вложенных исключений во всех случаях.

[Обновлено 2020.10.22]: Пример ниже не будет работать корректно в случае если в вашей программе есть блоки try внутри блоков finally / except , см. обсуждение ниже.

Если вы хотите сделать авто-захват вложенных исключений во всех случаях, то вам нужно подключить к вашей программе такой модуль (внимание: это хак; подробнее о внутреннем механизме InnerException можно почитать тут):
После подключения этого модуля наш второй пример также станет собирать InnerException, а в первом примере можно будет использовать как Exception.RaiseOuterException, так и просто raise.

Диагностика исключений

Ну, если вы читаете мой блог, то с понятием диагностики исключений и стека вызовов вы должны быть уже знакомы, поэтому я пропущу введение. С другой стороны, чтобы показать место/цель нововведений класса Exception, я кратко опишу архитектуру трейсеров исключений с самого начала.

Программа — это набор машинных команд, т.е. чисел. По-умолчанию, в программе нет никакого текста программы. Поэтому, построить стек штатными средствами — невозможно. И нужно использовать стороннее решение, которое делает следующее:

  • Добавляет в скомпилированный модуль отладочную информацию (соответствие машинных инструкций тексту программы) в общеизвестном или приватном формате. Чаще всего, она добавляется как ресурс RC_DATA или секция PE.
  • Устанавливает hook на возникновение исключений (на какую-либо функцию, которая вызывается всегда при возникновении исключений. Например, RaiseException из Kernel32). Обычно это патчинг чего-либо (таблицы импорта или секции кода).
  • В ловушке исключений строит стек, используя какой-либо алгоритм трассировки стека, вручную проходясь по машинному стеку и вылавливая из него адреса вызовов функций. Вы также можете использовать Майкросовтовский Debug Help API.

Если первый и последний пункт представляют собой красивое и документированное решение, то пункт два — это грязные хаки. Поэтому, чтобы упростить нам жизнь, разработчики Delphi добавили архитектуру поддержки трейсеров исключений (немного поздно, но всё же лучше поздно, чем никогда). Всё, что она делает — позволяет вам вызвать свою процедуру в момент возникновения исключения, убирая необходимость в ручном патчинге. Ничего больше она не делает. Иными словами, никакого стека вызовов «из коробки» вы не получите.

Ещё раз: это возможность предназначена для разработчиков трейсеров исключений. Есть подозрение, что она была добавлена в Delphi в преддверие перехода на Mac OS и Linux с целью унификации кода.

Поскольку уже написанные трейсеры исключений не используют эту возможность (да и не могут её использовать, потому что они работают и в тех версиях Delphi, где её нет), то вам надо использовать их возможности по получению стека вызовов. Например, для JCL это будет вызов JclLastExceptStackList, а для EurekaLog — GetLastExceptionCallStack.

Однако, вы можете интегрировать любой существующий трейсер (или написать свой) в эту новую архитектуру. Если написание своего — это достаточно сложная задача, то интегрировать уже существующий — это дело пары минут. Если при этом трейсер чётко состоит из нескольких частей, то, интегрировав его в эту архитектуру, вы можете не подключать ту его часть, которая ответственна за хуки.

Итак, если вы решили, что вам это надо, то вот краткое описание с примером для джедаев и EurekaLog.

Во-первых, надо понимать, что вышеуказанная поддержка касается двух модулей — System и SysUtils. Как и с другими возможностями по исключениям, весь базовый функционал заключён в модуле System. Модуль SysUtils является лишь удобной обёрткой к System. Для этого System выставляет наружу некоторые события (ExceptProc, ErrorProc, ExceptClsProc, ExceptObjProc, RaiseExceptionProc, RTLUnwindProc, RaiseExceptObjProc, ExceptionAcquired, ExceptionClass, SafeCallErrorProc, AssertErrorProc и AbstractErrorProc), которые и использует модуль SysUtils. Вам не следует использовать их напрямую, если только вы не отказались от модуля SysUtils. Вместо использования событий модуля System, вы используете модуль SysUtils. Более подробно события модуля System рассматриваются тут (пункт 10, обсуждение модуля System).

Итак, что же тогда нам предлагает модуль SysUtils? А модуль SysUtils предлагает нам новый класс Exception, в котором появились события GetExceptionStackInfoProc, GetStackInfoStringProc и CleanUpStackInfoProc. По-умолчанию, они не назначены — да и их некому реализовывать, т.к., как я уже сказал, в программе по-умолчанию просто нет информации для этого.

Значит, нам надо их реализовать. Поскольку мы пишем просто обёртку к уже существующему трейсеру, то всё, что нам надо будет сделать — вызвать подходящую функцию трейсера. Например:
Достаточно добавить этот модуль в uses (чем раньше — тем лучше) и вы волшебным образом получаете свой стек:
Для кода:
Пример вывода будет (с вложенными исключениями):
Обратите внимание, что в этом примере не используется хукинг исключений средствами JCL (JclHookExcept). Всё работает и без него. Мы используем только возможности JCL по чтению отладочной информации и трасировке стека.

Аналогичный модуль для EurekaLog (применимо для EurekaLog 6 и ниже; EurekaLog 7 и выше уже интегрируется с новым классом Exception):
Вариант модуля для madExcept я оставляю вам в качестве домашнего задания ;)

Delphi-Help

Except

Except

Описание

Ключевое слово Except используется для отметки начала блока инструкций, которые обрабатывают исключение в предложении Try. Если блок Except может обработать исключение, то программа не заканчивается.
Except имеет два различных синтаксиса:
Версия 1
В этой версии, если предложение Try генерирует исключение, предложение Except выполняется. Это используется для предпринятия альтернативного действия, когда что-нибудь неожиданно идет не так, как надо. Однако, предложение except не может определить тип ошибки.
Версия 2
Она подобна 1 версии, но определяет различные действия для различных типов исключений, типа EInOutError. Предложение Else может использоваться как перехватчик для всех неожиданных типов исключений. Общий тип исключения Exception может использоваться для захвата всех типов исключений.
Назначение Имени (Name) исключения, текста сообщения исключения (Name.Message) может использоваться для отображения в сообщении или других использований.
Когда исключение поднимается во 2 версии 2, то если исключение не обработано инструкциями On или Else, то выполняется проверка, находимся ли мы во вложенном блоке Try. Если да, то обрабатывается пункт Except своего родительского Try. Если нет или пункт Else не был найден, программа завершается.
Предложение Else, в действительности, не является необходимым — лучше использовать On E:Exception Do, для универсальной обработки особых ситуаций, так как оно обеспечивает сообщение об ошибках (E.Message).
Важно: Вы можете определить тип ошибки, которая произошла, используя универсальную обработку особых ситуаций — On E:Exception Do. Где E — указатель на объект исключения, который создан условием исключения. E.ClassName дает тип исключения, типа ‘EDivByZero’, как показано в конечном коде примера.

Пример кода

Пример кода : Деление на ноль с простым блоком Except

var
number, zero : Integer;
begin
// Попытка делить целое число на нуль — чтобы поднять исключение
Try
zero := 0;
number := 1 div zero;
ShowMessage(‘number / zero = ‘+IntToStr(number));
Except
ShowMessage(‘Неизвестная ошибка’);
end;
end;

Пример кода : Деление на ноль с предложением Except On

var
number, zero : Integer;
begin
// Попытка делить целое число на нуль — чтобы поднять исключение
Try
zero := 0;
number := 1 div zero;
ShowMessage(‘number / zero = ‘+IntToStr(number));
Except
on E : Exception do
ShowMessage(E.ClassName+’ ошибка с сообщением : ‘+E.Message);
end;
end;

EDivByZero ошибка с сообщением :Division by zero

Блог GunSmoker-а

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

27 апреля 2010 г.

Новый класс Exception в Delphi 2009 и выше

Начиная с Delphi 2009, класс Exception, который был неизменным с самого первого выпуска Delphi, получил новые поля и свойства. А значит — и новые возможности. Поэтому, я хотел бы сделать обзор новых возможностей и показать, как их можно использовать.

Итак, раньше Exception был очень простым объектом всего с двумя свойствами:
По сути, нам доступно только текстовое описание исключения (HelpContext, который должен содержать ID темы в справке, на практике не используется). Разумеется, мы можем объявить свой пользовательский класс, в котором мы можем добавить какие угодно свойства, но разве не было бы замечательно, если бы штатные исключения предоставляли бы чуть больше возможностей? Например, информацию о предыдущем исключении?

Так или иначе, но в Delphi 2009 класс Exception был (наконец-то!) расширен и обзавёлся такими свойствами:
Все новые свойства принадлежат одной из двух новых возможностей:

  • Поддержке вложенных исключений
  • Поддержке диагностики исключений

Давайте разберёмся с ними по очереди.

Вложенные исключения

Вложенное исключение (его ещё называют chained-исключение) — это ситуация, когда у вас возникает новое исключение в момент обработки какого-либо исключения (в блоках finally или except). Если у вас нет поддержки вложенных исключений (как в Delphi до 2009), то вы теряете исходное исключение, оставляя только самое последнее. Иногда, это то, что вы хотите сделать, иногда — нет.

Два примера. Первый:
Мне кажется, что пример достаточно прозрачен. Мы генерируем ошибку верхнего уровня (ESomeClassSaveError) по ошибке низкого уровня (это может быть тривиальный EStreamError из-за нехватки места или же index out of range из-за повреждений внутреннего состояния объекта). В любом случае, пользователь получит доступное описание ситуации — что и было нашей целью. Обратите внимание, что информация о исходной проблеме утеряна. Исключение более высокого уровня скрыло предыдущее. В этом случае мы возбудили исключение сами, намеренно. В следующем примере это будет неожиданно.

Пример два:
В данном примере, мы уже ничего не планируем. У нас появляется второе исключение в деструкторе (что есть плохая практика). Это какой-то тривиальный access violation из-за того, что мы не ожидали каких-либо условий. Хотя в этом случае второе исключение тоже скрывает первое, но ценная информация может теряться, а может и нет — смотря по тому, в чём же конкретно был баг в этой ситуации.

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

В новом классе Exception (да уж, это заняло немало времени, но надо же было потратить его на введение во вложенные исключения — многие просто не знакомы с этим понятием) у нас появились свойства InnerException и BaseException. Оба эти свойства устанавливаются (управляются) автоматически модулем SysUtils. Вы можете их читать и использовать. InnerException предоставляет вам вложенное исключение. BaseException — самое первое исключение, с которого и началась цепочка исключений. Если исключений в цепочке два, то InnerException равно BaseException. Если исключение всего одно, то оба свойства равны nil.

По-умолчанию, вложенные исключения не запоминаются. Чтобы сохранить вложенное исключение, вам нужно возбудить его через Exception.RaiseOuterException (стиль Delphi) или Exception.ThrowOuterException (стиль C++ Builder). Например:
После выполнения этого примера мы получим исключение класса ESomeClassSaveError, у которого в InnerException будет сидеть конкретная ошибка сохранения в поток (EStreamError или что там у нас было).

Во втором примере (с деструктором) — поскольку RaiseOuterException не используется, то InnerException будет nil.

Как связана поддержка вложенных исключений с показом сообщений? Ну, свойство Message неизменно — это свойство только текущего исключения. Поэтому, любой код, который не в курсе про вложенные исключения, будет показывать только сообщение (единственное) для последнего исключения. А вот метод ToString класса Exception покажет вам всю цепочку вызовов — по исключению на строчку (понятно, что в случае единственного исключения, ToString равно Message). С другой стороны, несколько странно выглядит показ сообщения в Application.ShowException: этот метод показывает сообщение от BaseException — вероятно, это не то, что вы бы хотели (в нашем примере выше мы хотели показать ‘Ошибка сохранения в поток’). Поэтому, я подозреваю, что вы захотите сделать свой обработчик Application.OnException, чтобы изменить это поведение. Например:
Далее, лично мне не очень понятно, почему разработчики Delphi не сделали авто-захват вложенных исключений во всех случаях.

[Обновлено 2020.10.22]: Пример ниже не будет работать корректно в случае если в вашей программе есть блоки try внутри блоков finally / except , см. обсуждение ниже.

Если вы хотите сделать авто-захват вложенных исключений во всех случаях, то вам нужно подключить к вашей программе такой модуль (внимание: это хак; подробнее о внутреннем механизме InnerException можно почитать тут):
После подключения этого модуля наш второй пример также станет собирать InnerException, а в первом примере можно будет использовать как Exception.RaiseOuterException, так и просто raise.

Диагностика исключений

Ну, если вы читаете мой блог, то с понятием диагностики исключений и стека вызовов вы должны быть уже знакомы, поэтому я пропущу введение. С другой стороны, чтобы показать место/цель нововведений класса Exception, я кратко опишу архитектуру трейсеров исключений с самого начала.

Программа — это набор машинных команд, т.е. чисел. По-умолчанию, в программе нет никакого текста программы. Поэтому, построить стек штатными средствами — невозможно. И нужно использовать стороннее решение, которое делает следующее:

  • Добавляет в скомпилированный модуль отладочную информацию (соответствие машинных инструкций тексту программы) в общеизвестном или приватном формате. Чаще всего, она добавляется как ресурс RC_DATA или секция PE.
  • Устанавливает hook на возникновение исключений (на какую-либо функцию, которая вызывается всегда при возникновении исключений. Например, RaiseException из Kernel32). Обычно это патчинг чего-либо (таблицы импорта или секции кода).
  • В ловушке исключений строит стек, используя какой-либо алгоритм трассировки стека, вручную проходясь по машинному стеку и вылавливая из него адреса вызовов функций. Вы также можете использовать Майкросовтовский Debug Help API.

Если первый и последний пункт представляют собой красивое и документированное решение, то пункт два — это грязные хаки. Поэтому, чтобы упростить нам жизнь, разработчики Delphi добавили архитектуру поддержки трейсеров исключений (немного поздно, но всё же лучше поздно, чем никогда). Всё, что она делает — позволяет вам вызвать свою процедуру в момент возникновения исключения, убирая необходимость в ручном патчинге. Ничего больше она не делает. Иными словами, никакого стека вызовов «из коробки» вы не получите.

Ещё раз: это возможность предназначена для разработчиков трейсеров исключений. Есть подозрение, что она была добавлена в Delphi в преддверие перехода на Mac OS и Linux с целью унификации кода.

Поскольку уже написанные трейсеры исключений не используют эту возможность (да и не могут её использовать, потому что они работают и в тех версиях Delphi, где её нет), то вам надо использовать их возможности по получению стека вызовов. Например, для JCL это будет вызов JclLastExceptStackList, а для EurekaLog — GetLastExceptionCallStack.

Однако, вы можете интегрировать любой существующий трейсер (или написать свой) в эту новую архитектуру. Если написание своего — это достаточно сложная задача, то интегрировать уже существующий — это дело пары минут. Если при этом трейсер чётко состоит из нескольких частей, то, интегрировав его в эту архитектуру, вы можете не подключать ту его часть, которая ответственна за хуки.

Итак, если вы решили, что вам это надо, то вот краткое описание с примером для джедаев и EurekaLog.

Во-первых, надо понимать, что вышеуказанная поддержка касается двух модулей — System и SysUtils. Как и с другими возможностями по исключениям, весь базовый функционал заключён в модуле System. Модуль SysUtils является лишь удобной обёрткой к System. Для этого System выставляет наружу некоторые события (ExceptProc, ErrorProc, ExceptClsProc, ExceptObjProc, RaiseExceptionProc, RTLUnwindProc, RaiseExceptObjProc, ExceptionAcquired, ExceptionClass, SafeCallErrorProc, AssertErrorProc и AbstractErrorProc), которые и использует модуль SysUtils. Вам не следует использовать их напрямую, если только вы не отказались от модуля SysUtils. Вместо использования событий модуля System, вы используете модуль SysUtils. Более подробно события модуля System рассматриваются тут (пункт 10, обсуждение модуля System).

Итак, что же тогда нам предлагает модуль SysUtils? А модуль SysUtils предлагает нам новый класс Exception, в котором появились события GetExceptionStackInfoProc, GetStackInfoStringProc и CleanUpStackInfoProc. По-умолчанию, они не назначены — да и их некому реализовывать, т.к., как я уже сказал, в программе по-умолчанию просто нет информации для этого.

Значит, нам надо их реализовать. Поскольку мы пишем просто обёртку к уже существующему трейсеру, то всё, что нам надо будет сделать — вызвать подходящую функцию трейсера. Например:
Достаточно добавить этот модуль в uses (чем раньше — тем лучше) и вы волшебным образом получаете свой стек:
Для кода:
Пример вывода будет (с вложенными исключениями):
Обратите внимание, что в этом примере не используется хукинг исключений средствами JCL (JclHookExcept). Всё работает и без него. Мы используем только возможности JCL по чтению отладочной информации и трасировке стека.

Аналогичный модуль для EurekaLog (применимо для EurekaLog 6 и ниже; EurekaLog 7 и выше уже интегрируется с новым классом Exception):
Вариант модуля для madExcept я оставляю вам в качестве домашнего задания ;)

Exceptions (Delphi)

This topic covers the following material:

  • A conceptual overview of exceptions and exception handling
  • Declaring exception types
  • Raising and handling exceptions

Contents

About Exceptions

An exception is raised when an error or other event interrupts normal execution of a program. The exception transfers control to an exception handler, which allows you to separate normal program logic from error-handling. Because exceptions are objects, they can be grouped into hierarchies using inheritance, and new exceptions can be introduced without affecting existing code. An exception can carry information, such as an error message, from the point where it is raised to the point where it is handled.

When an application uses the SysUtils unit, most runtime errors are automatically converted into exceptions. Many errors that would otherwise terminate an application — such as insufficient memory, division by zero, and general protection faults — can be caught and handled.

When To Use Exceptions

Exceptions prov >try. except or try. finally statement, in practice these tools are best reserved for special situations.

Exception handling is appropriate for errors whose chances of occurring are low or difficult to assess, but whose consequences are likely to be catastrophic (such as crashing the application); for error conditions that are complicated or difficult to test for in if. then statements; and when you need to respond to exceptions raised by the operating system or by routines whose source code you don’t control. Exceptions are commonly used for hardware, memory, I/O, and operating-system errors.

Conditional statements are often the best way to test for errors. For example, suppose you want to make sure that a file exists before trying to open it. You could do it this way:

But you could also avoid the overhead of exception handling by using:

Assertions prov >Assert statement fails, the program either halts with a runtime error or (if it uses the SysUtils unit) raises an SysUtils.EAssertionFailed exception. Assertions should be used only to test for conditions that you do not expect to occur.

Declaring Exception Types

Exception types are declared just like other >SysUtils .

You can group exceptions into families using inheritance. For example, the following declarations in SysUtils define a family of exception types for math errors:

Given these declarations, you can define a single SysUtils.EMathError exception handler that also handles SysUtils.EInvalidOp, SysUtils.EZeroDivide, SysUtils.Overflow, and SysUtils.EUnderflow.

Exception classes sometimes define fields, methods, or properties that convey additional information about the error. For example:

Raising and Handling Exceptions

To raise an exception object, use an instance of the exception class with a raise statement. For example:

In general, the form of a raise statement is

where object and at address are both optional. When an address is specified, it can be any expression that evaluates to a pointer type, but is usually a pointer to a procedure or function. For example:

Use this option to raise the exception from an earlier point in the stack than the one where the error actually occurred.

When an exception is raised — that is, referenced in a raise statement — it is governed by special exception-handling logic. A raise statement never returns control in the normal way. Instead, it transfers control to the innermost exception handler that can handle exceptions of the given >try. except block was most recently entered but has not yet exited.)

For example, the function below converts a string to an integer, raising an SysUtils.ERangeError exception if the resulting value is outside a specified range.

Notice the CreateFmt method called in the raise statement. SysUtils.Exception and its descendants have special constructors that provide alternative ways to create exception messages and context IDs.

A raised exception is destroyed automatically after it is handled. Never attempt to destroy a raised exception manually.

Note: Raising an exception in the initialization section of a unit may not produce the intended result. Normal exception support comes from the SysUtils unit, which must be initialized before such support is available. If an exception occurs during initialization, all initialized units — including SysUtils — are finalized and the exception is re-raised. Then the exception is caught and handled, usually by interrupting the program. Similarly, raising an exception in the finalization section of a unit may not lead to the intended result if SysUtils has already been finalized when the exception has been raised.

Try. except Statements

Exceptions are handled within try. except statements. For example:

This statement attempts to div >Y by Z , but calls a routine named HandleZeroDivide if an SysUtils.EZeroDivide exception is raised.

The syntax of a try. except statement is:

where statements is a sequence of statements (delimited by semicolons) and exceptionBlock is either:

  • another sequence of statements or
  • a sequence of exception handlers, optionally followed by

An exception handler has the form:

where identifier: is optional (if included, identifier can be any valid identifier), type is a type used to represent exceptions, and statement is any statement.

A try. except statement executes the statements in the initial statements list. If no exceptions are raised, the exception block (exceptionBlock) is ignored and control passes to the next part of the program.

If an exception is raised during execution of the initial statements list, either by a raise statement in the statements list or by a procedure or function called from the statements list, an attempt is made to ‘handle’ the exception:

  • If any of the handlers in the exception block matches the exception, control passes to the first such handler. An exception handler ‘matches’ an exception just in case the type in the handler is the class of the exception or an ancestor of that class.
  • If no such handler is found, control passes to the statement in the else clause, if there is one.
  • If the exception block is just a sequence of statements without any exception handlers, control passes to the first statement in the list.

If none of the conditions above is satisfied, the search continues in the exception block of the next-most-recently entered try. except statement that has not yet exited. If no appropriate handler, else clause, or statement list is found there, the search propagates to the next-most-recently entered try. except statement, and so forth. If the outermost try. except statement is reached and the exception is still not handled, the program terminates.

When an exception is handled, the stack is traced back to the procedure or function containing the try. except statement where the handling occurs, and control is transferred to the executed exception handler, else clause, or statement list. This process discards all procedure and function calls that occurred after entering the try. except statement where the exception is handled. The exception object is then automatically destroyed through a call to its Destroy destructor and control is passed to the statement following the try. except statement. (If a call to the Exit , Break , or Continue standard procedure causes control to leave the exception handler, the exception object is still automatically destroyed.)

In the example below, the first exception handler handles division-by-zero exceptions, the second one handles overflow exceptions, and the final one handles all other math exceptions. SysUtils.EMathError appears last in the exception block because it is the ancestor of the other two exception classes; if it appeared first, the other two handlers would never be invoked:

An exception handler can specify an >on. do . The scope of the identifier is limited to that statement. For example:

If the exception block specifies an else clause, the else clause handles any exceptions that aren’t handled by the block’s exception handlers. For example:

Here, the else clause handles any exception that isn’t an SysUtils.EMathError.

An exception block that contains no exception handlers, but instead consists only of a list of statements, handles all exceptions. For example:

Here, the HandleException routine handles any exception that occurs as a result of executing the statements between try and except.

Re-raising Exceptions

When the reserved word raise occurs in an exception block without an object reference following it, it raises whatever exception is handled by the block. This allows an exception handler to respond to an error in a limited way and then re-raise the exception. Re-raising is useful when a procedure or function has to clean up after an exception occurs but cannot fully handle the exception.

For example, the GetFileList function allocates a TStringList object and fills it with file names matching a specified search path:

GetFileList creates a TStringList object, then uses the FindFirst and FindNext functions (defined in SysUtils ) to initialize it. If the initialization fails — for example because the search path is inval >GetFileList needs to dispose of the new string list, since the caller does not yet know of its existence. For this reason, initialization of the string list is performed in a try. except statement. If an exception occurs, the statement’s exception block disposes of the string list, then re-raises the exception.

Nested Exceptions

Code executed in an exception handler can itself raise and handle exceptions. As long as these exceptions are also handled within the exception handler, they do not affect the original exception. However, once an exception raised in an exception handler propagates beyond that handler, the original exception is lost. This is illustrated by the Tan function below:

If an SysUtils.EMathError exception occurs during execution of Tan, the exception handler raises an ETrigError . Since Tan does not prov >ETrigError , the exception propagates beyond the original exception handler, causing the SysUtils.EMathError exception to be destroyed. To the caller, it appears as if the Tan function has raised an ETrigError exception.

Try. finally Statements

Sometimes you want to ensure that specific parts of an operation are completed, whether or not the operation is interrupted by an exception. For example, when a routine acquires control of a resource, it is often important that the resource be released, regardless of whether the routine terminates normally. In these situations, you can use a try. finally statement.

The following example shows how code that opens and processes a file can ensure that the file is ultimately closed, even if an error occurs during execution:

The syntax of a try. finally statement is

where each statementList is a sequence of statements delimited by semicolons. The try. finally statement executes the statements in statementList1 (the try clause). If statementList1 finishes without raising exceptions, statementList2 (the finally clause) is executed. If an exception is raised during execution of statementList1, control is transferred to statementList2; once statementList2 finishes executing, the exception is re-raised. If a call to the Exit , Break , or Continue procedure causes control to leave statementList1, statementList2 is automatically executed. Thus the finally clause is always executed, regardless of how the try clause terminates.

If an exception is raised but not handled in the finally clause, that exception is propagated out of the try. finally statement, and any exception already raised in the try clause is lost. The finally clause should therefore handle all locally raised exceptions, so as not to disturb propagation of other exceptions.

Новые книги

Признанный эксперт и один из самых влиятельных людей в области партнерского маркетинга, автор и спикер, известный своей образовательской деятельностью, знакомит читателя с ключевыми концепциями партнерского маркетинга, отвечая на самые важные вопросы и обеспечивая вас знаниями, необходимыми для начертания своей дорожной карты к успеху. В формате «вопрос-ответ» книга отвечает на 60 вопросов, закладывая надежный фундамент для построения успешных партнерских кампаний.

Книга предназначена для интересующихся партнерским маркетингом и покрывает все его ключевые аспекты на базовом уровне.

Глава 2. Исключительные ситуации и интерфейсы в Delphi

ГЛАВА 2

  • Понятие исключительной ситуации, ее обработка средствами Delphi
  • Обработка RTL-исключений.Иерархия исключений
  • Создание собственных исключений
  • Интерфейсы и их совместное использование классами
  • Интерфейс IUnknown
    • Класс TInterfasedObject
    • Использование оператора as
    • Использование ключевого слова implements
  • Использование инрефейсов в распределительных приложениях

Большинство разработчиков на Delphi создают довольно сложные программы. Сложная программа подразумевает разносторонее взаимодействие с операционной системой и приложениями операционной системы. Любое из этих взаимодействий может завершиться неправильно. Примеров этого можно привести очень много, от банального деления на ноль, до открытия несуществующего файла. Обычно для обхода таких ситуаций разработчику приходится вводить многочисленные проверки. Но любой разработчик просто не в состоянии рассмотреть все ситуации, которые могут возникнуть у его программы при взаимодействии с операционной системой и другими приложениями. Именно для таких непредвиденных событий была придумана структурированная обработка исключительных ситуаций. Первоначально она была создана для разработчиков под Windows NT, впоследствии структурированная обработка получила поддержку и в операционных системах Windows 9x. Большинство современных программных сред для создания приложений под Windows поддерживают обработку исключительных ситуаций. Не осталась в стороне и среда Delphi. Обработка исключений была введена уже в Delphi 1.0, но, только начиная с Delphi 2.0, исключения стали частью Win32 API.
В этой главе мы узнаем, что такое исключительная ситуация и как средствами Delphi можно ее обработать. Рассмотрим, что такое RTL-исключения, как можно использовать в обработке исключительных ситуаций метод TAppllcationHandleException. В данной главе мы познакомимся с интерфейсами. Подробно рассмотрим интерфейс IUnknown. А также рассмотрим, как можно использовать интерфейсы в распределенных приложениях.
Понятие исключительной ситуации, ее обработка средствами Delphi
Под исключительной ситуацией мы будем понимать некое непредвиденное событие, способное повлиять на дальнейшее выполнение программы.
При обработке такой ситуации Delphi, как обычно, работает с объектами. С точки зрения компилятора Delphi исключительная ситуация — это объект. Для работы с этим специфичным объектом в Delphi (точнее, в Object Pascal) были введены следующие языковые конструкции: try .. except и try .. finally .
Рассмотрим эти языковые конструкции более подробно.
Итак, конструкция try .. except имеет следующий синтаксис (листинг 1.6):

Если при выполнении кода, размещенного в разделе try, генерируется исключение, то выполнение этого раздела прекращается и управление передается коду, размещенному в разделе except. Раздел except может использоваться двумя способами. Во-первых, в нем могут располагаться любые операторы, кроме обработчиков исключений, начинающихся с приставки on. Это и операторы сообщения об ошибке, и команды, позволяющие освобождать системные ресурсы, а также другие операторы и команды. Во-вторых, раздел except используется для обработки исключений. В этом случае в него могут включаться только операторы обработки исключений. Если среди обработчиков встретился обработчик, соответствующий сгенерированному исключению, то выполняется оператор этого обработчика, исключение разрушается и управление передается коду, расположенному после оператора on Exception do. Раздел, расположенный после ключевого слова else, служит для обработки любых исключений, не описанных в разделе except. Этот раздел не является обязательным. Если при обработке исключительной ситуации не будет найден подходящий обработчик, то произойдет обработка системным обработчиком исключений.
Рассмотрим простой пример обработки исключительной ситуации деления на ноль (листинг 1.7).

Листинг 1.7
try
а:=10;
b:=0;
c:=a/b;
except
on EZeroDivide do MessageBox(‘Делить на ноль нельзя!’);
end;

Итак, как можно видеть из приведенного выше примера, для обработки разных исключений служат разные операторы. Рассмотрим более подробно оператор обработки on .. do . Данный оператор находится внутри раздела except и может иметь две формы (листинг 1.8).

Листинг 1.8
on do ;
или
on :
do

Этот оператор обрабатывает только тот класс исключений, который в нем указан. При указании родительского (базового) класса, все классы исключений — потомки данного класса — также будут обработаны. Для обработки всех исключений можно обратиться к базовому классу всех исключений: Exception. После обработки исключения оно разрушается. Вторая форма оператора on .. do отличается от первой тем, что данному исключению можно временно присвоить имя и обращаться к свойствам исключения. Обращаться к свойствам исключения можно с помощью конструкции . . Посмотрим листинг 1.9.

Листинг 1.9
try
ScrollBarl.Max := ScrollBarl.Min — 1;
except
on E: EInvalidOperation do
MessageDlg( ‘Игнорируем исключение: ‘- + E.Message, mtlnformation, [mbOK], O)
end;

В приведенном примере мы присваиваем исключению EInvalidOperation временное имя Е. Затем в окне сообщения выводим текст ошибки E.Message, выдаваемый Delphi по умолчанию (если бы не было нашего обработчика ошибки).

Примечание
Никогда не уничтожайте временный объект исключения. Обработчик исключений автоматически уничтожает объекты исключения. Если вы уничтожите объект исключения самостоятельно, это может привести к ошибке доступа.

Иногда, бывает необходимо, чтобы после обработки исключительной ситуации своим кодом вызывался стандартный обработчик ошибки. Например, в случае возникновения некоторой ошибки вы хотите, чтобы приложение сообщало пользователю какую-либо информацию, а затем передавало управление стандартному обработчику ошибок. Как вы уже знаете, после обработки исключения вашим кодом, исключение уничтожается. Для того чтобы самостоятельно вызвать снова это исключение, можно воспользоваться регенерацией исключений. Для регенерации исключения служит команда raise. Рассмотрим листинг 1.10.

После выполнения операторов обработки исключения, написанных программистом, выполняется команда raise, которая снова принудительно вызывает это исключение, после чего управление передается стандартному обработчику исключений.
В случае, когда исключение успешно проходит через все блоки try в коде приложения, вызывается метод HandleException . Он показывает диалоговое окно ошибки. Вы можете вызвать этот метод так, как в листинге 1.11.

Листинг 1.11
try
< операторы >except
Application.HandieException(Self);
end;

Следующая конструкция try .. finally служит для защиты кода, записанного в разделе finally от исключительных ситуаций, которые в силу каких-либо причин могут происходить в разделе try. Синтаксис этой конструкции представлен на листинге 1.12.

Итак, операторы, которые размещены после ключевого слова finally , будут выполняться в любом случае, была сгенерирована исключительная ситуация или нет. Если в разделе try была сгенерирована исключительная ситуация, то управление немедленно передается разделу finally . Также, если исключительной ситуации в разделе, try не было, блок finally будет выполняться. Даже если в разделе finally произойдет ошибка, выполнение операторов этого раздела будет продолжено до конца. В конструкции try .. finally не происходит обработка исключений, она используется в основмом для освобождения ресурсов памяти, закрытия ненужных файлов и других операций освобождения ресурсов. Таким образом, в данной конструкции нуждаются операции с файлами, памятью, ресурсами Windows и объектами.
Код обработки исключения можно разбить на блоки try .. except .. end и try .. finally .. end. Эти блоки могут быть вложенными (рис. 1.24, а и б).
При разработке приложений на Delphi часто возникают ситуации, когда программисту не требуется обрабатывать исключения, а необходимо лишь прервать нежелательное действие, вызывающее ошибку. Для этого применяются так называемые молчаливые исключения (silent exceptions). Молчаливые исключения являются потомками стандартного исключения EAbort . По умолчанию обработчик ошибок VCL Delphi отображает на экране диалоговое окно ошибки для всех исключений, кроме наследников EAbort.

Примечание
При создании консольных приложений сведения об ошибке выводятся и для необработанных исключений EAbort.

Для того чтобы сгенерировать молчаливое исключение, можно вызвать процедуру Abort. Она автоматически сгенерирует исключение EAbort, которое прервет текущую операцию без вывода сведения об ошибке на экран. Рассмотрим пример. Пусть форма содержит пустой список (ListBoxi) и кнопку (Button1). Запишем в обработчик события кнопки onclick следующий код:

Листинг 1.13
procedure TForml.ButtonlClick(Sender: TObject);
var
I: Integer;
begin
for I := 1 to 10 do (цикл 10 раз>
begin
ListBoxl.Items.Add(IntToStr(I)); <добавляем номер в список>
if I = 7 then Abort; <прерываем добавление номеров в список после добавления седьмого номера>
end;
end;

В результате работы программы, после нажатия кнопки Button1, в список будет добавлено семь строк с номерами от 1 до 7.

Рис. 1.24. Вложенные блоки в обработчике исключений (а) и в конструкции защиты кода (б)
Обработка RTL-исключений. Иерархия исключений
RTL (real time library)-исключения определены в модуле Delphi sysutils и являются наследниками базового класса исключений Exception.
Имеется несколько типов исключений-наследников RTL (табл. 1.1).
Таблица 1.1 . Типы исключений из RTL

Ошибка доступа к файлу или устройству ввода/вывода

Большинство исключений ввода/вывода связано с кодом ошибки, возвращаемом Windows при обращении к файлу

Ошибка использования динамической памяти

Ошибки кучи возникают при недостатке памяти или когда в приложении присутствует указатель на область памяти вне кучи

Целочисленные математические операции

Неправильное действие с выражением целого типа

Ошибки включают в себя: деление на ноль, переполнение, выход за пределы диапазона и др.

Математические операции с плавающей точкой

Неправильное действие с выражением вещественного типа

Ошибки с вещественными числами могут исходить от математического сопроцессора или программного эмулятора. Ошибки включают в себя: неправильные инструкции, деление на ноль, переполнение и др.

Неправильная работа с классами при помощи операции as

Объекты могут работать только с совместимыми объектами

Неправильное преобразование типов

Функции преобразования типов (IntToStr, StrToInt И др.) генерируют эту ошибку в случае невозможности преобразования

Аппаратные ошибки указывают, что или процессор, или пользователь сгенерировал ошибку: доступа, переполнения стека или другую

Неправильное использование типа

Ошибка возникает в выражениях, где не может использоваться тип


Рассмотрим теперь иерархию классов исключений .более подробно:

— базовый класс исключений

— исключение для намеренного прерывания вычислений

— попытка вызова абстрактного метода

— ошибка доступа к памяти

— ошибка при работе с массивами

— ошибка при проверке истинности

— ошибка доступа к массиву булевых величин TBits

— ошибка построения кэша

— ошибка регистрации или переименования компонента

— нажатие пользователем клавиш + при выполнении консольного приложения

— ошибка преобразования строк (объектов)

— ошибка работы с базами данных

— ошибка в наборе данных клиента

— ошибка обновления данных компонента

— генерируется компонентом TQuery при попытке открыть запрос без select

— ошибка при обновлении В TProvider

— ошибка ввода даты или времени

— ошибка формата данных в кубе решений

— ошибочный индекс в задании размерности в кубе решений

— ошибка ввода/вывода в файл

— базовый класс исключений целочисленных математических операций

— ошибка деления на ноль

— значение или индекс вне допустимого диапазона

— ошибочное преобразование типов as к интерфейсу

— нераспознаваемый графический файл

— ошибка при операциях с графикой

— ошибка при работе с сеткой (Grid)

— ошибочная операция с компонентом

— ошибка при операциях с указателем

— ошибка при работе со списком

— ошибка выделения памяти для куба решений

— базовый класс исключений операций с плавающей запятой

— недопустимое значение параметра при обращении к математической функции

— потеря значащих разрядов

— ошибка деления на ноль

— ошибка доступа к устройствам через драйвер MCI (Media Control Interface)

— ошибка при работе с элементами меню

— ошибка при связывании приложения с элементом ActiveX

— низкоуровневая ошибка OLE

— ошибка интерфейса OLE IDispatch

— ошибка OLE, связанная со свойством или методом

— ошибка при работе с Tout line

— ошибка распределения памяти

— ошибка создания обработчика Windows

— исключение, генерируемое при загрузке или использовании пакета

— ошибка преобразования текста описания формы в двоичный формат

— ошибка выполнения инструкции процессора из-за нехватки привилегий

— ошибка записи с помощью OLE значения свойства, предназначенного только для чтения

— ошибка чтения с помощью OLE значения свойства, предназначенного только для записи

— ошибка при задании значения свойства

— ошибка при работе с реестром Windows

— ошибка задания типа сервера (компонент TReport не может соединиться с базой данных)

— ошибка загрузки файла ресурсов (*.DFM или *.RES) во время создания приложения

— переполнение стека — базовый класс исключений ошибок потоков

— ошибка создания файла

— ошибка открытия файла

— базовый класс исключений файловых потоков

— ошибка чтения заданного числа байт

— ошибка записи заданного числа байт

— ошибка связи компонента с приложением

— ошибка чтения файла ресурсов

— не найден метод

— ошибка доступа к окну списка

— ошибка многопоточного приложения

— ошибка индекса при работе с TTreeview

— ошибка при работе с типом данных variant

— внутренняя ошибка Windows


Итак, класс Exception является базовым классом всех исключений в Delphi. Все вышеописанные классы являются прямыми или косвенными наследниками класса Exception. При создании собственных новых классов исключений необходимо использовать класс Exception как родительский. Только в этом случае Delphi гарантированно распознает и обработает новый класс как исключение. В свою очередь, класс Exception является прямым наследником базового класса TObject, и наследует все его функции. В отличие от
других классов, классы исключений начинаются не с буквы т, а с буквы Е. При создании собственных классов исключений можно называть их по своему усмотрению, не обязательно начиная с буквы Е (но это считается плохим стилем программирования).
Создание собственных исключений
Для создания собственных типов исключений необходимо знать, как определить тип объекта исключения, а также как вызвать исключение.
Так как исключение является объектом Delphi, то определение нового типа исключения так же nporfro, как определение объекта нового типа. Теоретически возможно вызывать любой объект как объект исключения, но стандартные обработчики исключений работают только с теми объектами, предками которых являются Exception или потомки Exception.
В качестве примера создания собственного типа исключения рассмотрим следующее определение:
type
EMyException = class(Exception);
Теперь, если вы вызовете исключение EMyException, но не напишете обработчик для него, то произойдет вызов стандартного обработчика для Exception. Так как стандартный обработчик для Exception показывает имя вызванного исключения, вы можете увидеть имя вашего нового исключения.
Для вызова созданного исключения используйте команду raise. Для примера рассмотрим типичную задачу проверки введенного пользователем пароля:
type
EPasswordlnval >
После определения нового типа исключения EpasswordInwalid вы можете вызвать это исключение в любом месте программы:
if Password <> CorrectPassword then raise EPasswordlnvalidCreate(‘Введен неправильный пароль’);
Вызов созданного исключения производится по его имени.
Интерфейсы и их совместное использование классами
Ключевое слово Delphi interface позволяет создавать и использовать интерфейсы в ваших приложениях. Интерфейсы служат для расширения модели наследования в VCL, позволяя одному классу принадлежать нескольким
интерфейсам, а также нескольким классам — наследникам различных базовых классов использовать один интерфейс. Интерфейсы полезны в тех случаях, когда наборы операций, такие как потоки, используются большим количеством объектов.
Таким образом, интерфейсы — это средства для обеспечения взаимодействия между разными объектами.
Интерфейсы являются фундаментом для технологий компонентной объектной модели (СОМ) и CORBA.
Интерфейсы похожи на классы, которые содержат в себе только абстрактные методы и четкие определения их функциональности. Определение метода интерфейса включает в себя параметры и типы параметров, возвращаемый тип, а также ожидаемое поведение. Методы интерфейса семантически или логически связаны с отражением цели интерфейса. Существует соглашение об интерфейсах, гласящее, что каждый интерфейс должен быть назван в соответствии с задачей, которую он будет выполнять. Например, интерфейс iMalloc предназначен для распределения, освобождения и управления памятью. Аналогично, интерфейс IPersist может использоваться как базовый интерфейс для потомков, каждый из которых определяет специфичные прототипы методов для загрузки и сохранения состояния объектов в память, поток или в файл. Приведем простой пример объявления интерфейса (листинг 1.14):

Листинг 1.14
type
IEdit = interface
procedure Copy; stdcall;
procedure Cut; stdcall;
procedure Paste; stdcall;
function Undo: Boolean; stdcall;
end;

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

Нельзя создать экземпляр интерфейса при помощи интерфейса. Для получения экземпляра интерфейса вам нужно объявить его в классе, содержащем данный интерфейс. Таким образом, нужно определить класс, который содержит необходимый интерфейс в списке своих родителей (листинг 1.15).

Листинг 1.15
TEditor = class(TInterfacedObject, lEdit)
procedure Copy; stdcall;
procedure Cut; stdcall;
procedure Paste; stdcall;
function Undo: Boolean; stdcall;
end;

Как уже было отмечено выше, использование интерфейсов позволяет нескольким классам использовать один интерфейс, игнорируя требование наличия одного базового класса-предка. Следует запомнить, что интерфейс — это тип с управляемым временем жизни, т. е., он автоматически, при инициализации, принимает значение nil, обладает счетчиком ссылок и автоматически уничтожается, при выходе за пределы своей области видимости.
Интерфейс IUnknown
По аналогии с наследованием классов, предком которых является базовый класс TObject, все интерфейсы — это прямые или косвенные наследники интерфейса IUnknown. Этот базовый интерфейс описан в модуле System следующим образом (листинг 1.16):

Листинг 1.16
type
IUnknown = interface
[‘< 00000000-0000-0000-С000-000000000046>‘]
function Querylnterfасе(const IID: TGUID; out Obj): Integer; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;7
end;

Синтаксис описания интерфейса похож на описание класса. Главное отличие заключается в том, что интерфейс может быть связан с глобальным уникальным идентификатором (Global Unique Identifier, GUID).
GUID — это 128-разрядное целое число, которое используется для уникальной идентификации интерфейсов. Так как в 128-ми разрядах можно закодировать большое количество чисел, GUID практически гарантирует глобальную уникальность идентификатора интерфейса. То есть, практически невозможно, чтобы на двух компьютерах GUID совпал. Алгоритм генерации GUID основан на аппаратной части компьютера (частота процессора, номер сетевой карты и т. д.). В результате работы алгоритма, который может быть реализован с помощью функции API CocreateGUID (), получается запись типа TGUID. Эту запись можно определить в виде строки следующего формата:
‘<хххххххх-хххх-хххх-хххх-хххххххххххх>‘
В дальнейшем, при рассмотрении СОМ, мы увидим, что каждый интерфейс или класс СОМ имеет собственный GUID. Для интерфейсов — это идентификатор интерфейса (Interface ID, IID), а для класса — идентификатор класса (Class ID, CLSID).
Для создания нового GUID в среде Delphi достаточно нажать комбинацию клавиш + + в окне редактора кода.
Итак, интерфейс lunknown поддерживает три метода, которые наследуются всеми интерфейсами:
— QueryInterface() — используется для создания запроса, поддерживается ли данный интерфейс и если ответ положителен, то метод возвращает указатель на него. Для примера, предположим, что имеется некоторый объект object, который поддерживает несколько интерфейсов interface1, interface2 и др. Для получения указателя на интерфейс interface2, объекта Object, вам нужно вызвать метод Interface2.Query Interface О;
— _AddRef () — используется, когда получен указатель на данный интерфейс и вы хотите работать с этим указателем. Метод _AddRef() обязательно должен заканчиваться вызовом метода _Release ();
— _Release () — данный метод применяется для завершения работы с интерфейсом.
Интерфейсы являются фундаментальными элементами таких распределенных объектных моделей, как СОМ и CORBA.
Более подробно интерфейс lunknown мы рассмотрим в третьей части книги, посвященной использованию технологий СОМ и ActiveX.
Класс TlnterfacedObject
В VCL Delphi определен класс TlnterfacedObject, который служит базовым классом для объектов интерфейса. Данный класс определен в модуле Delphi system следующим образом (листинг 1.17):

Листинг 1.17.
type
TlnterfacedObject = class(TObject, IUnknown) private
FRefCount: Integer;
protected
function Querylnterface(const IID: TGUID; out Obj): Integer; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall; public
property RefCount: Integer read FRefCount;
end;

Как мы видим, данный класс в качестве родителей имеет класс TObject и интерфейс lunknown. Класс Tinterfacedobject позволяет достаточно легко создавать классы, поддерживающие интерфейсы. Например,
type
TMyObjInterfaced = class(TInterfacedObject, IPaint)
end;
На вышеприведенном примере мы определяем новый класс TMyObj interfaced, который является прямым потомком класса Tinterfacedobject и поддерживает некий интерфейс IPaint.
Использование оператора as
Объекты, поддерживающие интерфейсы, могут использовать оператор as для динамического присоединения интерфейса. Например,
procedurePaintObjecta(P: TInterfacedObject) var
X: IPaint; begin
X := P as IPaint;
<операторы>
end;
В этом примере переменная Р имеет тип Tinterfacedobject. Данная переменная может быть назначена переменной х, как ссылка на интерфейс IPaint.. Для такого назначения компилятор генерирует код для вызова метода Querylnterface, относяшегося к Интерфейсу IUnknown переменной Р. Подобное назначение возможно, даже если Р не поддерживает данный интерфейс. То есть, компилятор не выдаст ошибку при таком назначении.
Во время выполнения вышеприведенного примера либо успешно происходит присваивание
Х:= Р as IPaint;
либо генерируется исключительная ситуация.
При использовании оператора as вы должны выполнять следующие требования:
— при объявлении интерфейса, явно объявляйте в качестве предка интерфейс lunknown. Так как только в этом случае вы сможете воспользоваться оператором аs;
— если вы используете оператор as для интерфейса, данный интерфейс должен иметь свой IID. Напомним, что для создания нового IID достаточно, находясь в редакторе кода, использовать комбинацию клавиш + + .
Использование ключевого слова implements
Многие классы VCL Delphi имеют в качестве некоторых своих свойств объекты. Кроме того, вы можете использовать в качестве свойств класса интерфейсы. В том случае, когда свойство имеет тип интерфейса, то вы можете использовать ключевое слово implements для определения методов, которые данный интерфейс передает объекту. По умолчанию, ключевое слово implements передает все методы интерфейса. Тем не менее, вы можете самостоятельно определить список тех методов интерфейса, которые передаются объекту.
На приведенном ниже листинге 1.18 представлен пример использования ключевого слова implements при создании объекта адаптера цвета, предназначенного для преобразования восьмибитного значения цвета RGB.

Листинг 1.18
unit cadapt;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
IRGBSbit = interface
[‘‘]
function Red: Byte;
function Green: Byte;
function Blue: Byte;
end;
IColorRef = interface
[41d76360b-f4f5-lldl-87d4-00c04fbl7199>’] function Color: Integer; end;
TRGBSColorRefAdapter = class(TInterfacedObject, IRGBSbit, IColorRef) private
FRGBSbit: IRGBSbit;
FPalRelative: Boolean; public
constructor Create(rgb: IRGBSbit);
property RGBSIntf: IRGBSbit read FRGBSbit implements IRGBSbit;
property PalRelative: Boolean read FPalRelative write-FPalRelative;
function Color: Integer; end;
implementation
constructor TRGBSColorRefAdapter.Create(rgb: IRGBSbit);
begin
FRGBSbit := rgb; end;
function TRGBSColorRefAdapter.Color: Integer;
begin
if FPalRelative then
Result := PaletteRGB(RGBSIntf.Red, RGBSIntf.Green, RGBSIntf.Blue) else
Result := RGB(RGBSIntf.Red, RGBSIntf.Green, RGBSIntf.Blue);
end;
end.

Использование интерфейсов в распределенных приложениях
Интерфейсы являются фундаментальным элементом распределенных объектных моделей СОМ и CORBA (более подробно о моделях читайте в третьей части книги). Delphi обеспечивает базовые классы для этих технологий, которые расширяют возможности объекта TInterfacedObject.
Классы СОМ добавляют возможности использования фабрик классов и идентификаторов классов (CLSID). Фабрики классов отвечают за создание экземпляров классов посредством CLSID. В свою очередь, CLSID используются для регистрации и манипуляции классами СОМ. Классы СОМ, которые обладают и фабрикой класса, и идентификатором класса, называются CoClasses. CoClasses имеют преимущество перед Querylnterface по части поддержки новых версий интерфейсов. Новые версии старых интерфейсов автоматически становятся доступными для программ-клиентов. В приложе ниях СОМ разработчик может вносить правку в код интерфейса для улучшения работы приложения, не изменяя клиентской части кода.
Другая распределенная технология называется CORBA (Архитектура Брокера Общих Объектных Запросов, Common Object Request Broker Architecture). Описание данной технологии не входит в эту книгу, заметим только, что она позволяет создавать приложения для взаимодействия с различными аппаратными или программными платформами. Так, клиентское приложение CORBA, работающее в операционной системе Windows 98, также легко будет работать с сервером приложений операционной системы UNIX.

Илон Маск рекомендует:  Asp объекты adsi
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL