Метаданные служат для описания типов, находящихся в сборке .NET, и хранятся в исполняемых файлах. Для хранения метаданных используется достаточно сложный двоичный формат, изложение которого заняло бы слишком много времени, поэтому в этом разделе мы ограничимся лишь частичным и в большой степени поверхностным описанием формата метаданных.
Если поставить себе цель в двух словах охарактеризовать формат метаданных в сборках .NET, то можно сказать следующее: он был бы значительно проще, если бы его разработчики не уделяли чрезмерного внимания вопросу компактности хранения метаданных. Дело в том, что
Для того чтобы провести обзор формата метаданных в этом разделе, мы используем следующий прием: рассмотрим только те детали формата, которые используются в незатейливом примере, выводящем на экран надпись "Hello, World!", а затем ожидающим ввода данных с клавиатуры. Может показаться, что пример слишком прост, однако, как мы увидим в дальнейшем, генерация метаданных даже для такого несложного примера требует больших усилий. Сборку .NET, соответствующую нашему примеру, можно получить, если откомпилировать следующую программу, записанную в синтаксисе ассемблера ILASM:
.assembly HelloWorld
{
.hash algorithm 0x00008004
.ver 1:0:1:1
}
.module HelloWorld.exe
// hello() - единственный метод в нашей сборке
.method public static void hello() cil managed
{
.entrypoint
.maxstack 8
// Загружаем строку "Hello, World!" на стек
ldstr "Hello, World!"
// Выводим строку на экран
call void [mscorlib]System.Console::WriteLine(string)
// Ожидаем, пока пользователь введет строку
call string [mscorlib]System.Console::ReadLine()
// Выводим введенную строку на экран
call void [mscorlib]System.Console::WriteLine(string)
// Завершаем выполнение
ret
}
В предыдущем разделе данной главы мы рассмотрели формат исполняемых файлов .NET и выяснили, что он дает разработчику достаточно большую свободу для размещения отдельных элементов внутри исполняемого файла. В частности, исполняемые файлы могут содержать несколько секций, и расположение этих секций, а также данных внутри них является более или менее произвольным.
Давайте выберем для нашего учебного примера схему размещения данных внутри исполняемого файла, изображенную на рисунке 2.7. Мы будем использовать две секции: секция ".text" будет содержать всю основную информацию, а в секции ".
(рис 2.7) Размещение данных внутри исполняемого файла для учебного примера
На схеме видно, что в начале секции ".text" располагается hello, за которым следуют метаданные. Напомним, что расположение метаданных задается в заголовке CLI, который мы рассматривали в предыдущем разделе данной главы. Тем самым, мы вольны выбрать для метаданных практически любое место внутри секции, и их расположение, изображенное на схеме, является лишь одним из многих возможных вариантов.
Заметим, что метаданные и CIL-код практически не зависят от остальных элементов формата исполняемого файла. Они занимают часть сборки .NET, при этом заголовок CLI указывает на метаданные, а внутри метаданных хранятся hello в нашем примере расположено в самом начале секции исключительно для того, чтобы облегчить вычисление
На рисунке 2.8 представлена схема структуры метаданных. Метаданные начинаются с заголовка, называемого корнем метаданных (metadata root). Корень метаданных начинается с 32-разрядной сигнатуры 0x424A5342. Если каждый байт сигнатуры рассматривать в виде ASCII-кода, то получится строка "BSJB", составленная из начальных букв имен четырех основных разработчиков .NET Framework: Брайана Харри (Brian Harry), Сьюзан Радке-Спроулл (Susan Radke-Sproull), Джейсона Зандера (Jason Zander) и Билла Эванса (Bill Evans). Далее в корне метаданных следует информация, относящаяся к версии .NET, а заканчивается корень метаданных 16-разрядным целым числом, содержащим количество так называемых потоков метаданных, заголовки которых располагаются непосредственно после корня метаданных.
(рис 2.8) Cтруктура метаданных
Потоки метаданных (metadata streams) предназначены для хранения определенных видов информации. Заголовок каждого потока метаданных представляет собой структуру, состоящую из трех полей:
long Offset;
Смещение потока метаданных относительно начала метаданных в файле (то есть относительно начала корня метаданных).
long Size;
Размер потока метаданных в байтах (должен быть кратен четырем).
char Name[x];
ASCIIZ-строка, содержащая имя потока метаданных. Это поле имеет переменную длину.
В спецификации CLI определено пять видов потоков метаданных. Четыре потока метаданных представляют собой так называемые кучи, то есть хранилища однородных объектов, таких как строки и GUID'ы, и один поток метаданных имеет реляционную структуру и содержит таблицы метаданных. В таблице 2.2 приведено описание каждого из пяти потоков.
| Поток | Имя потока | Описание |
|---|---|---|
| Куча GUID'ов | "#GUID" | Представляет собой последовательность 128-битных глобальных идентификаторов |
| Куча пользовательских строк | "#US" | Содержит строковые константы, определенные в программе |
| Куча строк | "#Strings" | Содержит названия |
| Куча двоичных данных | "# |
Содержит двоичные данные, описывающие метаданные (например, |
| Таблицы метаданных | "#~" | Содержит физическое представление таблиц метаданных |
В спецификации CLI определены несколько десятков видов таблиц метаданных. Мы ограничимся рассмотрением только тех из них, которые используются в нашем учебном примере.
На рис. 2.9 представлена структура потока таблиц метаданных для учебного примера. Поток таблиц начинается с заголовка таблиц, непосредственно после которого следуют сами таблицы.
(рис 2.9) Cтруктура потока таблиц метаданных
Заголовок таблиц метаданных содержит большое количество полей, из которых нас интересуют в первую очередь три поля:
char HeapSizes;
Различные биты этого поля задают размеры индексов, используемых для адресации куч метаданных. Бит 0 соответствует куче строк, бит 1 - куче GUID'ов, бит 3 - куче двоичных данных.
Если некоторый бит установлен, это означает, что соответствующая куча адресуется 32-разрядными индексами. В противном случае куча адресуется 16-разрядными индексами.
char Valid[8];
Размер этого поля - 64 бита. При этом каждый бит соответствует одной таблице метаданных. Если некоторый бит установлен, значит, соответствующая ему таблица присутствует в метаданных.
unsigned long Rows[7];
Массив 32-разрядных целых чисел, содержащих количество записей в каждой из присутствующих таблиц метаданных. В нашем учебном примере этот массив имеет размер 7, так как мы используем семь таблиц.
На рис. 2.10 представлено распределение Valid.
(рис 2.10) Распределение элементов метаданных учебного примера по таблицам
Давайте подробнее рассмотрим каждую из семи используемых в примере таблиц метаданных.
В этой таблице содержится только одна запись, описывающая нашу сборку. В этой записи для нас интерес представляют следующие поля:
short MajorVersion; short MinorVersion; short BuildNumber; short RevisionNumber;
Эти четыре поля хранят информацию о
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя сборки ("HelloWorld").
Таблица модулей содержит только одну запись, описывающую модуль. В этой записи существенными являются два поля:
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя модуля ("HelloWorld.exe").
short Mvid;
Это поле содержит индекс в куче GUID'ов, по которому хранится глобальный уникальный идентификатор модуля.
В этой таблице каждая запись соответствует одному типу, объявленному в сборке. В нашем учебном примере нет классов, но для того чтобы объявить глобальную функцию, нам требуется специальный абстрактный тип <Module>. Дело в том, что все глобальные функции и поля считаются принадлежащими этому типу. Запись, описывающая этот тип, содержит следующие интересующие нас поля:
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя типа ("<Module>").
short FieldList; short MethodList;
Эти два поля содержат индексы в таблицах полей и методов, начиная с которых расположены описатели полей и методов типа.
Таблица методов описывает методы, объявленные в сборке. Каждая запись этой таблицы содержит информацию об одном методе, представленную в следующих полях:
long RVA;
short Flags;
Набор флагов, задающих область видимости метода и другие его атрибуты.
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя метода.
short Signature;
Индекс в куче двоичных данных, по которому расположена
short ParamList;
В этом поле хранится индекс в таблице описателей параметров метода.
Все импортируемые сборки должны быть перечислены в таблице импортируемых сборок. Каждая запись этой таблицы содержит следующие поля:
short MajorVersion; short MinorVersion; short BuildNumber; short RevisionNumber;
Эти четыре поля хранят информацию о версии импортируемой сборки.
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя импортируемой сборки (в нашем случае это "mscorlib").
Каждая запись в этой таблице соответствует одному из импортируемых типов и содержит следующие поля:
short ResolutionScope;
Специальным образом закодированная информация о том, какой сборке или какому модулю принадлежит данный тип.
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя импортируемого типа (в нашем случае это "Console").
short Namespace;
Это поле содержит индекс в куче строк, по которому хранится имя пространства имен; данному пространству принадлежит импортируемый тип (в нашем случае это "System").
В таблице членов импортируемых типов перечислены все методы, поля и свойства этих типов, которые используются в программе. Каждая запись этой таблицы содержит следующие поля:
short Class;
Специальным образом закодированная информация об импортируемом типе.
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя члена импортируемого типа.
short Signature;
Индекс в куче двоичных данных, по которому расположена сигнатура члена импортируемого типа.
В настоящее время большой популярностью пользуется компонентный подход к разработке программного обеспечения. Этот подход характеризуется тем, что создаваемый программный продукт состоит из взаимодействующих компонентов. При этом различные компоненты могут независимо разрабатываться разными группами программистов, и при создании каждого компонента может применяться наиболее подходящий язык программирования. В качестве примера можно привести Microsoft Visual Studio .NET и Microsoft Office.
К сожалению, проблемы в организации взаимодействия компонентов зачастую перевешивают преимущества компонентного подхода. Действительно, языки программирования используют различные несовместимые между собой модели данных, соглашения о вызове подпрограмм, форматы исполняемых и
Отсутствие удовлетворительной технологии взаимодействия компонентов приводит к следующим негативным явлениям:
В компонентной системе можно выделить три вида взаимодействия компонентов:
В этом разделе мы ограничимся рассмотрением первого случая, то есть изучим, как на платформе .NET реализовано взаимодействие компонентов, работающих в рамках одного процесса. Межпроцессное и сетевое взаимодействие используется, главным образом, в распределенных системах, изучение которых выходит за рамки нашего учебника.
Существует множество способов и технологий организации
Наиболее древний способ заключается в использовании библиотек подпрограмм. Такие библиотеки создаются одной группой разработчиков, а затем могут быть использованы другими программистами в других проектах. Исходный код библиотек может быть закрыт, то есть они могут распространяться в откомпилированном виде, защищая тем самым интересы разработчиков. Однако организация
Динамические библиотеки некоторым образом облегчают
Еще один интересный путь создания компонентных программ придумали в мире открытых исходников, в котором компоненты распространяются в виде исходных текстов. Тем самым вроде бы решается часть проблем. По крайней мере, можно попытаться переносить эти компоненты с платформы на платформу (перекомпилируя исходники и исправляя возникающие ошибки). Кроме того, исходные тексты в отличие от двоичных файлов содержат информацию о типах, что позволяет использовать объектно-ориентированные возможности. Но и здесь нас подстерегают неприятности:
Особого внимания заслуживают технологии COM (Component Object Model) и CORBA (Common
Первое, с чем сталкивается программист при использовании COM или CORBA, это необходимость описывать метаданные на языке IDL (
Технологии COM и CORBA определяют двоичный стандарт для передачи данных между компонентами. Так как компоненты могут быть написаны на разных языках и, соответственно, внутреннее представление данных в них может существенно различаться, то компонент, осуществляющий передачу, вынужден выполнять преобразование передаваемых данных в стандартное представление (маршалинг), а компоненту, принимающему данные, приходится выполнять преобразование данных из стандартного представления к своему внутреннему представлению (демаршалинг). При межпроцессном взаимодействии и взаимодействии по сети затраты на преобразование данных не играют существенной роли, но при взаимодействии внутри адресного пространства одного процесса они могут сильно сказаться на производительности программы.
На рис. 2.11 изображена схема взаимодействия двух объектов при использовании технологии COM или CORBA. Объекты Client и Server находятся в разных компонентах, поэтому объект Client не может непосредственно передать сообщение объекту Server. Вместо этого он обращается к специальному объекту-заглушке ClientStub, который осуществляет упаковку сообщения и затем передает его информационной магистрали
(рис 2.11) Взаимодействия двух объектов через COM или CORBA
(в терминах CORBA информационная магистраль называется ServerStub, который распаковывает сообщение и вызывает соответствующий метод объекта Server. Результат, возвращаемый методом объекта Server, совершает обратный путь к объекту Client аналогичным образом. Данный пример показывает, что использование технологий COM и CORBA связано с большими трудностями. Ситуация, пожалуй, облегчается лишь тем, что объекты-заглушки могут быть сгенерированы автоматически на основе описания объекта Server на языке IDL (для этого существуют специальные компиляторы).
Хотя технологии COM и CORBA продолжают активно применяться, есть все основания утверждать, что в самом ближайшем будущем они будут вытеснены более эффективными и удобными технологиями (например, .NET).
Организация
Во-первых, компилятор любого языка программирования, реализованного на платформе .NET, генерирует сборки, содержащие CIL-код и метаданные. При этом формат этих сборок определяется спецификацией CLI и не зависит от языка программирования.
Во-вторых, любая программа, работающая в среде .NET, использует общую систему типов
В-третьих, выполнение любой программы управляется средой выполнения CLR. Это означает, что среда выполнения контролирует JIT-компиляцию CIL-кода программы, выполняет управление памятью и в каждый момент времени имеет всю информацию о
Эти три особенности платформы .NET позволяют среде выполнения автоматически обеспечивать взаимодействие компонентов вне зависимости от того, на каком языке они написаны.
На рис. 2.12 изображена схема взаимодействия двух объектов на платформе .NET. Объекты Client и Server находятся в разных компонентах, работающих в адресном пространстве одного процесса (здесь мы не рассматриваем Client объекту Server сводится к простому вызову соответствующего метода объекта Server, то есть в среде .NET не нужны никакие объекты-заглушки.
(рис 2.12) Взаимодействия двух объектов в среде .NET
Компоненты на платформе .NET представляют собой сборки .NET. Сборка .NET может статически импортировать любую другую сборку и свободно использовать типы, экспортируемые из этой сборки. Для этого информация об импортируемой сборке заносится в таблицу метаданных AssemblyRef, информация о каждом импортируемом типе - в таблицу TypeRef, а информация о каждом импортируемом методе и поле - в таблицу MemberRef. Кроме того, сборка может импортироваться динамически через рефлексию (мы рассмотрим эту возможность в следующей лекции).
Метаданные в сборках .NET содержат полную информацию о типах. При этом каждый тип может экспортироваться или не экспортироваться из сборки, а каждый член типа (метод, поле, свойство и т.д.) должен быть объявлен с определенным значением флага доступа.
Видимость типа (т.е. экспортируется он из сборки или нет) определяется флагом видимости и хранится в поле Flags соответствующей этому типу записи в таблице метаданных TypeDef. В таблице 2.3 приведен набор флагов видимости для типов.
| Флаг | Значение | Описание |
|---|---|---|
NotPublic |
0x00000000 | Тип не экспортируется из сборки |
Public |
0x00000001 | Тип экспортируется из сборки |
NestedPublic |
0x00000002 | |
NestedPrivate |
0x00000003 | |
NestedFamily |
0x00000004 | |
NestedAssembly |
0x00000005 | |
NestedFamAndAssem |
0x00000006 | |
NestedFamOrAssem |
0x00000007 |
Доступ к методам, полям и свойствам типа определяется значением флага доступа. Описание допустимых значений приводится в таблице 2.4.
| Флаг | Значение | Описание |
|---|---|---|
CompilerControlled |
0x00000000 | Доступ контролируется компилятором |
Private |
0x00000001 | Доступен только внутри типа |
FamAndAssem |
0x00000002 | Доступен наследникам типа, объявленным внутри сборки |
Assembly |
0x00000003 | Доступен только внутри сборки |
Family |
0x00000004 | Доступен наследникам типа |
FamOrAssem |
0x00000005 | Доступен внутри сборки, а также наследникам типа |
Public |
0x00000006 | Доступен везде |
Рассмотрим учебный пример, который представляет собой компонентную систему, написанную сразу на четырех языках.
(рис 2.13) Диаграмма классов учебного примера
Абстрактный класс SortedArray реализован на Visual Basic .NET. В этом классе определено поле Arr, представляющее собой массив целых чисел. Конструктор класса SortedArray копирует в это поле массив, передаваемый ему в качестве параметра, а затем вызывает Sort() для сортировки этого массива. Для доступа к отсортированному массиву используются свойства Array и Count:
Public MustInherit Class SortedArray
Protected Arr() As Integer
Protected MustOverride Sub Sort()
Public Sub New(ByVal A() As Integer)
Dim i As Integer
Arr = New Integer(A.Length - 1) {}
For i = 0 To A.Length - 1
Arr(i) = A(i)
Next
Sort()
End Sub
Default Public ReadOnly Property
Array (ByVal Index As Integer) As Integer
Get
Return Arr(Index)
End Get
End Property
Public ReadOnly Property Count() As Integer
Get
Return Arr.Length
End Get
End Property
End Class
Класс BubbleSortedArray написан на Visual C++ with Managed Extensions. Он переопределяет Sort(), реализуя в нем
using namespace VBLib;
public _ _gc class BubbleSortedArray: public SortedArray
{
protected:
void Sort()
{
for (int i = Arr->Length, flag = 1; i > 1 flag; i--)
{
flag = 0;
for (int j = 0; j < i-1; j++)
if (Arr[j] < Arr[j+1])
{
int tmp = Arr[j];
Arr[j] = Arr[j+1];
Arr[j+1] = tmp;
flag = 1;
}
}
}
public:
BubbleSortedArray(int A _ _gc []): SortedArray(A) { }
};
Класс InsertSortedArray написан на Visual C#. Он переопределяет Sort(), реализуя в нем
using VBLib;
public class InsertSortedArray: SortedArray
{
protected override void Sort()
{
for (int i = 0; i < Arr.Length-1; i++)
{
int max = i;
for (int j = i+1; j < Arr.Length; j++)
if (Arr[j] > Arr[max])
max = j;
int tmp = Arr[i];
Arr[i] = Arr[max];
Arr[max] = tmp;
}
}
public InsertSortedArray(int[] A): base(A) { }
}
И, наконец, все вышеперечисленные классы используются в программе, написанной на Visual J#.
package JsApp;
import VBLib.SortedArray;
public class Main
{
public static void main(String[] args)
{
int A[] = new int[] { 5, 1, 6, 0, -4, 3 };
SortedArray SA1 = new BubbleSortedArray(A),
SA2 = new InsertSortedArray(A);
for (int i = 0; i < SA1.get_Count(); i++)
System.out.print(""+SA1.get_Array(i)+" ");
System.out.println();
for (int i = 0; i < SA2.get_Count(); i++)
System.out.print(""+SA2.get_Array(i)+" ");
System.out.println();
}
}
Различные языки программирования, которые уже реализованы или могут быть реализованы на платформе .NET, используют общую систему типов (
Для того, чтобы любую библиотеку классов можно было использовать из любого языка платформы .NET, разработчики .NET придумали общую спецификацию языков (Common Language Specification - CLS).
В этой спецификации оговариваются правила, которым должны следовать разработчики языков и библиотек. То есть она описывает некоторое подмножество общей системы типов, и если некий язык реализует хотя бы это подмножество, а библиотека использует только входящие в это подмножество типы, то такая библиотека может быть использована из этого языка.
В терминологии CLS библиотеки, соответствующие спецификации CLS, называются средами (frameworks), но мы будем называть их CLS-библиотеками. Компиляторы, генерирующие код, из которого можно получить доступ к CLS-библиотекам, называются потребителями (consumers). Компиляторы, которые являются потребителями, но, к тому же, способны создавать новые CLS-библиотеки, называются расширителями (extenders).
Приведем основные правила общей спецификации языков:
Метаданные служат для описания типов, находящихся в сборке .NET, и хранятся в исполняемых файлах. Для хранения метаданных используется достаточно сложный двоичный формат, изложение которого заняло бы слишком много времени, поэтому в этом разделе мы ограничимся лишь частичным и в большой степени поверхностным описанием формата метаданных.
Если поставить себе цель в двух словах охарактеризовать формат метаданных в сборках .NET, то можно сказать следующее: он был бы значительно проще, если бы его разработчики не уделяли чрезмерного внимания вопросу компактности хранения метаданных. Дело в том, что
Для того чтобы провести обзор формата метаданных в этом разделе, мы используем следующий прием: рассмотрим только те детали формата, которые используются в незатейливом примере, выводящем на экран надпись "Hello, World!", а затем ожидающим ввода данных с клавиатуры. Может показаться, что пример слишком прост, однако, как мы увидим в дальнейшем, генерация метаданных даже для такого несложного примера требует больших усилий. Сборку .NET, соответствующую нашему примеру, можно получить, если откомпилировать следующую программу, записанную в синтаксисе ассемблера ILASM:
.assembly HelloWorld
{
.hash algorithm 0x00008004
.ver 1:0:1:1
}
.module HelloWorld.exe
// hello() - единственный метод в нашей сборке
.method public static void hello() cil managed
{
.entrypoint
.maxstack 8
// Загружаем строку "Hello, World!" на стек
ldstr "Hello, World!"
// Выводим строку на экран
call void [mscorlib]System.Console::WriteLine(string)
// Ожидаем, пока пользователь введет строку
call string [mscorlib]System.Console::ReadLine()
// Выводим введенную строку на экран
call void [mscorlib]System.Console::WriteLine(string)
// Завершаем выполнение
ret
}
В предыдущем разделе данной главы мы рассмотрели формат исполняемых файлов .NET и выяснили, что он дает разработчику достаточно большую свободу для размещения отдельных элементов внутри исполняемого файла. В частности, исполняемые файлы могут содержать несколько секций, и расположение этих секций, а также данных внутри них является более или менее произвольным.
Давайте выберем для нашего учебного примера схему размещения данных внутри исполняемого файла, изображенную на рисунке 2.7. Мы будем использовать две секции: секция ".text" будет содержать всю основную информацию, а в секции ".
(рис 2.7) Размещение данных внутри исполняемого файла для учебного примера
На схеме видно, что в начале секции ".text" располагается hello, за которым следуют метаданные. Напомним, что расположение метаданных задается в заголовке CLI, который мы рассматривали в предыдущем разделе данной главы. Тем самым, мы вольны выбрать для метаданных практически любое место внутри секции, и их расположение, изображенное на схеме, является лишь одним из многих возможных вариантов.
Заметим, что метаданные и CIL-код практически не зависят от остальных элементов формата исполняемого файла. Они занимают часть сборки .NET, при этом заголовок CLI указывает на метаданные, а внутри метаданных хранятся hello в нашем примере расположено в самом начале секции исключительно для того, чтобы облегчить вычисление
На рисунке 2.8 представлена схема структуры метаданных. Метаданные начинаются с заголовка, называемого корнем метаданных (metadata root). Корень метаданных начинается с 32-разрядной сигнатуры 0x424A5342. Если каждый байт сигнатуры рассматривать в виде ASCII-кода, то получится строка "BSJB", составленная из начальных букв имен четырех основных разработчиков .NET Framework: Брайана Харри (Brian Harry), Сьюзан Радке-Спроулл (Susan Radke-Sproull), Джейсона Зандера (Jason Zander) и Билла Эванса (Bill Evans). Далее в корне метаданных следует информация, относящаяся к версии .NET, а заканчивается корень метаданных 16-разрядным целым числом, содержащим количество так называемых потоков метаданных, заголовки которых располагаются непосредственно после корня метаданных.
(рис 2.8) Cтруктура метаданных
Потоки метаданных (metadata streams) предназначены для хранения определенных видов информации. Заголовок каждого потока метаданных представляет собой структуру, состоящую из трех полей:
long Offset;
Смещение потока метаданных относительно начала метаданных в файле (то есть относительно начала корня метаданных).
long Size;
Размер потока метаданных в байтах (должен быть кратен четырем).
char Name[x];
ASCIIZ-строка, содержащая имя потока метаданных. Это поле имеет переменную длину.
В спецификации CLI определено пять видов потоков метаданных. Четыре потока метаданных представляют собой так называемые кучи, то есть хранилища однородных объектов, таких как строки и GUID'ы, и один поток метаданных имеет реляционную структуру и содержит таблицы метаданных. В таблице 2.2 приведено описание каждого из пяти потоков.
| Поток | Имя потока | Описание |
|---|---|---|
| Куча GUID'ов | "#GUID" | Представляет собой последовательность 128-битных глобальных идентификаторов |
| Куча пользовательских строк | "#US" | Содержит строковые константы, определенные в программе |
| Куча строк | "#Strings" | Содержит названия |
| Куча двоичных данных | "# |
Содержит двоичные данные, описывающие метаданные (например, |
| Таблицы метаданных | "#~" | Содержит физическое представление таблиц метаданных |
В спецификации CLI определены несколько десятков видов таблиц метаданных. Мы ограничимся рассмотрением только тех из них, которые используются в нашем учебном примере.
На рис. 2.9 представлена структура потока таблиц метаданных для учебного примера. Поток таблиц начинается с заголовка таблиц, непосредственно после которого следуют сами таблицы.
(рис 2.9) Cтруктура потока таблиц метаданных
Заголовок таблиц метаданных содержит большое количество полей, из которых нас интересуют в первую очередь три поля:
char HeapSizes;
Различные биты этого поля задают размеры индексов, используемых для адресации куч метаданных. Бит 0 соответствует куче строк, бит 1 - куче GUID'ов, бит 3 - куче двоичных данных.
Если некоторый бит установлен, это означает, что соответствующая куча адресуется 32-разрядными индексами. В противном случае куча адресуется 16-разрядными индексами.
char Valid[8];
Размер этого поля - 64 бита. При этом каждый бит соответствует одной таблице метаданных. Если некоторый бит установлен, значит, соответствующая ему таблица присутствует в метаданных.
unsigned long Rows[7];
Массив 32-разрядных целых чисел, содержащих количество записей в каждой из присутствующих таблиц метаданных. В нашем учебном примере этот массив имеет размер 7, так как мы используем семь таблиц.
На рис. 2.10 представлено распределение Valid.
(рис 2.10) Распределение элементов метаданных учебного примера по таблицам
Давайте подробнее рассмотрим каждую из семи используемых в примере таблиц метаданных.
В этой таблице содержится только одна запись, описывающая нашу сборку. В этой записи для нас интерес представляют следующие поля:
short MajorVersion; short MinorVersion; short BuildNumber; short RevisionNumber;
Эти четыре поля хранят информацию о
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя сборки ("HelloWorld").
Таблица модулей содержит только одну запись, описывающую модуль. В этой записи существенными являются два поля:
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя модуля ("HelloWorld.exe").
short Mvid;
Это поле содержит индекс в куче GUID'ов, по которому хранится глобальный уникальный идентификатор модуля.
В этой таблице каждая запись соответствует одному типу, объявленному в сборке. В нашем учебном примере нет классов, но для того чтобы объявить глобальную функцию, нам требуется специальный абстрактный тип <Module>. Дело в том, что все глобальные функции и поля считаются принадлежащими этому типу. Запись, описывающая этот тип, содержит следующие интересующие нас поля:
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя типа ("<Module>").
short FieldList; short MethodList;
Эти два поля содержат индексы в таблицах полей и методов, начиная с которых расположены описатели полей и методов типа.
Таблица методов описывает методы, объявленные в сборке. Каждая запись этой таблицы содержит информацию об одном методе, представленную в следующих полях:
long RVA;
short Flags;
Набор флагов, задающих область видимости метода и другие его атрибуты.
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя метода.
short Signature;
Индекс в куче двоичных данных, по которому расположена
short ParamList;
В этом поле хранится индекс в таблице описателей параметров метода.
Все импортируемые сборки должны быть перечислены в таблице импортируемых сборок. Каждая запись этой таблицы содержит следующие поля:
short MajorVersion; short MinorVersion; short BuildNumber; short RevisionNumber;
Эти четыре поля хранят информацию о версии импортируемой сборки.
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя импортируемой сборки (в нашем случае это "mscorlib").
Каждая запись в этой таблице соответствует одному из импортируемых типов и содержит следующие поля:
short ResolutionScope;
Специальным образом закодированная информация о том, какой сборке или какому модулю принадлежит данный тип.
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя импортируемого типа (в нашем случае это "Console").
short Namespace;
Это поле содержит индекс в куче строк, по которому хранится имя пространства имен; данному пространству принадлежит импортируемый тип (в нашем случае это "System").
В таблице членов импортируемых типов перечислены все методы, поля и свойства этих типов, которые используются в программе. Каждая запись этой таблицы содержит следующие поля:
short Class;
Специальным образом закодированная информация об импортируемом типе.
short Name;
Это поле содержит индекс в куче строк, по которому хранится имя члена импортируемого типа.
short Signature;
Индекс в куче двоичных данных, по которому расположена сигнатура члена импортируемого типа.
В настоящее время большой популярностью пользуется компонентный подход к разработке программного обеспечения. Этот подход характеризуется тем, что создаваемый программный продукт состоит из взаимодействующих компонентов. При этом различные компоненты могут независимо разрабатываться разными группами программистов, и при создании каждого компонента может применяться наиболее подходящий язык программирования. В качестве примера можно привести Microsoft Visual Studio .NET и Microsoft Office.
К сожалению, проблемы в организации взаимодействия компонентов зачастую перевешивают преимущества компонентного подхода. Действительно, языки программирования используют различные несовместимые между собой модели данных, соглашения о вызове подпрограмм, форматы исполняемых и
Отсутствие удовлетворительной технологии взаимодействия компонентов приводит к следующим негативным явлениям:
В компонентной системе можно выделить три вида взаимодействия компонентов:
В этом разделе мы ограничимся рассмотрением первого случая, то есть изучим, как на платформе .NET реализовано взаимодействие компонентов, работающих в рамках одного процесса. Межпроцессное и сетевое взаимодействие используется, главным образом, в распределенных системах, изучение которых выходит за рамки нашего учебника.
Существует множество способов и технологий организации
Наиболее древний способ заключается в использовании библиотек подпрограмм. Такие библиотеки создаются одной группой разработчиков, а затем могут быть использованы другими программистами в других проектах. Исходный код библиотек может быть закрыт, то есть они могут распространяться в откомпилированном виде, защищая тем самым интересы разработчиков. Однако организация
Динамические библиотеки некоторым образом облегчают
Еще один интересный путь создания компонентных программ придумали в мире открытых исходников, в котором компоненты распространяются в виде исходных текстов. Тем самым вроде бы решается часть проблем. По крайней мере, можно попытаться переносить эти компоненты с платформы на платформу (перекомпилируя исходники и исправляя возникающие ошибки). Кроме того, исходные тексты в отличие от двоичных файлов содержат информацию о типах, что позволяет использовать объектно-ориентированные возможности. Но и здесь нас подстерегают неприятности:
Особого внимания заслуживают технологии COM (Component Object Model) и CORBA (Common
Первое, с чем сталкивается программист при использовании COM или CORBA, это необходимость описывать метаданные на языке IDL (
Технологии COM и CORBA определяют двоичный стандарт для передачи данных между компонентами. Так как компоненты могут быть написаны на разных языках и, соответственно, внутреннее представление данных в них может существенно различаться, то компонент, осуществляющий передачу, вынужден выполнять преобразование передаваемых данных в стандартное представление (маршалинг), а компоненту, принимающему данные, приходится выполнять преобразование данных из стандартного представления к своему внутреннему представлению (демаршалинг). При межпроцессном взаимодействии и взаимодействии по сети затраты на преобразование данных не играют существенной роли, но при взаимодействии внутри адресного пространства одного процесса они могут сильно сказаться на производительности программы.
На рис. 2.11 изображена схема взаимодействия двух объектов при использовании технологии COM или CORBA. Объекты Client и Server находятся в разных компонентах, поэтому объект Client не может непосредственно передать сообщение объекту Server. Вместо этого он обращается к специальному объекту-заглушке ClientStub, который осуществляет упаковку сообщения и затем передает его информационной магистрали
(рис 2.11) Взаимодействия двух объектов через COM или CORBA
(в терминах CORBA информационная магистраль называется ServerStub, который распаковывает сообщение и вызывает соответствующий метод объекта Server. Результат, возвращаемый методом объекта Server, совершает обратный путь к объекту Client аналогичным образом. Данный пример показывает, что использование технологий COM и CORBA связано с большими трудностями. Ситуация, пожалуй, облегчается лишь тем, что объекты-заглушки могут быть сгенерированы автоматически на основе описания объекта Server на языке IDL (для этого существуют специальные компиляторы).
Хотя технологии COM и CORBA продолжают активно применяться, есть все основания утверждать, что в самом ближайшем будущем они будут вытеснены более эффективными и удобными технологиями (например, .NET).
Организация
Во-первых, компилятор любого языка программирования, реализованного на платформе .NET, генерирует сборки, содержащие CIL-код и метаданные. При этом формат этих сборок определяется спецификацией CLI и не зависит от языка программирования.
Во-вторых, любая программа, работающая в среде .NET, использует общую систему типов
В-третьих, выполнение любой программы управляется средой выполнения CLR. Это означает, что среда выполнения контролирует JIT-компиляцию CIL-кода программы, выполняет управление памятью и в каждый момент времени имеет всю информацию о
Эти три особенности платформы .NET позволяют среде выполнения автоматически обеспечивать взаимодействие компонентов вне зависимости от того, на каком языке они написаны.
На рис. 2.12 изображена схема взаимодействия двух объектов на платформе .NET. Объекты Client и Server находятся в разных компонентах, работающих в адресном пространстве одного процесса (здесь мы не рассматриваем Client объекту Server сводится к простому вызову соответствующего метода объекта Server, то есть в среде .NET не нужны никакие объекты-заглушки.
(рис 2.12) Взаимодействия двух объектов в среде .NET
Компоненты на платформе .NET представляют собой сборки .NET. Сборка .NET может статически импортировать любую другую сборку и свободно использовать типы, экспортируемые из этой сборки. Для этого информация об импортируемой сборке заносится в таблицу метаданных AssemblyRef, информация о каждом импортируемом типе - в таблицу TypeRef, а информация о каждом импортируемом методе и поле - в таблицу MemberRef. Кроме того, сборка может импортироваться динамически через рефлексию (мы рассмотрим эту возможность в следующей лекции).
Метаданные в сборках .NET содержат полную информацию о типах. При этом каждый тип может экспортироваться или не экспортироваться из сборки, а каждый член типа (метод, поле, свойство и т.д.) должен быть объявлен с определенным значением флага доступа.
Видимость типа (т.е. экспортируется он из сборки или нет) определяется флагом видимости и хранится в поле Flags соответствующей этому типу записи в таблице метаданных TypeDef. В таблице 2.3 приведен набор флагов видимости для типов.
| Флаг | Значение | Описание |
|---|---|---|
NotPublic |
0x00000000 | Тип не экспортируется из сборки |
Public |
0x00000001 | Тип экспортируется из сборки |
NestedPublic |
0x00000002 | |
NestedPrivate |
0x00000003 | |
NestedFamily |
0x00000004 | |
NestedAssembly |
0x00000005 | |
NestedFamAndAssem |
0x00000006 | |
NestedFamOrAssem |
0x00000007 |
Доступ к методам, полям и свойствам типа определяется значением флага доступа. Описание допустимых значений приводится в таблице 2.4.
| Флаг | Значение | Описание |
|---|---|---|
CompilerControlled |
0x00000000 | Доступ контролируется компилятором |
Private |
0x00000001 | Доступен только внутри типа |
FamAndAssem |
0x00000002 | Доступен наследникам типа, объявленным внутри сборки |
Assembly |
0x00000003 | Доступен только внутри сборки |
Family |
0x00000004 | Доступен наследникам типа |
FamOrAssem |
0x00000005 | Доступен внутри сборки, а также наследникам типа |
Public |
0x00000006 | Доступен везде |
Рассмотрим учебный пример, который представляет собой компонентную систему, написанную сразу на четырех языках.
(рис 2.13) Диаграмма классов учебного примера
Абстрактный класс SortedArray реализован на Visual Basic .NET. В этом классе определено поле Arr, представляющее собой массив целых чисел. Конструктор класса SortedArray копирует в это поле массив, передаваемый ему в качестве параметра, а затем вызывает Sort() для сортировки этого массива. Для доступа к отсортированному массиву используются свойства Array и Count:
Public MustInherit Class SortedArray
Protected Arr() As Integer
Protected MustOverride Sub Sort()
Public Sub New(ByVal A() As Integer)
Dim i As Integer
Arr = New Integer(A.Length - 1) {}
For i = 0 To A.Length - 1
Arr(i) = A(i)
Next
Sort()
End Sub
Default Public ReadOnly Property
Array (ByVal Index As Integer) As Integer
Get
Return Arr(Index)
End Get
End Property
Public ReadOnly Property Count() As Integer
Get
Return Arr.Length
End Get
End Property
End Class
Класс BubbleSortedArray написан на Visual C++ with Managed Extensions. Он переопределяет Sort(), реализуя в нем
using namespace VBLib;
public _ _gc class BubbleSortedArray: public SortedArray
{
protected:
void Sort()
{
for (int i = Arr->Length, flag = 1; i > 1 flag; i--)
{
flag = 0;
for (int j = 0; j < i-1; j++)
if (Arr[j] < Arr[j+1])
{
int tmp = Arr[j];
Arr[j] = Arr[j+1];
Arr[j+1] = tmp;
flag = 1;
}
}
}
public:
BubbleSortedArray(int A _ _gc []): SortedArray(A) { }
};
Класс InsertSortedArray написан на Visual C#. Он переопределяет Sort(), реализуя в нем
using VBLib;
public class InsertSortedArray: SortedArray
{
protected override void Sort()
{
for (int i = 0; i < Arr.Length-1; i++)
{
int max = i;
for (int j = i+1; j < Arr.Length; j++)
if (Arr[j] > Arr[max])
max = j;
int tmp = Arr[i];
Arr[i] = Arr[max];
Arr[max] = tmp;
}
}
public InsertSortedArray(int[] A): base(A) { }
}
И, наконец, все вышеперечисленные классы используются в программе, написанной на Visual J#.
package JsApp;
import VBLib.SortedArray;
public class Main
{
public static void main(String[] args)
{
int A[] = new int[] { 5, 1, 6, 0, -4, 3 };
SortedArray SA1 = new BubbleSortedArray(A),
SA2 = new InsertSortedArray(A);
for (int i = 0; i < SA1.get_Count(); i++)
System.out.print(""+SA1.get_Array(i)+" ");
System.out.println();
for (int i = 0; i < SA2.get_Count(); i++)
System.out.print(""+SA2.get_Array(i)+" ");
System.out.println();
}
}
Различные языки программирования, которые уже реализованы или могут быть реализованы на платформе .NET, используют общую систему типов (
Для того, чтобы любую библиотеку классов можно было использовать из любого языка платформы .NET, разработчики .NET придумали общую спецификацию языков (Common Language Specification - CLS).
В этой спецификации оговариваются правила, которым должны следовать разработчики языков и библиотек. То есть она описывает некоторое подмножество общей системы типов, и если некий язык реализует хотя бы это подмножество, а библиотека использует только входящие в это подмножество типы, то такая библиотека может быть использована из этого языка.
В терминологии CLS библиотеки, соответствующие спецификации CLS, называются средами (frameworks), но мы будем называть их CLS-библиотеками. Компиляторы, генерирующие код, из которого можно получить доступ к CLS-библиотекам, называются потребителями (consumers). Компиляторы, которые являются потребителями, но, к тому же, способны создавать новые CLS-библиотеки, называются расширителями (extenders).
Приведем основные правила общей спецификации языков:
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.