Введение в MSIL - Часть 5: Обработка исключений — Архив WASM.RU
В этой части "Введения в MSIL" я расскажу о конструкциях, которые CLI предоставляет для обработки исключений.
Блок try используется для защиты блока инструкций. Если одна из них бросает исключение или один из методов явно или неявно, вызываемых в защищённом блоке, то контроль передаётся соответствующему обработчику инструкций. Блок try объявляется с помощью директивы .try.
Код (Text):
.try { /* защищённый код */ leave.s _CONTINUE } <exception handler> _CONTINUE:Последняя инструкция в блоке try - это leave.s, которая передаётся контроль код с меткой _CONTINUE. Эта инструкция гарантирует, что стек будет очищен, а соответствующие блоки finally будут выполнены. Из этого следует, что если выход из защищённого блока произошёл каким-либо другим образом, то было брошено исключение. Обработчик исключения должен следовать непосредственно за блоком try.
CLI предлагает четыре разных типа обработчика, из которых вы можете выбирать в зависимости от вашего языка или приложения. Давайте рассмотрим каждый из них.
Catch
Обработчик catch или блок catch является одним из самых известных, так как он напрямую предоставляется как C++, так и C#. Этот блок объявляется с помощью ключевого слова catch, вместе с которым задаётся тип исключения, для которого предназначается данный обработчик, а также сам код, которому передаётся контроль. Catch-блоки также для удобство могут быть объединены в цепь и использовать один блок try. Посмотрите следующий пример:
Код (Text):
.try { ldstr "I'm not a number" // ldnull // ldstr "123" call int32 [mscorlib]System.Int32::Parse(string) leave.s _CONTINUE } catch [mscorlib]System.ArgumentNullException { callvirt instance string [mscorlib]System.Exception::get_Message() call void [mscorlib]System.Console::WriteLine(string) leave.s _CONTINUE } catch [mscorlib]System.FormatException { callvirt instance string [mscorlib]System.Exception::get_Message() call void [mscorlib]System.Console::WriteLine(string) leave.s _CONTINUE }Здесь мы просим метод Int32::Parse обработать "I'm not a number", что очевидным образом вызывает исключение FormatException. Первый catch-обработчик никогда не запускается. Обработчик FormatException послушно пишет в консоль сообщение, описывающее ошибку, а затем вызывает инструкцию leave.s, чтобы передать контроль коду по метке _CONTINUE. Где находился объект исключения? Среда выполнения гарантирует, что ссылка на исключения будет помещена в стек прежде, чем будет вызван обработчик. Вы можеет поэкспериментировать, закомментировав инструкцию ldstr и раскомментировав ldnull. Это поместит в стек null-ссылку, из-за чего вознинет исключение ArgementNullException.
Filter
Для C++-программиста filter-обработчик является довольно странной конструкцией. Данный обработчик предоставляет блок видимости, где он может решить, хочет ли он обрабатывать исключение. Если да, то в стек помещается значение 1, в противном же случае - 0. Далее следует блок обработки, где, собственно, и находится код, обрабатывающий исключение.
Код (Text):
.try { // ldstr "I'm not a number" ldnull // ldstr "123" call int32 [mscorlib]System.Int32::Parse(string) leave.s _CONTINUE } filter { ldstr "filter evaluation\n\t" call void [mscorlib]System.Console::Write(string) callvirt instance string [mscorlib]System.Exception::get_Message() call void [mscorlib]System.Console::WriteLine(string) ldc.i4.1 endfilter } { ldstr "filter handler\n\t" call void [mscorlib]System.Console::Write(string) callvirt instance string [mscorlib]System.Exception::get_Message() call void [mscorlib]System.Console::WriteLine(string) leave.s _CONTINUE }Try-блок вызовет исключение ArgumentNullException, так как в стек в качестве аргумента метода Int32::Parse была помещена пустая ссылка.
Фильтру даётся шанс определить, будет ли он обрабатывать исключение или нет. В данном случае он просто пишет сообщение об ошибке и помещает в стек значение 1, используя инструкцию ldc.i4.1, чтобы указать, что он собирается обрабатывать исключение. Инструкция endfillter вызывается, чтобы возвратиться из фильтра.
Затем вызывается блок обработки. Обратите внимание, что как один, так и другой блок могут обратиться к исключению на лету, взяв соответствующий объект из стека. Держите в уме, что могут быть брошены ссылки на объекты различных типов, поэтому не ожидайте, что ссылка на объект, которую вы достанете из стека в блоке обработки обязательно будет экземпляров System.Exception. Это не проблема для catch-обработчика, так как вы уже знаете тип исключения.
Finally
Finally-обработчик исключения должны узнать C#- и C++-программисты, знакомые с Structured Exception Handling (SEH). Блок finally, ассоцированный с блоком try выполняется всегда, вне зависимости от того, передаёт ли последний контроль нормальным образом или в результате исключения. Это предоставляет надёжный механизм для того, чтобы гарантировать выполнение определённого блока кода для языков, которые не предоставляют деструкторы, такие как C и C#. Хотя язык С не использует CLI, SEH часто применяется, и слово __finally, предоставляемое компилятором Microsoft C/C++, используется именно для этой цели.
Код (Text):
.try { /* защищённый код */ leave.s _CONTINUE } finally { /* очищающий код */ endfinally }Fault
Fault-обработчик исключения похож на finally, не считая того, что он запускается только если в ассоциированном с ним блоке try произошло исключение. После того, как отрабатывает fault-обработчик, исключение продолжает искать соответствующий catch-обработчик.
Код (Text):
.try { /* защищённый код */ leave.s _CONTINUE } fault { /* очищающий код */ endfault }Теперь настало время сменить тему и в следующей части мы поговорим о том, как всё это соотносится с такими популярными языками программирования, как C# и C++/CLI. © Кенни Керр, пер. Aquila
Введение в MSIL - Часть 5: Обработка исключений
Дата публикации 25 сен 2006