│
English (en) │
suomi (fi) │
Free Pascal supports exceptions. Exceptions are useful for error handling and avoiding resource leaks. However, bear in mind that exceptions have a performance impact.
The official documentation is here: Reference guide chapter 17.
By default exceptions are disabled. You can opt in by using the ObjFPC or DELPHI Compiler Mode, or adding -Sx to the compiler commandline. This enables the try
, raise
, except
, and finally
reserved words for use in your own code, but it doesn’t enable exception generation from the RTL. To enable the RTL to raise exceptions instead of generating runtime errors, use the SysUtils unit in your program.
The base Exception
class is found in the SysUtils unit. All exceptions should preferably use this class or its descendants.
SysUtils automatically sets up a basic exception catcher, so any otherwise uncaught exception is displayed in human-readable form, as the program terminates. To generate readable callstacks from caught exceptions in your own code, without necessarily terminating the program, you can use SysUtils functions ExceptAddr
, ExceptFrames
, ExceptFrameCount
, and BackTraceStrFunc
.
Examples
Note that Pascal uses different keywords than some other languages: raise
instead of throw
, and except
instead of catch
. Also, as a compiler design choice, each try
-block can pair with either an except
or finally
block, but not both at the same time. You’ll need to nest try
-blocks if you need except
and finally
protecting the same code.
Error handling
uses sysutils; begin try // Do something that might go wrong. except begin // Try to recover or show the user an error message. end; end; end.
Cleaning up resources
try // Do something that might go wrong. finally // Clean-up code that is always called even if an exception was raised. end;
Exception leaking
Finally-blocks don’t destroy exception objects. Any exception that reaches the program’s «end.» will trigger a memory leak warning. An extra except block can be used to consume such exceptions. This is Delphi-compatible.
begin try // Your main program, where an exception object is created. finally try // Clean-up code. except end; end; end.
Signalling problems
raise Exception.Create('Helpful description of what went wrong');
Using specialised exception types to signal different problems
type EMyLittleException = Class(Exception); begin try raise EMyLittleException.Create('Foo'); except on E : EMyLittleException do writeln(E.Message); on E : Exception do writeln('This is not my exception!'); else writeln('This is not an Exception-descendant at all!'); end; end;
Re-raising exceptions
try // Do something that might go wrong. except // Try to recover or show the user an error message. if recoveredSuccessfully = FALSE then raise; end;
Exception classes
SysUtils defines and raises many specific exception classes.
With SysUtils included in your program, and exceptions enabled, various runtime errors are changed into exceptions. Processor-level interrupts like SIGSEGV or SIGFPU, which normally translate to run-time errors, are also changed to exceptions. For example, run-time error 200 (division by zero) becomes EDivByZero or EZeroDivide, while run-time error 216 (a general protection fault) becomes EAccessViolation.
Best practice
- Raise exceptions to signal that an operation could not be completed, where it normally should have succeeded.
- Do not use exceptions as part of expected control flow. Instead, add checks for common error conditions and return error values the old-fashioned way. For example, input parsing problems or file existence fails are usually not truly exceptional.
- But do raise an exception if it’s critical that the error is noticed; programmers may forget to check for returned error values.
- Naming convention: Prefix exception class names with a capital E.
- Re-raise exceptions in
except
-blocks usingraise;
if you were unable to recover from the problem; this preserves the original exception’s callstack. - Be careful about using the catch-all base
Exception
class, since the underlying code or OS/filesystem might produce an unanticipated exception that slips through your exception handler, potentially corrupting the program state. - When writing a unit or library, if exceptions are used at all, they should be documented clearly up front. This way the unit user knows what to expect.
- Keep exception handling away from code that needs to run as fast as possible.
Performance
Compiler optimisation levels don’t make much difference. All exception blocks use a small amount of wrapper code that can’t be optimised away. There are different ways for a compiler to produce exception code, with varying performance implications. As of FPC 3.0.4, the default exception mechanism uses the standard setjmp/longjmp style. FPC also supports OS-provided Structured Exception Handling; this is the default on Win64, and can be enabled on Win32 (recompile the compiler with $define TEST_WIN32_SEH
). Other mechanisms may be added to FPC in the future.
To get a better feel for what’s going on, try writing a small test program and compiling it with the -a
switch. This leaves behind a human-readable assembly file.
Notes on the setjmp/longjmp method:
Try
-statements insert some extra address calculations, a stack push and pop, some direct jumps, and a conditional jump. This is the setup cost of an exception frame, even if no exception is raised.Except
-statements insert the above, plus a push and pop, more direct jumps, and another conditional jump.Finally
-statements add a pop and a conditional jump.Raise
-statements create a string and pass control to FPC’s exception raiser. The string creation itself can spawn an implicit exception frame for the function/method, due to dynamic string allocations, even if theraise
is not executed. This is why in e.g. container types one often sees theraise
moved to a separate local (private) procedure. This localises the exception frame to that procedure, so the performance hit is only taken if the procedure is called.
Furthermore, generating a human-readable callstack from a raised exception involves time-consuming stack traversal and string manipulation.
With all that said, outside of heavy processing code, the convenience of exceptions usually outweighs their performance impact. Don’t feel bad about using them if it makes your code better.
Further reading
Logging exceptions
Avoiding implicit try finally section
Exceptions vs Error codes — a situation where exceptions might be a danger in some code (see middle of this page)
Блок try…except имеет следущий вид:
Оператор try..except
Если исключение не инициируется во время выполнения списка операторов, то все операторы в списке будут выполняться последовательно до end, за исключением блока except, который будет пропущен,
Если исключение происходит во время выполнения списка операторов, поток выполнения будет прерван и управление будет передано на except блок. Операторы между местом, где произошло исключение и до блока except — игнорируются.
В блоке обработки исключения, проверяется объект исключения, и если есть обработчик исключения, данного типа (класса) или родительского класса объекта исключения, то выполняются операторы после соответствующего do. Используется первый же подходящий объект, данного класса. После того , как блок — do был выполнен, программа продолжается c конца блока (служебное слово end).
Идентификатор (имя, после служебного слова on) в операторе обработки исключений не является обязательным, и если он есть, то он объявляет объект исключения (можно указать имя объекта, а можно только его тип). Он может быть использован для манипуляций с объектом исключения в коде обработки исключений. То, как организует программист блок после слова do, зависит от его потребностей.
Если ни один из обработчиков on не соответствует типу объекта исключения, то выполняется список операторов после else. Если этот список не найден (отсутствует сл. слово else), то обработка исключения передаётся на более высокий уровень (неявный raise). Этот процесс позволяет обрабатывать только некоторые исключения (указанные в блоке try…except).
Если исключение начало обрабатываться, то объект исключения автоматически разрушается вызовом деструктора destroy (самому вызывть ненадо) и упрвление передается инструкции, следующей за блоком try…except. (Если вызов стандартных процедур Exit, Break или Continue выводит управление из обработчика, объект исключения также разрушается).
В качестве примера, рассмотрим предыдущую функцию DoDiv (из предыдущего примера):
Try
Z := DoDiv (X,Y);
Except
On EDivException do Z := 0;
end;
Если Y будет равным нулю, то код функции DoDiv вызовет исключение. Когда это произойдёт, будет создано исключение, и его обработчик установит Z значение 0. Если исключение не происходит, то программа выполнится, игнорируя блок except. Для того, что-бы применить восстановление после ошибки, нужно использовать блок. Блок try…finally, гарантирует, что будут выполнены операторы после сл. слова finally, даже если произойдёт исключение.
Sharpix 6 / 6 / 0 Регистрация: 07.10.2013 Сообщений: 93 |
||||
1 |
||||
19.09.2016, 22:39. Показов 14128. Ответов 1 Метки exception, finally, lazarus, try except, try except finally, try exception, try finally, исключения, лазарус, научите, обработка исключений, отладка, try/catch (Все метки)
Здравствуйте. Научите меня правильно использовать обработку исключений на примере деления на ноль. Добавил просто блок try-except — оно не работает. Не нашел в интернете ни одного рабочего примера.
0 |
BOGG ART 588 / 455 / 147 Регистрация: 09.12.2013 Сообщений: 2,385 Записей в блоге: 2 |
||||||||
19.09.2016, 23:54 |
2 |
|||||||
Что в вашем понимании «не работает»? Отладка выскакивает? Ну так и должно быть по умолчанию. В настройках отключите. Или запускайте не из под IDE. Добавлено через 1 минуту
Добавлено через 8 минут
1 |
Об исключениях
Исключительная ситуация возникает при ошибке или прерывании нормального хода выполнения программы каким-либо событием. Исключение передает контроль выполнения программы обработчику исключительной ситуации, который позволяет отделить нормальную логику работы программы от обработки ошибок. Поскольку исключения являются объектами, они могут быть сгруппированы в иерархию, использующую наследование, а новые исключения могут объявляться без изменения уже готового кода. Исключение может передавать информацию (например, сообшение об ошибке) из точки возникновения исключительной ситуации к месту ее обработки.
Когда приложение подключает модуль SysUtils большая часть ошибок, возникающих при выполнении программы, автоматически преобразуется в исключения. Значительная часть ошибок, результатом которых могло бы стать завершение программы (такие как недостаток памяти, деление на ноль, ошибки общей защиты) перехватываются и обрабатываются.
Когда следует применять исключения
Исключения обеспечивают удобный способ отслеживать ошибки при выполнении программы без применения массивных условных конструкций. Требования, накладываемые семантикой обработки исключительных ситуаций, влекут за собой увеличение объема кода и данных, а также снижение производительности приложения. По этому, не смотря на то, что существует возможность создавать исключения практически в любой ситуации, защищая любой блок исходного кода заключением его в структуру try…except или try…finally, на практике эти инструменты лучше применять только в особых случаях.
Обработка исключительных ситуаций подходит для:
- трудноопределимых ошибок, вероятность возникновения которых невелика, но последствия которых могут оказаться серьезными (например, аварийное завершение приложения);
- ошибок, возникновение которых трудно отследить при помощи инструкций if…then ;
- случаев, когда необходимо реагировать на исключительные ситуации, создаваемые операционной системой или другими программами, код которых вы не контролируете.
Обычно исключения используются для обработки ошибок аппаратного обеспечения, памяти, обработки ввода/вывода и ошибок операционной системы.
Инструкции условных переходов зачастую являются лучшим способом обработки ошибок. Например, если перед тем как открыть файл, вы хотите убедиться, что файл существует, можно сделать это следующим образом:
try AssignFile(F, FileName); Reset(F); // если файл не найден, //возникает исключительная ситуация EInOutError except on Exception do ... end;
Но для экономии ресурсов можно использовать и другой способ:
if FileExists(FileName) then // возвращает False если файл не найден //исключительной ситуации не возникает begin AssignFile(F, FileName); Reset(F); end;
Утверждения (Assertions) дают еще одну возможность проверки логических условий в вашем исходном коде. Если при выполнении инструкции Assert происходит ошибка, приложение либо завершается, либо (в том случае, если подключен модуль SysUtils) создает исключение SysUtils.EAssertionFailed. Утверждения должны использоваться только для проверки условий, невыполнения которых вы не ожидаете.
Объявление типов исключений
Исключения объявляются как и прочие классы. На самом деле в качестве исключения вы можете использовать экземпляр любого класса, но рекомендуется наследовать исключения от класса SysUtils.Exception, который объявлен в модуле SysUtils.
Вы можете объединять исключения в семейства при помощи наследования. Приведенные ниже объявления из модуля SysUtils объявляют семейство исключений для обработки математических ошибок:
type EMathError = class(Exception); EInvalidOp = class(EMathError); EZeroDivide = class(EMathError); EOverflow = class(EMathError); EUnderflow = class(EMathError);
Сделав такие объявления, вы можете создать один обработчик исключений для SysUtils.EMathError, который сможет работать с SysUtils.EInvalidOp, SysUtils.EZeroDivide, SysUtils.Overflow и SysUtils.EUnderflow.
Классы исключений иногда содержат поля, методы или свойства для передачи дополнительной информации об ошибке:
type EInOutError = class(Exception) ErrorCode: Integer; end;
Инициирование и обработка исключений
Для инициирования исключения вам необходимо использовать экземпляр класса исключения с инструкцией raise:
raise EMathError.Create;
Вообще говоря, инструкция инициирования исключения имеет следующий вид:
raise object at address
где object и at address являются опциональными. Когда указывается address, им может быть любое выражение, которое можно обработать как указатель. Обычно это указатель на процедуру или функцию. Например:
raise Exception.Create('Missing parameter') at @MyFunction;
Этой опцией можно инициировать исключение из участка стека, предшествующего тому, в котором на самом деле случилась ошибка. Когда исключение инициировано, то есть после вызова инструкции raise, управление им осуществляется по специальному принципу обработки исключительных ситуаций. Инструкция raise не возвращает управление в точку своего выполнения. Вместо этого управление передается обработчику исключений соответствующего класса, который находится в самом большом уровне вложенности блоков try except (т.е. относящийся к блоку, вход в который был осуществлен позже всего).
Например, приведенная ниже функция преобразует строку в целое число и инициирует исключение в том случае, если результирующее значение не находится в обозначенном диапазоне.
function StrToIntRange(const S: string; Min, Max: Longint): Longint; begin Result := StrToInt(S); // StrToInt объявлен в SysUtils if (Result < Min) or (Result > Max) then raise ERangeError.CreateFmt('%d is not within the valid range of %d..%d', [Result, Min, Max]); end;
Обратите внимание на метод CreateFmt , вызываемый в инструкции raise. Класс SysUtils.Exception и его потомки оснащены особыми конструкторами, которые предоставляют возможность создавать сообщения об ошибках и идентификаторы контекста.
Инициированное исключение автоматически разрушается после его обработки. Не следует пытаться разрушать исключения самостоятельно.
Замечание: Вызов (инициирование) исключения в секции инициализации модуля может не привести к ожидаемому результату. Обычно обработка исключительных ситуаций выполняется из модуля SysUtils, который должен быть инициализирован прежде чем выполнять такие функции. Если исключительная ситуация имеет место в процессе инициализации – все инициализированные модули (включая SysUtils) финализируются, а исключение инициируется повторно. Затем, после перехвата, исключение обрабатывается (обычно завершением приложения). Инициирование исключений в секции финализации модуля также может не привести к нужному результату, в том случае, если на момент инициирования исключения модуль SysUtils уже финализирован.
Инструкции Try…except
Исключительные ситуации обрабатываются обрабатываются внутри конструкций try…except. Например:
try X := Y/Z; except on EZeroDivide do HandleZeroDivide; end;
Эта конструкция предпринимает попытку деления y на z, а в случае возникновения исключительной ситуации SysUtils.EZeroDivide вызывает подпрограмму HandleZeroDivide.
Синтаксис инструкции try…except:
try statements except exceptionBlock end
где statements – это последовательность инструкций, разделенных точками с запятой (;), а exceptionBlock – может быть:
- еще одной последовательностью инструкций
- еще одной последовательностью инструкций или последовательностью обработчиков исключений, разделяемых инструкциями else.
Обработчик исключения имеет вид:
on identifier: type do statement
где identifier: необязателен (если присутствует – может быть любым допустимым идентификатором), type – это тип для представления исключений, а statement – это любая инструкция.
Инструкция try…except выполняет команды из изначального списка. Если исключительных ситуаций не возникло, блок исключений игнорируется и управление передается следующей части программы.
Если в процессе выполнения списка команд возникла исключительная ситуация (она может быть инициирована инструкцией raise в списке команд или внутри процедуры или функции, вызываемой из этого списка) предпринимается попытка обработки исключительной ситуации:
Если какой-либо из обработчиков в exception block подходит для обработки инициированного исключения, управление передается первому такому обработчику. Обработчик подходит для исключения только в том случае, когда тип, указанный в нем, является типом исключения или предком его класса.
Если обработчика не найдено, управление передается инструкции в секции else (если таковая присутствует).
Если блок обработчиков – это просто последовательность инструкций без каких либо обработчиков – управление передается первой инструкции в этой последовательности.
Если ни одно из указанных выше условий не выполнено, поиск продолжается в блоке обработчиков внешней инструкции try…except , выход из которой еще не выполнен. Если и там не находится соответствующего обработчика, секции else или последовательности инструкций, поиск продолжается в следующей внешней инструкции и так далее. Если в самой внешней инструкции исключительная ситуация не будет обработана – приложение завершается.
Когда исключительная ситуация обработана, в стеке находится процедура или функция, содержащая инструкцию try…except , в которой произошла обработка и управление передается выполненному обработчику, секции else или последовательности инструкций. Этот процесс отменяет все вызовы процедур и функций, имевшие место после входа в блок try…except, в котором исключительная ситуация была обработана. Объект исключения автоматически разрушается вызовом деструктора Destroy и упрвление передается инструкции, следующей за блоком try…except. (Если вызов стандартных процедур Exit, Break или Continue выводит управление из обработчика, объект исключения также разрушается).
В следующем примере первый обработчик обрабатывает исключение деления на ноль, второй – ошибки переполнения, а последний – остальные математические исключения. SysUtils.EMathError указан последним в блоке обработчиков, так как он является предком обоих этих классов. Если бы он указан первым, последующие обработчики никогда не были бы вызваны:
try ... except on EZeroDivide do HandleZeroDivide; on EOverflow do HandleOverflow; on EMathError do HandleMathError; end;
В обработчике исключения перед именем класса исключения можно указать идентификатор. Таким образом объявляется идентификатор для представления объекта исключения в процессе выполнения инструкции, следующей за on…do. Видимость идентификатора ограничивается этой инструкцией. Например:
try ... except on E: Exception do ErrorDialog(E.Message, E.HelpContext); end;
Если блок исключения имеет секцию else, в этой секции происходит обработка всех исключений, которые не были обработаны в блоке обработчика исключения. То есть:
try ... except on EZeroDivide do HandleZeroDivide; on EOverflow do HandleOverflow; on EMathError do HandleMathError; else HandleAllOthers; end;
В этом примере секция else обрабатывает все исключения, не являющимися SysUtils.EMathError.
Блок исключения, не содержащий обработчиков, но содержащий список инструкций, обрабатывает любые исключения. Например:
try ... except HandleException; end;
Здесь подпрограмма HandleException обрабатывает все исключенительные ситуации, которые возникают при выполнении инструкций между try и except.
Повторное инициирование исключений
Когда ключевое слово raise включается в блок исключения без указания ссылки на объект, оно инициирует исключение, уже обработанное в блоке. Это позволяет обработчику исключения среагировать на ошибку и затем повторно инициировать исключение. Повторное инициирование исключения может оказаться полезным, когда процедура или функция должна освободить память после своего выполнения, но не может полностью обработать исключение.
Например, функция GetFileList создает объект TStringList и заполняет его именами файлов, расположенными в определенной для поиска папке:
function GetFileList(const Path: string): TStringList; var I: Integer; SearchRec: TSearchRec; begin Result := TStringList.Create; try I := FindFirst(Path, 0, SearchRec); while I = 0 do begin Result.Add(SearchRec.Name); I := FindNext(SearchRec); end; except Result.Free; raise; end; end;
GetFileList создает объект TStringList, затем применяет функции FindFirst и FindNext (определены в модуле SysUtils) для его инициализации. Если в процессе инициализации происходит ошибка – например, из-за того, что путь задан неверно или для заполенения списка строк недостаточно памяти — GetFileList нужно высвободить память созданного списка при том, что вызывающая подпрограмма ничего не знает о его существовании. По этой причине, инициализация списка строк выполняется внутри инструкции try…except. Если возникает исключительная ситуация, блок обработки исключения разрушает список строк и повторно инициирует исключение.
Встроенные исключения
Код, выполняемый в обработчике исключения может сам инициировать и обрабатывать исключительные ситуации. Поскольку исключения обрабатываются внутри обработчика, они не оказывают влияния на первичное исключение. Тем не менее, если исключение инициированное внутри обработчика, не обрабатывается в нем, первичное исключение теряется. Эту ситуацию иллюстрирует функция Tan, код которой приведен ниже:
type ETrigError = class(EMathError); function Tan(X: Extended): Extended; begin try Result := Sin(X) / Cos(X); except on EMathError do raise ETrigError.Create('Invalid argument to Tan'); end; end;
Если исключение SysUtils.EMathError инициируется при выполнении Tan, обработчик инициирует исключение ETrigError. Поскольку Tan не имеет обработчика для ETrigError, это исключение выходит из обработчика, тем самым вызывая разрушение исключения SysUtils.EMathError. Вызывающей подпрограмме передается ETrigErrorexception, инициированное внутри функции Tan.
Инструкции Try…finally
Иногда вам необходимо быть уверенными, что некоторые части операции выполнены, вне зависимости от того, прерывается ли выполнение операции исключением или нет. Например, если подпрограмма получает управление ресурсом, важно чтобы память, которую он занимает, была освобождена несмотря на то, что подпрограмма завершается ошибкой. В таких ситуациях вы можете использовать инструкцию try…finally.
В следующем примере показан код, который открывает и обрабатывает файл, при этом файл будет в конечном итоге закрыт, даже если в процессе обработки произойдет ошибка:
Reset(F); try ... // process file F finally CloseFile(F); end;
Синтаксис инструкции try…finally выглядит следующим образом:
try statementList1 finally statementList2 end
где каждый statementList – это последовательность инструкций, разделенных точками с запятой (;). Инструкция try…finally выполняет инструкции в statementList1 (секция try). Если выполнение statementList1 завершается без ошибок, выполняется statementList2 (секция finally). Если в процессе выполнения statementList1 возникает исключительная ситуация, управление передается в statementList2, когда statementList2 завершает свою работу исключение инициируется повторно. Если в процессе выполнения statementList1 происходит вызов процедур Exit, Break или Continue – управление выходит из statementList1 , statementList2 выполняется автоматически. Таким образом секция finally выполняется всегда, вне зависимости от того, как завершается работа секции try.
Если в секции finally возникает исключительная ситуация, которая не обрабатывается, исключение выводится из из инструкции try…finally, а исключение, инициированное в секции try, разрушается. Таким образом, секция finally должна обработать все внутренние исключительные ситуации с тем, чтобы они не были переданы за пределы секции.
Стандартные классы подпрограммы для работы с исключениями
Модули SysUtils и System объявляют несколько стандартных подпрограмм для обработки исключений, включая ExceptObject, ExceptAddr и ShowException. SysUtils, System и прочие модули включают в себя множество классов исключений, которые наследуются от SysUtils.Exception (или от OutlineError).
Класс SysUtils.Exception имеет свойства Message и HelpContext, которые можно использовать для передачи описания ошибки и идентификатора контекста для контекстной онлайн документации. Кроме того, этот класс объявляет конструкторы, позволяющие различными способами задавать описание и идентификатор контекста.
Глава 6 Программирование приложений с графическим интерфейсом
____________________________________________________________________
но возникновение исключений. После except следуют операторы, которые образуют секцию обработки исключений. Признаком конца секции служит ключевое слово end. Внутри секции программист указывает классы исключе-
ний (говорят еще типы исключений) после слова on, а затем после ключевого слова do оператор обработки исключения, причем оператор может быть со-
ставным. После необязательного else следуют операторы обработки исклю-
чений, не вошедшие в перечень on. Если программисту нужно только устано-
вить сам факт исключения, независимо от типа, то он может просто записать обработчик исключения после слова except.
Вторая конструкция имеет вид:
try
<Потенциально «опасные» операторы, при выполнении которых могут возникнуть исключительные ситуации >
finally
<операторы, которые должны быть выполнены в любом случае, незави-
симо от того, произошло исключение или нет >
end;
В чем их различие? В конструкции try..except если при выполнении операторов секции try возникло исключение, то управление передается в сек-
цию except, где и происходит обработка исключения. Если же исключения не произошло, то операторы блока except просто пропускаются. В конструкции try..finally операторы будут выполнены независимо от того, произошло исключение или нет.
Рассмотрим пример:
try
num:=StrToInt(Stroka);
521
6.3 Визуальное программирование в среде Lazarus
____________________________________________________________________
except
on EConvertError do
ShowMessage(‘Ошибка преобразования строки в целое число‘);
end;
Здесь сообщение будет выведено только в том случае, когда невозможно преобразование строки символов в целое число, то есть когда возникнет ис-
ключение EConvertError.
Конструкции try..except и try..finally могут быть вложены друг в друга на неограниченную глубину. Рассмотрим реализацию примера с файлом.
Procedure Test; var
F: TextFile; number: integer; s: string;
begin
AssignFile(F, ‘Data.txt’);
Rewrite(F);
s:= ’12#4′; |
// В файл намеренно записывается |
|
Writeln(F, s); |
// ошибочная строка символов |
|
Reset(F); |
||
try |
// начало секции (блока) try..except |
|
Readln(F, s); |
||
try |
// начало секции try..finally |
number:= StrToInt(s);
finally
CloseFile(F); // эти два оператора будут выполнены
522
Глава 6 Программирование приложений с графическим интерфейсом
____________________________________________________________________
DeleteFile(‘Data.txt’); // в любом случае
end; // конец секции try..finally
except
on EConvertError do
ShowMessage(‘Ошибка преобразования’); end; // конец секции try..except
end;
Вернемся к примеру, в котором осуществляется ввод двух целых чисел и выполняются четыре арифметических действия (сложение, вычитание, умно-
жение и деление нацело), рис. 6.30. Модифицируем программу, добавив в него обработку исключений. Перепишите обработчики события OnKeyPress для
LabeledEdit1 и событий OnClick кнопок SpeedButton1, SpeedButton2, SpeedButton3 и SpeedButton4 в виде:
procedure TForm1.LabeledEdit1KeyPress(Sender: TObject; var Key: char);
begin
if Key = #13 then begin
try StrToInt(LabeledEdit1.Text);
except
on EConvertError do begin
ShowMessage(‘Ошибка преобразования! Вероятно, ‘ +
‘Вы ошиблись при вводе числа‘);
exit;
end;
523
6.3 Визуальное программирование в среде Lazarus
____________________________________________________________________
end;
LabeledEdit2.SetFocus; SpeedButton1.Down:= false; SpeedButton2.Down:= false; SpeedButton3.Down:= false; SpeedButton4.Down:= false; exit;
end;
end;
procedure TForm1.SpeedButton1Click(Sender: TObject); begin
try LabeledEdit3.Text:=IntToStr(StrToInt(LabeledEdit1.Text)
+ StrToInt(LabeledEdit2.Text));
except
on EConvertError do begin
ShowMessage(‘Ошибка преобразования! Вероятно, ‘ + ‘Вы ошиблись при вводе второго числа‘);
exit;
end;
end;
StatusBar1.SimpleText:= ‘Сложение‘;
LabeledEdit1.SetFocus;
LabeledEdit1.SelectAll;
end;
procedure TForm1.SpeedButton2Click(Sender: TObject);
524
Глава 6 Программирование приложений с графическим интерфейсом
____________________________________________________________________
begin try
LabeledEdit3.Text:=IntToStr(StrToInt(LabeledEdit1.Text)
— StrToInt(LabeledEdit2.Text));
except
on EConvertError do begin
ShowMessage(‘Ошибка преобразования! Вероятно, ‘ + ‘Вы ошиблись при вводе второго числа‘);
exit;
end;
end;
StatusBar1.SimpleText:= ‘Вычитание‘;
LabeledEdit1.SetFocus;
LabeledEdit1.SelectAll;
end;
procedure TForm1.SpeedButton3Click(Sender: TObject); begin
try LabeledEdit3.Text:=IntToStr(StrToInt(LabeledEdit1.Text)
* StrToInt(LabeledEdit2.Text));
except
on EConvertError do begin
ShowMessage(‘Ошибка преобразования! Вероятно, ‘ +
‘Вы ошиблись при вводе второго числа‘);
exit;
end;
525
6.3 Визуальное программирование в среде Lazarus
____________________________________________________________________
end;
StatusBar1.SimpleText:= ‘Умножение‘;
LabeledEdit1.SetFocus;
LabeledEdit1.SelectAll;
end;
procedure TForm1.SpeedButton4Click(Sender: TObject); begin
try LabeledEdit3.Text:=IntToStr(StrToInt(LabeledEdit1.Text)
div StrToInt(LabeledEdit2.Text));
except
on EConvertError do begin
ShowMessage(‘Ошибка преобразования! Вероятно, ‘ + ‘Вы ошиблись при вводе второго числа‘);
exit;
end;
on EDivByZero do begin
ShowMessage(‘Ошибка! Произошло деление на ноль. Вероятно, ‘ + ‘Вы ошиблись при вводе второго числа‘);
exit;
end;
end;
StatusBar1.SimpleText:= ‘Деление нацело‘;
LabeledEdit1.SetFocus;
LabeledEdit1.SelectAll;
end;
526
Глава 6 Программирование приложений с графическим интерфейсом
____________________________________________________________________
Теперь при вводе недопустимого символа для целого числа, а также при вводе числа 0 в качестве второго операнда программа перехватывает исключе-
ния и реагирует соответствующим образом.
Имейте в виду, если вы запускаете программу из среды Lazarus, то исклю-
чения будет перехватывать отладчик. Поэтому лучше запускать программу из командной строки или в настройках окружения для отладчика добавьте нужные вам типы исключений в список игнорирования.
Обработку исключений вполне можно применять и в консольных прило-
жениях. Вспомним программу из 2.1.14. Организуем контроль ввода данных,
используя механизм исключений.
program int_operations_control; {$mode objfpc}{$H+}
uses
CRT, FileUtil, SysUtils; var
A, B, C: integer; begin
writeln(UTF8ToConsole(‘Введите два числа‘)); readln(A, B);
writeln(‘A= ‘, A, ‘ B= ‘, B); C:= A + B;
writeln(UTF8ToConsole(‘Демонстрация сложения, C= ‘), C); C:= A * B;
writeln(UTF8ToConsole(‘Демонстрация умножения, C= ‘), C); try
C:= A div B;
writeln(UTF8ToConsole(‘Демонстрация деления нацело, C= ‘),
C);
527
6.3 Визуальное программирование в среде Lazarus
____________________________________________________________________
except
on EDivByZero do begin
writeln(UTF8ToConsole(‘Ошибка!! Деление на ноль.‘)); writeln(UTF8ToConsole(‘Нажмите любую клавишу‘)); readkey;
exit;
end;
end;
C:= A mod B;
writeln(UTF8ToConsole(‘Остаток от деления, C= ‘), C); C:= A — B;
writeln(UTF8ToConsole(‘Демонстрация вычитания, C= ‘), C); writeln(UTF8ToConsole(‘Нажмите любую клавишу‘)); readkey;
end.
Сравните эту программу с программой из 2.1.25.
Контроль и обработку ошибок с применением механизма исключений удобнее использовать, когда работа программы уже не зависит от действий пользователя, т.е. все необходимые данные от пользователя уже получены,
программа перешла непосредственно к обработке данных – идут вычисления,
происходит чтение и запись в файлы, обращения к различным внешним уст-
ройствам и т.д. На этапе же ввода данных, когда пользователь в режиме диало-
га вводит какие-то данные с клавиатуры, удобнее использовать другой способ контроля. Поскольку обработка исключений происходит после завершения ввода пользователем, то есть этим происходит только констатация факта, что ошибка пользователем уже совершена. Приходится организовывать повторный ввод, причем с того места, где пользователь ошибся. А это приведет к усложне-
528
Глава 6 Программирование приложений с графическим интерфейсом
____________________________________________________________________
нию кода. Гораздо разумнее и эффективней при вводе данных просто не давать пользователю совершить ошибку. Например, если пользователь должен вво-
дить только целые числа, проще контролировать введенные пользователем символы и, если будет попытка ввести недопустимый для целых чисел символ,
просто этот символ игнорировать.
Для этого удобнее использовать другую разновидность компонента TEdit
– TMaskEdit из страницы Additional.
6.3.7.1. Компонент TMaskEdit
В этом компоненте имеется свойство EditMask, с помощью которого можно задать маску ввода. Маска это некий шаблон, задающий какие символы может вводить пользователь в окне ввода. Недопустимые символы игнориру-
ются. Маска состоит из трех частей, между которыми ставится точка с запятой
(;). В первой части маски – шаблоне записываются символы (табл. 6.2), кото-
рые указывают какие символы можно вводить в каждой позиции.
Таблица 6.2
Означает, что в EditText недостающие символы предваряются
!пробелами. В случае отсутствия символа пробелы размещаются в конце.
0 Означает, что в данной позиции должна быть цифра.
9 Означает, что в данной позиции может быть цифра или ничего.
#Означает, что в данной позиции может быть цифра, знак « + », знак «-» или ничего.
Далее через точку с запятой (;) записывается 1 или 0 в зависимости от того,
надо или нет, чтобы символы, добавляемые маской, включались в свойство
Text компонента. В третьей части маски указывается символ-заполнитель, ис-
пользуемый для обозначения позиций, в которых еще не осуществлен ввод. Ус-
тановить нужную маску можно прямо в свойстве EditMask, введя необходи-
мые символы маски или в редакторе масок, открыть который можно нажав
529
6.3 Визуальное программирование в среде Lazarus
____________________________________________________________________
кнопку с троеточием, рис. 6.33.
Рис. 6.33. Окно редактора масок
Прочитать результат ввода можно или в свойстве Text, которое, в зависи-
мости от вида второй части маски, включает или не включает в себя символы маски, или в свойстве EditText, содержащем введенный текст вместе с сим-
волами маски.
Итак, давайте применим для нашего примера компонент TMaskEdit.
Удалите из формы LabeledEdit1 и LabeledEdit2. Перенесите на их ме-
сто два компонента TMaskEdit. Также вставьте в форму два компонента
TLabel. В общем, восстановите внешний вид формы как на рисунке 6.30.
Установите следующие свойства MaskEdit1:
AutoSelect = true
EditMask = #9999;0;
TabOrder = 0
свойство Text оставьте пустым.
В качестве символа заполнителя в окошке «Символы для пробелов» в ре-
дакторе масок введите пробел вместо знака подчеркивания.
Установите свойства MaskEdit2:
530
│
English (en) │
русский (ru) │
Вступление
Stacktrace иногда называют backtrace или call stack. Это список снимков стека, помещенных в стек, содержащих адрес возврата и локальные переменные. Поэтому трассировка стека полезна для отслеживания пути выполнения вашей программы (после вызова процедур).
Чтобы получить трассировку стека, компилятор сначала должен обратиться к сгенерированной отладочной информации с использованием ключей:
- -g — генерировать отладочную информацию (в выходном формате по умолчанию для соответствующей платформы, который является dwarf2 на многих платформах)
- -gl — генерировать номера строк для отладочной информации
- -gs — генерировать отладочную информацию для устаревшего режима stabs; не используйте одновременно оба ключа -gs и -gw
- -gw — генерировать отладочную информацию dwarf2; не используйте одновременно оба ключа -gs и -gw
- -Xg — использовать внешний файл символов отладки
Модуль SysUtils
Этот модуль содержит некоторые процедуры, полезные для отладки исключений.
{ Процедуры обработки исключений } function ExceptObject: TObject; function ExceptAddr: Pointer; function ExceptFrameCount: Longint; function ExceptFrames: PPointer;
Модуль System
Этот модуль содержит некоторые связанные со стеком процедуры:
function SysBackTraceStr(Addr:Pointer): ShortString; // Адрес по умолчанию для преобразования строки, назначенный BackTraceStrFunc procedure Dump_Stack(var f : text;bp:pointer); // Дамп стека в текстовый файл procedure DumpExceptionBackTrace(var f:text); // Дамп backtrace в текстовый файл
Процедурная переменная BackTraceStrFunc отвечает за преобразование адреса памяти в строковую отладочную информацию. Поведение по умолчанию реализовано с помощью SysBackTraceStr.
Выводимая информация
Если выбран вывод отладочной информации в виде строк (переключатель компилятора -gl), в программу автоматически включается модуль lineinfo. Этот модуль гарантирует, что отладчики/обработчики исключений смогут найти номера строк выполняющегося кода. Это может быть полезно, если вы не хотите развертывать получающийся исполняемый файл с полной отладочной информацией, но вам нужна полезная информация при возникновении ошибок.
Stabs
Если используется старый формат stabs (-gs), функция BackTraceStrFunc переназначается на StabBackTraceStr.
DWARF
Если выбран формат отладки dwarf (ключ компилятора -gw), в программу автоматически включается модуль lnfodwrf, а функция BackTraceStrFunc преобразуется в DwarfBacktraceStr.
Модуль LCLProc
Этот модуль Lazarus имеет некоторые функции, связанные с отладкой:
//Отладка procedure RaiseGDBException(const Msg: string); procedure RaiseAndCatchException; procedure DumpExceptionBackTrace; procedure DumpStack; function GetStackTrace(UseCache: boolean): string; procedure GetStackTracePointers(var AStack: TStackTracePointers); function StackTraceAsString(const AStack: TStackTracePointers; UseCache: boolean): string; function GetLineInfo(Addr: Pointer; UseCache: boolean): string;
Сброс текущего стека вызовов
См. также справку FPC [для получения] подробностей по сбросу стека/исключений:
- DumpExceptionBackTrace
- dump_stack
procedure DumpCallStack; var I: Longint; prevbp: Pointer; CallerFrame, CallerAddress, bp: Pointer; Report: string; const MaxDepth = 20; begin Report := ''; bp := get_frame; // Этот трюк пропустит элемент SendCallstack // bp:= get_caller_frame(get_frame); try prevbp := bp - 1; I := 0; while bp > prevbp do begin CallerAddress := get_caller_addr(bp); CallerFrame := get_caller_frame(bp); if (CallerAddress = nil) then Break; Report := Report + BackTraceStrFunc(CallerAddress) + LineEnding; Inc(I); if (I >= MaxDepth) or (CallerFrame = nil) then Break; prevbp := bp; bp := CallerFrame; end; except { предотвратить бесконечный сброс, если произошло исключение} end; ShowMessage(Report); end;
Сброс исключений стека вызовов
Стек вызовов исключения может быть получен через функции модуля SysUtils ExceptAddr, ExceptFrames и ExceptFrameCount.
uses SysUtils; procedure DumpExceptionCallStack(E: Exception); var I: Integer; Frames: PPointer; Report: string; begin Report := 'Program exception! ' + LineEnding + 'Stacktrace:' + LineEnding + LineEnding; if E <> nil then begin Report := Report + 'Exception class: ' + E.ClassName + LineEnding + 'Message: ' + E.Message + LineEnding; end; Report := Report + BackTraceStrFunc(ExceptAddr); Frames := ExceptFrames; for I := 0 to ExceptFrameCount - 1 do Report := Report + LineEnding + BackTraceStrFunc(Frames[I]); ShowMessage(Report); Halt; // Конец выполнения программы end;
Обработка исключений
Ручная обработка исключений
Ручное выполнение обработчика исключений может быть вставлено во многие места в коде.
try // ... какая-то операция, которая должна вызвать исключение... raise Exception.Create('Test error'); except on E: Exception do DumpExceptionCallStack(E); end;
ExceptProc из модуля System
Если происходит необработанное исключение, выполняется переменная процедуры ExceptProc. Поведение по умолчанию инициализируется процедурой InitExceptions из модуля System. Если вы хотите, эту процедуру можно переназначить на собственный обработчик.
Пример:
procedure CatchUnhandledException(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer); var Message: string; i: LongInt; hstdout: ^Text; begin hstdout := @stdout; Writeln(hstdout^, 'An unhandled exception occurred at $', HexStr(PtrUInt(Addr), SizeOf(PtrUInt) * 2), ' :'); if Obj is exception then begin Message := Exception(Obj).ClassName + ' : ' + Exception(Obj).Message; Writeln(hstdout^, Message); end else Writeln(hstdout^, 'Exception object ', Obj.ClassName, ' is not of class Exception.'); Writeln(hstdout^, BackTraceStrFunc(Addr)); if (FrameCount > 0) then begin for i := 0 to FrameCount - 1 do Writeln(hstdout^, BackTraceStrFunc(Frames[i])); end; Writeln(hstdout^,''); end;
Событие TApplication.OnException
Это событие можно использовать для перезаписи глобального обработчика исключений приложения по умолчанию. Пользовательский механизм ведения журнала может обеспечивать показ настраиваемого диалога, запись в файл, консоль, отправку отчета на почту, ведение журнала на HTTP-сервер, например,
procedure TMainForm.CustomExceptionHandler(Sender: TObject; E: Exception); begin DumpExceptionCallStack; Halt; // Конец выполнения программы end; procedure TMainForm.FormCreate(Sender: TObject); begin Application.OnException := @CustomExceptionHandler; end; procedure TMainForm.ButtonClick(Sender: TObject); begin raise Exception.Create('Test'); end;
Обработка исключений доп.потока
Обработка исключений, возникающих в потоках, должна выполняться вручную. Метод основного потока TThread Execute вызывается из функции ThreadProc, расположенной в модуле Classes.
function ThreadProc(ThreadObjPtr: Pointer): PtrInt; begin ... try Thread.Execute; except Thread.FFatalException := TObject(AcquireExceptionObject); end; ... end;
В этой функции метод Execute заключен в блок try-except, и все исключения обрабатываются присвоением объекта исключения свойству FatalException объекта TThread как объекта последнего возникшего исключения. Таким образом, исключения не отображаются для пользователя вообще.
В каждом потоке приложения должен быть вставлен отдельный блок try-except, чтобы перехватить все необработанные исключения с помощью специального обработчика исключений.
procedure TMyThread.Execute; begin try // какой-нибудь ошибочный код except on E: Exception do CustomExceptionThreadHandler(Self, E); end; end;
Затем CustomExceptionThreadHandler может получить стек вызовов исключений и управлять отображением сообщений об ошибках или протоколированием отчетов журнала в файл. Поскольку обработчик выполняется из потока, показ диалога сообщений должен быть безопасным для потока с использованием метода Synchronize.
procedure TMainForm.CustomExceptionHandler(Thread: TThread; E: Exception); begin Thread.Synchronize(DumpExceptionCallStack); end;
Использование map-файла
Используйте ключ компилятора -Xm для генерации map-файла.
Исключения в DLL
todo
См.также
- Profiling
- Creating a Backtrace with GDB
- MultiLog — протоколирующий пакет
- log4delphi — протоколирующий пакет
Внешние ссылки
- esprinter — инструмент для получения трассировки стека из запущенной программы FreePascal, заданной идентификатором процесса и идентификатором потока (для Win32)
- PascalBugReports — проект, имитирующий EurekaLog / MadExcept, но долгое время не разрабатывался
With the help of Try-Except blocks, Delphi and Lazarus give us an easy way to catch and handle exceptions. If in the following code an error between «try» and «except» occurs, the code behind the «except» will be executed and we can prevent our application from showing a default error message to the user and implement our own error processing instead:
try // code except on E:Exception do begin // will be executed when there is an error end; end;
However, if you desire a global error handler for all exceptions that occur somewhere in the program, Try-Except quickly reaches its limits. Finally, it would be too cumbersome to write any code of the program into a try-except block in order to redirect the error to a central function from there.
For this reason, I want to show you a way in this tutorial, how you can catch all errors occuring in your application in a single procedure to handle the exceptions there centrally.
Global Error Treatment
We call our procedure GlobalExceptionHandler and we declare it under «public» in this way:
public procedure GlobalExceptionHandler(Sender: TObject; E: Exception);
Our application knows the event «OnException» that is fired when an exception occurs. We want to overwrite this event with our own procedure, so we are writing this code with the following assignment into «OnCreate»:
procedure TForm1.FormCreate(Sender: TObject); begin // Code for Delphi: Application.OnException := GlobalExceptionHandler; // Code for Lazarus: Application.OnException := @GlobalExceptionHandler; end;
Depending on whether we use Delphi or Lazarus for programming, we have to either assign the procedure with a preceding «@» character (Lazarus) or without (Delphi).
Finally, we only have to define our procedure GlobalExceptionHandler:
procedure TForm1.GlobalExceptionHandler(Sender: TObject; E: Exception); begin // this code will be executed in the case of an exception // for example we could show a message... ShowMessage(E.Message); // ...or we can collect the error message with date and time in a memo Memo1.Lines.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss', now) + ' ' + E.Message); end;
This procedure is now called, whenever an error somewhere in our program encounters which triggers an exception.
Within the procedure, we can gather more information about the error. In E.ClassName, the type of our exception is written and the error message can be retrieved from E.Message. So, with this, you can define a different error handling for each type of error or we can simply write E.ClassName and E.Message to something like an error log and save it or whatelse you can imaging. In our example, we simply show the error message via a ShowMessage dialog and log the errors with date and time in a Memo field.
Raise own Exceptions
Of course, this type of global error treatment only works for the errors within our program, which trigger an exception (automatically only for errors such as divisions by zero, access violations and so on). If we also want to treat errors in our own code in our global exceptional handler, we can simply trigger our own exception. This works as follows:
raise Exception.Create('Arbitrary text of the error message');
When we execute this line of code, an exception is created, which will then be passed on to our exception handler procedure and can be evaluated there. We can pass any text as an error message, for example, more information about the error, like which incorrect values or user inputs led to the problem or which occurrences were otherwise responsible for the exception.