Введение в MSIL - Часть 5: Обработка исключений — Архив WASM.RU

Все статьи

Введение в MSIL - Часть 5: Обработка исключений — Архив WASM.RU

В этой части "Введения в MSIL" я расскажу о конструкциях, которые CLI предоставляет для обработки исключений.

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

.try
{
    /* защищённый код */

    leave.s _CONTINUE
}
<exception handler>

_CONTINUE:

Последняя инструкция в блоке try - это leave.s, которая передаётся контроль код с меткой _CONTINUE. Эта инструкция гарантирует, что стек будет очищен, а соответствующие блоки finally будут выполнены. Из этого следует, что если выход из защищённого блока произошёл каким-либо другим образом, то было брошено исключение. Обработчик исключения должен следовать непосредственно за блоком try.

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

Catch

Обработчик catch или блок catch является одним из самых известных, так как он напрямую предоставляется как C++, так и C#. Этот блок объявляется с помощью ключевого слова catch, вместе с которым задаётся тип исключения, для которого предназначается данный обработчик, а также сам код, которому передаётся контроль. Catch-блоки также для удобство могут быть объединены в цепь и использовать один блок try. Посмотрите следующий пример:

.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. Далее следует блок обработки, где, собственно, и находится код, обрабатывающий исключение.

.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++, используется именно для этой цели.

.try
{
    /* защищённый код */
    
    leave.s _CONTINUE
}
finally
{
    /* очищающий код */

    endfinally
}

Fault

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

.try
{
    /* защищённый код */
    
    leave.s _CONTINUE
}
fault
{
    /* очищающий код */

    endfault
}

Теперь настало время сменить тему и в следующей части мы поговорим о том, как всё это соотносится с такими популярными языками программирования, как C# и C++/CLI.

2002-2013 (c) wasm.ru