Существует два основных способа перехвата ошибок, возникающих в процессе работы программы:
Функция, выполнение которой может привести к ошибочной ситуации, возвращает некоторое значение, сообщающее, успешно или неуспешно функция выполнила свою задачу. Перехват ошибок заключается в том, что в коде, вызывающем такую функцию, стоят проверки ее возвращаемого значения.
Этот способ хорошо работает, если глубина
Этот способ заключается в том, что в случае возникновения ошибки генерируется так называемая исключительная ситуация (исключение), которая описывается некоторым объектом. Генерация исключения приводит к передаче управления на фрагмент кода программы, называемый обработчиком исключения. Преимуществом такого подхода является то, что перехват ошибок локализован в отдельной части программы, а не распределен по всему коду, как в случае с обработкой
Основная трудность для понимания деталей реализации обработки исключений в CLI заключается в том, что обработка исключений частично закодирована в
Для дальнейшего изложения нам понадобится ввести понятия области в коде метода и координат области.
Будем называть областью непрерывную последовательность инструкций в коде метода. При этом область будет определяться своими координатами, а именно парой чисел ( offset, length ), где offset - это смещение первой инструкции области относительно начала length - длина области. Как смещение, так и длину будем измерять в байтах.
Заголовок каждого метода содержит специальный массив, элементы которого называются предложениями обработки исключений (exception handling clause).
Каждое предложение обработки исключений представляет собой структуру, состоящую из нескольких полей. В этих полях записаны координаты двух или трех областей, а именно: в любом предложении присутствуют координаты
Если говорить в терминах языка C#, то
Давайте рассмотрим два возможных формата, в которых кодируется массив предложений обработки исключений. В каждом из двух форматов содержатся одни и те же поля, и различаются они только размерами полей. Первый формат называется коротким форматом (см. таблицу 3.43) и используется тогда, когда смещения областей не превышают 65535 байт, а длины областей не превышают 255 байт. Во втором, длинном формате (см. таблицу 3.44) допускаются любые смещения и длины. Строго говоря, не любые, а укладывающиеся в 32 бита. Но на практике этого более чем достаточно.
| Смещение | Размер | Поле | Описание |
|---|---|---|---|
| 0 | 2 | Flags |
Флаги |
| 2 | 2 | TryOffset |
Координаты |
| 4 | 1 | TryLength |
|
| 5 | 2 | HandlerOffset |
Координаты области обработчика |
| 7 | 1 | HandlerLengh |
|
| 8 | 4 | ClassToken |
|
| 8 | 4 | FilterOffset |
Смещение области фильтра |
| Смещение | Размер | Поле | Описание |
|---|---|---|---|
| 0 | 4 | Flags |
Флаги |
| 4 | 4 | TryOffset |
Координаты |
| 8 | 4 | TryLength |
|
| 12 | 4 | HandlerOffset |
Координаты области обработчика |
| 16 | 4 | HandlerLengh |
|
| 20 | 4 | ClassToken |
|
| 20 | 4 | FilterOffset |
Смещение области фильтра |
Итак, координаты TryOffset, TryLength ), а координаты области обработчика - парой ( HandlerOffset, HandlerLength ). Для области фильтра указывается только ее смещение, потому что подразумевается, что она непосредственно предшествует области обработчика (длину области фильтра можно вычислить: она равна HandlerOffset - FilterOffset ).
Обратите внимание, что смещения полей ClassToken и FilterOffset совпадают. Это означает, что фактически они представляют собой одно поле. Просто иногда оно интерпретируется как
Поле Flags, возможные значения которого перечислены в таблице 3.45, задает тип обработчика.
| Значение | Описание |
|---|---|
| 0 | Обработчик исключений с фильтрацией по типу |
| 1 | Обработчик исключений с пользовательской фильтрацией |
| 2 | Обработчик finally |
| 4 | Обработчик |
Всего возможны четыре типа обработчиков исключений, отличающихся друг от друга тем, по каким критериям принимается решение о передаче на них управления:
Получает управление, если ClassToken предложения обработки исключений.
Решение о том, получит или не получит управление обработчик, принимает код, содержащийся в области фильтра.
finally.Вызывается при выходе из
fault .Вызывается, если внутри
Первые два типа обработчиков мы будем относить к категории обработчиков с фильтрацией, а последние два - к категории обработчиков без фильтрации.
В CIL предусмотрено несколько инструкций, отвечающих за порождение исключений и передачу управления из обработчиков исключений.
Инструкция throw (см. таблицу 3.46) генерирует исключение, включая тем самым механизм обработки исключений.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x7A | throw |
- | Генерирует исключение |
Диаграмма стека для инструкции throw:
... , obj -> ...
Объектная ссылка, которую инструкция throw потребляет со стека вычислений, должна указывать на объект в куче, описывающий исключение. Вообще говоря, в качестве такого объекта может выступать объект любого типа, в том числе упакованный тип-значение, но спецификация System.Exception.
Если при выполнении инструкции throw на стеке вычислений лежит нулевая ссылка, то генерируется исключение System.NullReferenceException.
Инструкция rethrow (см. таблицу 3.47) разрешена только внутри обработчика исключений с фильтрацией по типу и предназначена для генерации того же самого исключения, которое было поймано обработчиком.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xFE 0x1A | rethrow |
- | Генерирует то же самое исключение, что было поймано обработчиком |
Диаграмма стека для инструкции rethrow:
... -> ...
Инструкции, представленные в таблице 3.48, являются аналогами инструкций безусловного перехода и используются для выхода из
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xDD | leave |
int32 |
Выход из области |
| 0xDE | leave.s |
int8 |
Выход из области (короткий период) |
Диаграмма стека для инструкции leave:
... ->
Как видно из диаграммы, побочным эффектом при выполнении инструкции leave является очистка стека вычислений.
Инструкция endfinally (см. таблицу 3.49) используется для выхода из областей обработчиков без фильтрации. У нее есть псевдоним - endfault.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xDC | endfinally (endfault) |
- | Выход из обработчиков finally и |
Диаграмма стека для инструкции endfinally:
... -> ...
Инструкция endfilter (см. таблицу 3.50) завершает область фильтра. Ее основная задача состоит в том, чтобы вернуть целое число (0 или 1). Значение 0 означает, что данное исключение не может быть обработано и нужно поискать другой обработчик. Значение 1 говорит о том, что нужно передать управление на обработчик.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xFE 0x11 | endfilter |
- | Завершение области фильтра |
Диаграмма стека для инструкции endfilter:
... , value -> ...
Итак, в общем случае, предложение обработки исключений определяет три области в коде метода: область защищенного блока, область фильтра и область обработчика (фильтр может отсутствовать). Эти области должны быть расположены в соответствии с определенными правилами:
Передача управления внутрь
leave из области обработчика с фильтрацией, связанной с данной throw ;leave из endfilter из области фильтра;endfinally из области без фильтрации;rethrow из области с фильтрацией.Давайте рассмотрим последовательность действий, осуществляемую системой выполнения для обработки сгенерированного исключения.
Пусть в некотором методе инструкция, расположенная по некоторому адресу, породила исключение. Система выполнения обрабатывает это исключение в два этапа. Задача первого этапа - поиск подходящего для этого исключения обработчика с фильтрацией. Задача второго этапа - выполнение нужных обработчиков без фильтрации и передача управления найденному во время первого этапа обработчику с фильтрацией.
Выполнение первого этапа начинается с просмотра массива предложений обработки исключений, принадлежащего методу, где произошло исключение. В этом массиве осуществляется поиск такого предложения, что:
Таким образом, на первом этапе finally и
Если в методе, внутри которого было сгенерировано исключение, не оказалось подходящего предложения обработки исключений, то система выполнения переходит на следующий метод в
Первый этап может завершиться либо нахождением подходящего предложения, либо обнаружением того факта, что подходящее предложение не существует на всей последовательности методов в
В первом случае система переходит к следующему этапу, а во втором - выполнение программы аварийно завершается.
На втором этапе система выполнения повторно просматривает массивы предложений, вызывая все обработчики без фильтрации. Она останавливается, когда доходит до предложения, найденного на первом этапе, после чего вызывает обработчик, описываемый этим предложением.
В составе .NET Framework SDK поставляется ассемблер ILASM, который позволяет компилировать текстовые файлы, содержащие CIL-код и метаданные. В этом разделе мы проведем краткий обзор формата, в котором записываются эти файлы, и рассмотрим несколько примеров программ. Будем называть IL-форматом формат файлов, поддерживаемый ассемблером ILASM, а программы, записанные в IL-формате, - IL-программами.
Программы в IL-формате состоят из следующих лексических элементов:
Идентификаторы и метки чаще всего представляют последовательности символов, начинающиеся с латинской буквы (или с символов "_", "$", "@" и "?"), за которой следуют латинские буквы, цифры или символы "_", "$", "@" и "?". Кроме того, для идентификаторов и меток существует особая форма записи в апострофах: она позволяет включать в идентификаторы любые символы Unicode. Например:
Label_1 $Name 'Идентификатор'
Несколько идентификаторов могут быть объединены в один идентификатор с помощью точек. Например:
System.Console.WriteLine
Целочисленные константы записываются либо в десятичной системе счисления, либо в шестнадцатеричной (тогда перед ними ставится префикс "0x"). Например:
128 -10 0xFF10B000
В вещественных константах точка используется для разделения целой и дробной части, а символы "e" и "E" служат для указания экспоненциальной части. Кроме того, поддерживается особая форма записи float32 (целая_константа) и float64 (целая_константа), позволяющая представить целое число в виде числа с плавающей точкой. Например:
5.5 -1.05e10 float32(128) float64(50)
Строковые константы записываются в двойных кавычках и могут содержать
"Alpha Beta Gamma" "Hello, World\n" "Concat"+"enation"
Комментарии в IL-программах записываются так же, как в языке C#:
IL-программа представляет собой последовательность объявлений.
В этом разделе мы рассмотрим
Каждый IL-файл для ассемблера ILASM представляет собой отдельный модуль сборки. Мы не будем касаться вопросов компиляции сборки, состоящей из нескольких модулей, поэтому приведем образец заголовка IL-файла для одномодульной сборки:
.assembly MyProgram { }
.module MyProgram.exe
.assembly extern mscorlib { }
В заголовке используются три директивы: директива ".assembly" позволяет задать имя нашей сборки. Директива ".module" определяет имя модуля и совпадает с именем исполняемого файла, в который будет записана откомпилированная сборка. Директива ".assembly extern" указывает, что мы будем импортировать сборку mscorlib, в которой находится основная часть библиотеки классов .NET. В фигурных скобках после имени сборки могут перечисляться свойства сборки, но в простейшем случае их можно оставить пустыми.
Объявление типа осуществляется с помощью директивы ".class" и состоит из четырех частей:
Последовательность атрибутов следует непосредственно после ключевого слова ".class". В таблице 3.51 приведен набор наиболее часто используемых атрибутов.
| Атрибут | Описание |
|---|---|
abstract |
Тип является абстрактным классом |
interface |
Тип является интерфейсом |
private |
Тип не экспортируется из сборки |
public |
Тип экспортируется из сборки |
sealed |
Тип не может являться базовым классом для другого типа (от него нельзя наследовать) |
|
Экземпляры типа могут быть сериализованы |
После атрибутов следует идентификатор, задающий имя объявляемого типа.
Если объявляемый тип наследует от какого-нибудь другого типа (базового класса), отличного от System.Object, то необходимо указать имя базового класса после ключевого слова "extends". При этом, если в качестве базового класса выбран System.ValueType, то объявляемый тип будет типом-значением.
Если объявляемый тип реализует методы каких-либо интерфейсов, то должен быть приведен список этих интерфейсов после ключевого слова "implements".
Рассмотрим несколько примеров:
IEnumerable ..class public abstract MyAbstractClass
extends [mscorlib]System.Object
implements [mscorlib]System.Collections.IEnumerable
{ }
.class private interface MyInterface { }
.class public sealed MyValueType
extends [mscorlib]System.ValueType
{ }
Обратите внимание, что перед именами библиотечных классов и интерфейсов в квадратных скобках указывается имя сборки, в которой они содержатся.
Поля объявляются внутри объявлений типов. Объявление поля осуществляется с помощью директивы ".field" и состоит из трех частей:
Последовательность атрибутов следует непосредственно после ключевого слова ".field". В таблице 3.52 приведен набор наиболее часто используемых атрибутов.
| Атрибут | Описание |
|---|---|
assembly |
Поле видимо внутри сборки |
family |
Поле видимо для наследников типа |
public |
Поле видимо для всех |
private |
Поле видимо только внутри типа |
static |
Поле является статическим |
После атрибутов следует тип поля и идентификатор, задающий имя поля.
Рассмотрим несколько примеров:
x типа массив..field private int32[] x
table типа Hashtable ..field public class [mscorlib]System.Collections.Hashtable table
Методы объявляются внутри объявлений типов. Объявление метода осуществляется с помощью директивы ".method" и состоит из пяти частей:
Последовательность атрибутов следует непосредственно после ключевого слова ".method". В таблице 3.53 приведен набор наиболее часто используемых атрибутов.
После атрибутов следует тип возвращаемого значения и идентификатор, задающий имя метода. Если метод не возвращает значения, в качестве типа возвращаемого значения указывается void. Конструкторы всегда имеют имя ".ctor", а статические конструкторы - ".cctor".
| Атрибут | Описание |
|---|---|
assembly |
Метод видим внутри сборки |
family |
Метод видим для наследников типа |
public |
Метод видим для всех |
private |
Метод видим только внутри типа |
abstract |
Метод является абстрактным |
virtual |
Метод является виртуальным |
final |
Метод не может переопределяться в наследниках |
static |
Метод является статическим |
Список параметров метода следует за именем метода и заключается в круглые скобки. Для каждого параметра указывается его тип и имя.
Прежде чем перейти к рассмотрению
.method public void .ctor (int32 x, class [mscorlib]System.String s)
.method private virtual int32 myMethod(int32 pX)
.method public static int32[] MyStaticMethod()
Hello: ldstr "Hello, World!" call void [mscorlib]System.Console.WriteLine(string)
Кроме инструкций CIL
| Директива | Описание |
|---|---|
.entrypoint |
Показывает, что данный метод является точкой входа в сборку (метод должен быть статическим, возвращать int32 или ничего не возвращать, иметь в качестве параметров массив строк или вообще не иметь параметров) |
.locals (объявления) |
Определяет набор локальных переменных метода. Локальные переменные объявляются аналогично параметрам метода |
.maxstack число |
Задает глубину стека вычислений |
Давайте рассмотрим пример программы, написанной прямо на CIL с использованием синтаксиса ILASM. Мы не станем приводить весь текст программы сразу, а будем рассматривать ее постепенно, по частям.
Естественно, наша программа будет начинаться с заголовка, объявляющего имена сборки и модуля и импортирующего стандартную библиотеку:
.assembly Sample1 { }
.module sample1.exe
.assembly extern mscorlib { }
Далее объявим тип-значение Point, реализующий понятие точки на плоскости. Он будет содержать два поля x и y типа float64, а также конструктор и статический метод, вычисляющий расстояние между двумя точками:
.class public sealed Point extends [mscorlib]System.ValueType
{
.field public float64 x
.field public float64 y
.method public void .ctor (float64 x, float64 y)
{
.maxstack 3
ldarg.0
dup
ldarg.1
stfld float64 Point::x
ldarg.2
stfld float64 Point::y
ret
}
.method public static float64 Distance
(valuetype Point a, valuetype Point b)
{
.maxstack 3
ldarga a
ldfld float64 Point::x
ldarga b
ldfld float64 Point::x
sub
dup
mul
ldarga a
ldfld float64 Point::y
ldarga b
ldfld float64 Point::y
sub
dup
mul
add
call float64 [mscorlib]System.Math::Sqrt(float64)
ret
}
}
А теперь объявим вспомогательный класс SampleClass, который будет содержать точку входа в нашу сборку. Метод Demo (точка входа) будет вычислять расстояние между точками (0.0,0.0) и (1.0,1.0) и выводить результат на экран:
.class public SampleClass
{
.method public static void Demo()
{
.entrypoint
.maxstack 3
ldc.r8 0.0
ldc.r8 0.0
newobj void Point::.ctor(float64,float64)
ldc.r8 1.0
ldc.r8 1.0
newobj void Point::.ctor(float64,float64)
call float64 Point::Distance(valuetype Point, valuetype Point)
call void [mscorlib]System.Console::WriteLine (float64)
ret
}
}
Откомпилируем нашу программу, которая записана в текстовом файле sample1.il. Подразумевается, что мы работаем в Windows и у нас переменная окружения path настроена таким образом, что программы ILASM и PEVERIFY можно вызывать без указания путей. Наберем в консоли команду:
ilasm sample1.il
Мы получим следующее сообщение от компилятора ILASM:
Microsoft (R) .NET Framework IL Assembler. Version 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Assembling 'sample1.il', no listing file, to EXE --> 'sample1.EXE' Source file is ANSI Assembled method Point::.ctor Assembled method Point::Distance Assembled method SampleClass::Demo Creating PE file Emitting members: Global Class 1 Fields: 2; Methods: 2; Class 2 Methods: 1; Resolving member refs: 9 -> 9 defs, 0 refs Writing PE file Operation completed successfully
Итак, наша программа успешно откомпилировалась и на диске появилась сборка sample1.exe. Попробуем провести ее верификацию:
peverify sample1.exe
В ответ верификатор выведет на экран:
Microsoft (R) .NET Framework PE Verifier Version 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. All Classes and Methods in sample1.exe Verified
Таким образом, сборка успешно прошла верификацию и мы можем рискнуть ее запустить:
sample1.exe
В результате на экран выводится расстояние между точками (0.0,0.0) и (1.0,1.0):
1, 4142135623731
Существует два основных способа перехвата ошибок, возникающих в процессе работы программы:
Функция, выполнение которой может привести к ошибочной ситуации, возвращает некоторое значение, сообщающее, успешно или неуспешно функция выполнила свою задачу. Перехват ошибок заключается в том, что в коде, вызывающем такую функцию, стоят проверки ее возвращаемого значения.
Этот способ хорошо работает, если глубина
Этот способ заключается в том, что в случае возникновения ошибки генерируется так называемая исключительная ситуация (исключение), которая описывается некоторым объектом. Генерация исключения приводит к передаче управления на фрагмент кода программы, называемый обработчиком исключения. Преимуществом такого подхода является то, что перехват ошибок локализован в отдельной части программы, а не распределен по всему коду, как в случае с обработкой
Основная трудность для понимания деталей реализации обработки исключений в CLI заключается в том, что обработка исключений частично закодирована в
Для дальнейшего изложения нам понадобится ввести понятия области в коде метода и координат области.
Будем называть областью непрерывную последовательность инструкций в коде метода. При этом область будет определяться своими координатами, а именно парой чисел ( offset, length ), где offset - это смещение первой инструкции области относительно начала length - длина области. Как смещение, так и длину будем измерять в байтах.
Заголовок каждого метода содержит специальный массив, элементы которого называются предложениями обработки исключений (exception handling clause).
Каждое предложение обработки исключений представляет собой структуру, состоящую из нескольких полей. В этих полях записаны координаты двух или трех областей, а именно: в любом предложении присутствуют координаты
Если говорить в терминах языка C#, то
Давайте рассмотрим два возможных формата, в которых кодируется массив предложений обработки исключений. В каждом из двух форматов содержатся одни и те же поля, и различаются они только размерами полей. Первый формат называется коротким форматом (см. таблицу 3.43) и используется тогда, когда смещения областей не превышают 65535 байт, а длины областей не превышают 255 байт. Во втором, длинном формате (см. таблицу 3.44) допускаются любые смещения и длины. Строго говоря, не любые, а укладывающиеся в 32 бита. Но на практике этого более чем достаточно.
| Смещение | Размер | Поле | Описание |
|---|---|---|---|
| 0 | 2 | Flags |
Флаги |
| 2 | 2 | TryOffset |
Координаты |
| 4 | 1 | TryLength |
|
| 5 | 2 | HandlerOffset |
Координаты области обработчика |
| 7 | 1 | HandlerLengh |
|
| 8 | 4 | ClassToken |
|
| 8 | 4 | FilterOffset |
Смещение области фильтра |
| Смещение | Размер | Поле | Описание |
|---|---|---|---|
| 0 | 4 | Flags |
Флаги |
| 4 | 4 | TryOffset |
Координаты |
| 8 | 4 | TryLength |
|
| 12 | 4 | HandlerOffset |
Координаты области обработчика |
| 16 | 4 | HandlerLengh |
|
| 20 | 4 | ClassToken |
|
| 20 | 4 | FilterOffset |
Смещение области фильтра |
Итак, координаты TryOffset, TryLength ), а координаты области обработчика - парой ( HandlerOffset, HandlerLength ). Для области фильтра указывается только ее смещение, потому что подразумевается, что она непосредственно предшествует области обработчика (длину области фильтра можно вычислить: она равна HandlerOffset - FilterOffset ).
Обратите внимание, что смещения полей ClassToken и FilterOffset совпадают. Это означает, что фактически они представляют собой одно поле. Просто иногда оно интерпретируется как
Поле Flags, возможные значения которого перечислены в таблице 3.45, задает тип обработчика.
| Значение | Описание |
|---|---|
| 0 | Обработчик исключений с фильтрацией по типу |
| 1 | Обработчик исключений с пользовательской фильтрацией |
| 2 | Обработчик finally |
| 4 | Обработчик |
Всего возможны четыре типа обработчиков исключений, отличающихся друг от друга тем, по каким критериям принимается решение о передаче на них управления:
Получает управление, если ClassToken предложения обработки исключений.
Решение о том, получит или не получит управление обработчик, принимает код, содержащийся в области фильтра.
finally.Вызывается при выходе из
fault .Вызывается, если внутри
Первые два типа обработчиков мы будем относить к категории обработчиков с фильтрацией, а последние два - к категории обработчиков без фильтрации.
В CIL предусмотрено несколько инструкций, отвечающих за порождение исключений и передачу управления из обработчиков исключений.
Инструкция throw (см. таблицу 3.46) генерирует исключение, включая тем самым механизм обработки исключений.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x7A | throw |
- | Генерирует исключение |
Диаграмма стека для инструкции throw:
... , obj -> ...
Объектная ссылка, которую инструкция throw потребляет со стека вычислений, должна указывать на объект в куче, описывающий исключение. Вообще говоря, в качестве такого объекта может выступать объект любого типа, в том числе упакованный тип-значение, но спецификация System.Exception.
Если при выполнении инструкции throw на стеке вычислений лежит нулевая ссылка, то генерируется исключение System.NullReferenceException.
Инструкция rethrow (см. таблицу 3.47) разрешена только внутри обработчика исключений с фильтрацией по типу и предназначена для генерации того же самого исключения, которое было поймано обработчиком.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xFE 0x1A | rethrow |
- | Генерирует то же самое исключение, что было поймано обработчиком |
Диаграмма стека для инструкции rethrow:
... -> ...
Инструкции, представленные в таблице 3.48, являются аналогами инструкций безусловного перехода и используются для выхода из
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xDD | leave |
int32 |
Выход из области |
| 0xDE | leave.s |
int8 |
Выход из области (короткий период) |
Диаграмма стека для инструкции leave:
... ->
Как видно из диаграммы, побочным эффектом при выполнении инструкции leave является очистка стека вычислений.
Инструкция endfinally (см. таблицу 3.49) используется для выхода из областей обработчиков без фильтрации. У нее есть псевдоним - endfault.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xDC | endfinally (endfault) |
- | Выход из обработчиков finally и |
Диаграмма стека для инструкции endfinally:
... -> ...
Инструкция endfilter (см. таблицу 3.50) завершает область фильтра. Ее основная задача состоит в том, чтобы вернуть целое число (0 или 1). Значение 0 означает, что данное исключение не может быть обработано и нужно поискать другой обработчик. Значение 1 говорит о том, что нужно передать управление на обработчик.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xFE 0x11 | endfilter |
- | Завершение области фильтра |
Диаграмма стека для инструкции endfilter:
... , value -> ...
Итак, в общем случае, предложение обработки исключений определяет три области в коде метода: область защищенного блока, область фильтра и область обработчика (фильтр может отсутствовать). Эти области должны быть расположены в соответствии с определенными правилами:
Передача управления внутрь
leave из области обработчика с фильтрацией, связанной с данной throw ;leave из endfilter из области фильтра;endfinally из области без фильтрации;rethrow из области с фильтрацией.Давайте рассмотрим последовательность действий, осуществляемую системой выполнения для обработки сгенерированного исключения.
Пусть в некотором методе инструкция, расположенная по некоторому адресу, породила исключение. Система выполнения обрабатывает это исключение в два этапа. Задача первого этапа - поиск подходящего для этого исключения обработчика с фильтрацией. Задача второго этапа - выполнение нужных обработчиков без фильтрации и передача управления найденному во время первого этапа обработчику с фильтрацией.
Выполнение первого этапа начинается с просмотра массива предложений обработки исключений, принадлежащего методу, где произошло исключение. В этом массиве осуществляется поиск такого предложения, что:
Таким образом, на первом этапе finally и
Если в методе, внутри которого было сгенерировано исключение, не оказалось подходящего предложения обработки исключений, то система выполнения переходит на следующий метод в
Первый этап может завершиться либо нахождением подходящего предложения, либо обнаружением того факта, что подходящее предложение не существует на всей последовательности методов в
В первом случае система переходит к следующему этапу, а во втором - выполнение программы аварийно завершается.
На втором этапе система выполнения повторно просматривает массивы предложений, вызывая все обработчики без фильтрации. Она останавливается, когда доходит до предложения, найденного на первом этапе, после чего вызывает обработчик, описываемый этим предложением.
В составе .NET Framework SDK поставляется ассемблер ILASM, который позволяет компилировать текстовые файлы, содержащие CIL-код и метаданные. В этом разделе мы проведем краткий обзор формата, в котором записываются эти файлы, и рассмотрим несколько примеров программ. Будем называть IL-форматом формат файлов, поддерживаемый ассемблером ILASM, а программы, записанные в IL-формате, - IL-программами.
Программы в IL-формате состоят из следующих лексических элементов:
Идентификаторы и метки чаще всего представляют последовательности символов, начинающиеся с латинской буквы (или с символов "_", "$", "@" и "?"), за которой следуют латинские буквы, цифры или символы "_", "$", "@" и "?". Кроме того, для идентификаторов и меток существует особая форма записи в апострофах: она позволяет включать в идентификаторы любые символы Unicode. Например:
Label_1 $Name 'Идентификатор'
Несколько идентификаторов могут быть объединены в один идентификатор с помощью точек. Например:
System.Console.WriteLine
Целочисленные константы записываются либо в десятичной системе счисления, либо в шестнадцатеричной (тогда перед ними ставится префикс "0x"). Например:
128 -10 0xFF10B000
В вещественных константах точка используется для разделения целой и дробной части, а символы "e" и "E" служат для указания экспоненциальной части. Кроме того, поддерживается особая форма записи float32 (целая_константа) и float64 (целая_константа), позволяющая представить целое число в виде числа с плавающей точкой. Например:
5.5 -1.05e10 float32(128) float64(50)
Строковые константы записываются в двойных кавычках и могут содержать
"Alpha Beta Gamma" "Hello, World\n" "Concat"+"enation"
Комментарии в IL-программах записываются так же, как в языке C#:
IL-программа представляет собой последовательность объявлений.
В этом разделе мы рассмотрим
Каждый IL-файл для ассемблера ILASM представляет собой отдельный модуль сборки. Мы не будем касаться вопросов компиляции сборки, состоящей из нескольких модулей, поэтому приведем образец заголовка IL-файла для одномодульной сборки:
.assembly MyProgram { }
.module MyProgram.exe
.assembly extern mscorlib { }
В заголовке используются три директивы: директива ".assembly" позволяет задать имя нашей сборки. Директива ".module" определяет имя модуля и совпадает с именем исполняемого файла, в который будет записана откомпилированная сборка. Директива ".assembly extern" указывает, что мы будем импортировать сборку mscorlib, в которой находится основная часть библиотеки классов .NET. В фигурных скобках после имени сборки могут перечисляться свойства сборки, но в простейшем случае их можно оставить пустыми.
Объявление типа осуществляется с помощью директивы ".class" и состоит из четырех частей:
Последовательность атрибутов следует непосредственно после ключевого слова ".class". В таблице 3.51 приведен набор наиболее часто используемых атрибутов.
| Атрибут | Описание |
|---|---|
abstract |
Тип является абстрактным классом |
interface |
Тип является интерфейсом |
private |
Тип не экспортируется из сборки |
public |
Тип экспортируется из сборки |
sealed |
Тип не может являться базовым классом для другого типа (от него нельзя наследовать) |
|
Экземпляры типа могут быть сериализованы |
После атрибутов следует идентификатор, задающий имя объявляемого типа.
Если объявляемый тип наследует от какого-нибудь другого типа (базового класса), отличного от System.Object, то необходимо указать имя базового класса после ключевого слова "extends". При этом, если в качестве базового класса выбран System.ValueType, то объявляемый тип будет типом-значением.
Если объявляемый тип реализует методы каких-либо интерфейсов, то должен быть приведен список этих интерфейсов после ключевого слова "implements".
Рассмотрим несколько примеров:
IEnumerable ..class public abstract MyAbstractClass
extends [mscorlib]System.Object
implements [mscorlib]System.Collections.IEnumerable
{ }
.class private interface MyInterface { }
.class public sealed MyValueType
extends [mscorlib]System.ValueType
{ }
Обратите внимание, что перед именами библиотечных классов и интерфейсов в квадратных скобках указывается имя сборки, в которой они содержатся.
Поля объявляются внутри объявлений типов. Объявление поля осуществляется с помощью директивы ".field" и состоит из трех частей:
Последовательность атрибутов следует непосредственно после ключевого слова ".field". В таблице 3.52 приведен набор наиболее часто используемых атрибутов.
| Атрибут | Описание |
|---|---|
assembly |
Поле видимо внутри сборки |
family |
Поле видимо для наследников типа |
public |
Поле видимо для всех |
private |
Поле видимо только внутри типа |
static |
Поле является статическим |
После атрибутов следует тип поля и идентификатор, задающий имя поля.
Рассмотрим несколько примеров:
x типа массив..field private int32[] x
table типа Hashtable ..field public class [mscorlib]System.Collections.Hashtable table
Методы объявляются внутри объявлений типов. Объявление метода осуществляется с помощью директивы ".method" и состоит из пяти частей:
Последовательность атрибутов следует непосредственно после ключевого слова ".method". В таблице 3.53 приведен набор наиболее часто используемых атрибутов.
После атрибутов следует тип возвращаемого значения и идентификатор, задающий имя метода. Если метод не возвращает значения, в качестве типа возвращаемого значения указывается void. Конструкторы всегда имеют имя ".ctor", а статические конструкторы - ".cctor".
| Атрибут | Описание |
|---|---|
assembly |
Метод видим внутри сборки |
family |
Метод видим для наследников типа |
public |
Метод видим для всех |
private |
Метод видим только внутри типа |
abstract |
Метод является абстрактным |
virtual |
Метод является виртуальным |
final |
Метод не может переопределяться в наследниках |
static |
Метод является статическим |
Список параметров метода следует за именем метода и заключается в круглые скобки. Для каждого параметра указывается его тип и имя.
Прежде чем перейти к рассмотрению
.method public void .ctor (int32 x, class [mscorlib]System.String s)
.method private virtual int32 myMethod(int32 pX)
.method public static int32[] MyStaticMethod()
Hello: ldstr "Hello, World!" call void [mscorlib]System.Console.WriteLine(string)
Кроме инструкций CIL
| Директива | Описание |
|---|---|
.entrypoint |
Показывает, что данный метод является точкой входа в сборку (метод должен быть статическим, возвращать int32 или ничего не возвращать, иметь в качестве параметров массив строк или вообще не иметь параметров) |
.locals (объявления) |
Определяет набор локальных переменных метода. Локальные переменные объявляются аналогично параметрам метода |
.maxstack число |
Задает глубину стека вычислений |
Давайте рассмотрим пример программы, написанной прямо на CIL с использованием синтаксиса ILASM. Мы не станем приводить весь текст программы сразу, а будем рассматривать ее постепенно, по частям.
Естественно, наша программа будет начинаться с заголовка, объявляющего имена сборки и модуля и импортирующего стандартную библиотеку:
.assembly Sample1 { }
.module sample1.exe
.assembly extern mscorlib { }
Далее объявим тип-значение Point, реализующий понятие точки на плоскости. Он будет содержать два поля x и y типа float64, а также конструктор и статический метод, вычисляющий расстояние между двумя точками:
.class public sealed Point extends [mscorlib]System.ValueType
{
.field public float64 x
.field public float64 y
.method public void .ctor (float64 x, float64 y)
{
.maxstack 3
ldarg.0
dup
ldarg.1
stfld float64 Point::x
ldarg.2
stfld float64 Point::y
ret
}
.method public static float64 Distance
(valuetype Point a, valuetype Point b)
{
.maxstack 3
ldarga a
ldfld float64 Point::x
ldarga b
ldfld float64 Point::x
sub
dup
mul
ldarga a
ldfld float64 Point::y
ldarga b
ldfld float64 Point::y
sub
dup
mul
add
call float64 [mscorlib]System.Math::Sqrt(float64)
ret
}
}
А теперь объявим вспомогательный класс SampleClass, который будет содержать точку входа в нашу сборку. Метод Demo (точка входа) будет вычислять расстояние между точками (0.0,0.0) и (1.0,1.0) и выводить результат на экран:
.class public SampleClass
{
.method public static void Demo()
{
.entrypoint
.maxstack 3
ldc.r8 0.0
ldc.r8 0.0
newobj void Point::.ctor(float64,float64)
ldc.r8 1.0
ldc.r8 1.0
newobj void Point::.ctor(float64,float64)
call float64 Point::Distance(valuetype Point, valuetype Point)
call void [mscorlib]System.Console::WriteLine (float64)
ret
}
}
Откомпилируем нашу программу, которая записана в текстовом файле sample1.il. Подразумевается, что мы работаем в Windows и у нас переменная окружения path настроена таким образом, что программы ILASM и PEVERIFY можно вызывать без указания путей. Наберем в консоли команду:
ilasm sample1.il
Мы получим следующее сообщение от компилятора ILASM:
Microsoft (R) .NET Framework IL Assembler. Version 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Assembling 'sample1.il', no listing file, to EXE --> 'sample1.EXE' Source file is ANSI Assembled method Point::.ctor Assembled method Point::Distance Assembled method SampleClass::Demo Creating PE file Emitting members: Global Class 1 Fields: 2; Methods: 2; Class 2 Methods: 1; Resolving member refs: 9 -> 9 defs, 0 refs Writing PE file Operation completed successfully
Итак, наша программа успешно откомпилировалась и на диске появилась сборка sample1.exe. Попробуем провести ее верификацию:
peverify sample1.exe
В ответ верификатор выведет на экран:
Microsoft (R) .NET Framework PE Verifier Version 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. All Classes and Methods in sample1.exe Verified
Таким образом, сборка успешно прошла верификацию и мы можем рискнуть ее запустить:
sample1.exe
В результате на экран выводится расстояние между точками (0.0,0.0) и (1.0,1.0):
1, 4142135623731
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.