Знакомство с Microsoft .NET Framework
Часть 1. Common Language Runtime
Компонент Common Language Runtime
Исполняемые файлы и метаданные
Microsoft Intermediate Language
Microsoft .NET Framework является ключевым компонентом Microsoft .NET и служит платформой для создания, внедрения и выполнения Web-сервисов и приложений. Она предоставляет основанную на стандартах высокопроизводительную многоязычную среду, в которой выполняются различные типы приложений и сервисов.
Введение
В этой и последующих частях статьи мы ознакомимся с Microsoft .NET Framework, с ее основными компонентами — Common Language Runtime (CLR), библиотекой классов и ASP.NET, рассмотрим назначение каждого из них, архитектуру, составные части, а также приведем примеры их использования.
Основные компоненты Microsoft .NET Framework показаны на диаграмме (рис. 1).
Компонент Common Language Runtime, подробно рассматриваемый в данной статье, располагается над сервисами операционной системы, которая в настоящее время является операционной системой Windows, но в дальнейшем таковой может быть практически любая программная платформа. Основное назначение CLR — выполнение приложений, соблюдение всех программных зависимостей, управление памятью, обеспечение безопасности, интеграция с языками программирования и т.п. Среда выполнения обеспечивает множество сервисов, облегчающих создание и внедрение приложений, и существенно улучшает надежность последних.
Разработчики не взаимодействуют с Common Language Runtime напрямую — все сервисы предоставляются унифицированной библиотекой классов, которая располагается над CLR. Эта библиотека содержит более 1000 классов для решения различных программных задач — от взаимодействия с сервисами операционной системы до работы с данными и XML-документами.
Частью указанной библиотеки классов является программная модель для создания Web-приложений, называемая ASP.NET. Она модель содержит классы, компоненты и сервисы, облегчающие создание Web-сервисов и приложений. Отметим, что помимо Web-сервисов и Web-приложений с помощью Microsoft .NET Framework можно создавать и другие типы приложений — консольные приложения, приложения на основе Windows Forms и Windows-сервисы. Но поскольку основной задачей Microsoft .NET Framework является поддержка создания Web-приложений, именно компонент ASP.NET играет наиболее значительную роль в архитектуре Microsoft .NET Framework.
На самом нижнем уровне нашей диаграммы (см. рис. 1) располагаются сервисы операционной системы. Сервисы платформы Windows обеспечивают поддержку таких технологий, как COM+, а также управление транзакциями, очередями сообщений и т.п.
После того как мы рассмотрели основные компоненты Microsoft .NET Framework, перейдем к более подробному изучению важнейшего из них — Common Language Runtime.
Компонент Common Language Runtime
Common Language Runtime обеспечивает среду выполнения .NET-приложений. Среди предоставляемых этой средой функций следует отметить обработку исключительных ситуаций, обеспечение безопасности, средства отладки поддержки версий. Все эти функции доступны из любого языка программирования, соответствующего спецификации Common Language Specification. Microsoft предоставляет три языка программирования, способных использовать CLR, — Visual Basic .NET, Visual C# .NET и Visual C++ With Managed Extensions. Кроме того, ряд третьих фирм работает над .NET-версиями таких языков программирования, как Perl, Python и COBOL.
компилируемый компилятором код для CLR называется управляемым кодом (managed code). Управляемый код пользуется преимуществами среды выполнения и помимо собственно кода содержит метаданные, которые создаются в процессе компиляции и содержат информацию о типах, членах и ссылках, используемых в коде. Метаданные используются средой выполнения:
- для обнаружения классов;
- загрузки классов;
- генерации кода для конкретной платформы;
- обеспечения безопасности.
Среда выполнения также следит за временем жизни объектов. В COM/COM+ с этой целью использовались специальные счетчики (reference counter); в CLR тоже используются счетчики, а удаление объектов из памяти происходит с помощью процесса, называемого сборкой мусора (garbage collection).
Common Language Runtime также задает общую систему типов, используемую всеми языками программирования. Это означает, например, что все языки программирования будут оперировать целочисленными данными или данными с плавающей точкой единого формата и единой длины, а представления строк тоже будут едиными для всех языков программирования. За счет единой системы типов достигается более простая интеграция компонентов и кода, написанных на разных языках программирования. В отличие от COM-технологии, также основанной на наборе стандартных типов, но представляемых в бинарном виде, CLR позволяет выполнять интеграцию кода (который может быть написан на различных языках программирования) в режиме дизайна, а не в режиме выполнения.
После компиляции управляемый код содержит метаданные, описывающие сам компонент, а также компоненты, использовавшиеся для создания кода. Среда выполнения проверяет, доступны ли все необходимые ресурсы. Использование метаданных позволяет отказаться от необходимости хранить информацию о компонентах в реестре. Следовательно, при переносе компонента на другой компьютер нам больше не требуется регистрировать этот компонент (за исключением глобальной сборки — global assembly, которую мы рассмотрим ниже), а удаление компонента сводится к простому удалению содержащей его сборки.
Как видно из приведенного выше функционального описания Common Language Runtime, среда выполнения обеспечивает ряд преимуществ, облегчающих создание, выполнение и внедрение .NET-приложений.
Исполняемые файлы и метаданные
А теперь более подробно остановимся на концепции метаданных. Начнем с того, что создадим примитивную программу на VB.NET. Эта консольная программа выводит строку “Running under .NET” на стандартное устройство вывода и завершает свое выполнение. Код этой программы таков:
'--------------------------------- ' VB.NET-приложение – CONS.VB '--------------------------------- Imports System Module Cons Sub Main() Console.WriteLine("Running under .NET") End Sub End Module
Директива Imports указывает на то, что мы используем классы, реализованные в пространстве имен System. Одним из таких классов является класс Console. Метод WriteLine этого класса мы используем для вывода строки на экран. Откомпилируем эту программу (рис. 2) с помощью пакетного компилятора VB.NET:
vbc cons.vb
В результате компиляции мы получаем исполняемый файл CONS.EXE, который является файлом в формате СOFF/PE с дополнительными секциями, содержащими информацию, необходимую для Common Language Runtime. Мы можем убедиться в этом, выполнив команду DUMPBIN:
dumpbin cons.exe /all
На листинге 1 приведен фрагмент дампа исполняемого файла (показаны только основные элементы).
Дамп исполняемого файла для .NET начинается с обычных заголовков для MS-DOS и COFF-заголовка, стандартных в отношении всех Windows-программ. Далее мы увидим, что наша программа — это 32-битная Windows-программа (PE32). В секции данных #1 располагаются CLR-заголовок и данные. Отметим, что эта секция имеет атрибуты Code и Execute Read, указывающие загрузчику на то, что в секции содержится код, который будет выполняться средой выполнения.
В заголовке CLR Header мы видим импортируемую функцию _CorExeMain, реализованную в mscoree.dll — основном модуле среды выполнения. Для реализации возможности выполнения .NET-файлов под управлением операционных систем Windows 98, Windows Me и Windows 2000 фирма Microsoft изменила код загрузчика: теперь он распознает CLR-элементы в исполняемых файлах и передает управление CLR через точку входа — _CorExeMain. Функция Main() нашей программы вызывается самой средой выполнения.
После того как мы рассмотрели содержимое исполняемого файла, обратимся к той его секции, где размещаются код и метаданные.
Метаданные содержат описания типов, информацию о версии, ссылки на различные сборки и другую информацию, используемую средой выполнения. В целом метаданные представляют собой высокоуровневый вариант библиотеки типов, известной нам из технологии COM/COM+. Метаданные используются самой средой выполнения, загрузчиком классов, JIT-компилятором, а также различными утилитами. Одна из таких утилит — ILDASM, входящая в состав Microsoft .NET Framework. Данная утилита может использоваться не только для просмотра метаданных, но и для изучения кода на промежуточном языке (IL) в том или ином файле (в файле с расширением .EXE или .DLL, рис. 3).
С помощью ILDASM можно сохранить в текстовом файле дамп нашего исполняемого файла. В результате мы получим более подробное описание заголовка, уже рассмотренного выше, а также дополнительную информацию, используемую CLR. На листинге 2 показан фрагмент дампа, содержащий секции, о которых мы расскажем далее.
Как видно из приведенного фрагмента дампа, в нем содержится вся необходимая информация о нашей программе. Первая инструкция на языке IL (.assembly) содержит указание на внешнюю сборку — mscorlib. Следующая инструкция также содержит ссылку на внешнюю сборку. Это будет набор классов для поддержки программ на Visual Basic, реализованный в сборке Microsoft, — Microsoft.VisualBasic. Далее идет инструкция, описывающая нашу программу (в виде сборки) и собственно код. Отметим, что нашей программе присвоен уникальный глобальный идентификатор (GUID).
Обратите внимание на то, что наш класс Cons является наследником класса System.Object — базового класса библиотеки классов .NET Framework. Как видно из кода нашей программы, мы не указывали этого наследования напрямую — этот факт автоматически добавлен компилятором. Класс имеет два метода. Метод Main() является статическим методом, который реализован в коде нашей программы. Второй метод — конструктор ctor(), автоматически добавленный компилятором. Конструктор позволяет другим приложениям создавать экземпляры нашего приложения.
Microsoft Intermediate Language
В вышеприведенном дампе намеренно пропущена важная часть — код на языке IL. Когда мы компилируем наш код, результатом этого процесса становится не код на языке, понятном конкретной платформе, а код на промежуточном языке, называемом Microsoft Intermediate Language (MSIL), который представляет собой набор инструкций, не зависящих от конкретного процессора. Ниже приведен IL-код нашего метода Main():
.method public static void Main() cil managed { .entrypoint .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "Running under .NET" IL_0005: call void [mscorlib]System.Console::WriteLine(string) IL_000a: ret } // end of method Cons::Main
Если опустить пролог, наш код занимает 8 ячеек стека и состоит из 3 инструкций. Первая инструкция — ldstr — загружает строку в стек. Инструкция call вызывает указанный метод из сборки и передает ей параметр. Выполнение завершается инструкцией ret. Несмотря на то что язык IL является достаточно простым, писать программы на нем не имеет особого смысла, однако общее понимание инструкций может помочь вам разобраться в логике работы программ, а также ядра .NET Framework. (Дополнительную информацию можно получить из спецификации Common Language Infrastructure, Partition 3. CIL Instruction Set, доступной на Web-сайте фирмы Microsoft.)
Just-In-Time Compiler
Программа на языке IL остается неизменной до тех пор, пока мы не вызовем на выполнение тот файл, в котором она хранится. В момент, когда управление передается среде исполнения, IL-код преобразуется в код для конкретной платформы. Эту задачу выполняет компилятор, называемый Just-In-Time Compiler (JIT). Теоретически только указанный компилятор является компонентом .NET, зависящим от конкретной платформы. Однако на самом деле от платформы также зависят и значительная часть библиотеки классов, и ряд других компонентов, например следующие компоненты платформы .NET:
- mscorlib
- System
- System.Design
- System.Drawing
- System.Windows.Forms
Тем не менее существует реальная возможность наличия .NET для не-Windows-платформ, и, возможно, уже в ближайшее время мы увидим реализации .NET для Linux.
Следует также обратить внимание на то, что из .NET-программ можно напрямую вызывать функции Windows API. В этом случае наши программы будут привязаны к конкретной платформе и перенести их без изменения кода будет невозможно.
Интересно, что JIT-компилятор не выполняет компиляцию всего IL-кода при первом обращении к программе. Вместо этого каждый метод компилируется при первом обращении к нему, и, таким образом, неиспользуемый код не компилируется. Откомпилированный код хранится в памяти, а последующие обращения к программе выполняют уже откомпилированный код. Microsoft предоставляет специальный компилятор CLR Native Image Generator (NGEN), который выполняет компиляцию всего IL-кода и сохраняет результат на диске (рис. 4).
На рис. 5 представлены пояснения к циклу приложений под управлением .NET.
Сборки
При рассказе о Common Language Runtime мы неоднократно использовали термин «сборка» (assembly). Сборка представляет собой коллекцию из одного или более файлов. Часто эти файлы содержат код, но в состав сборки могут также входить и графические изображения, и ресурсы, и другие бинарные данные, ассоциированные с кодом. Такие сборки называются статическими сборками, поскольку они хранятся на диске. Динамические сборки создаются во время выполнения программы и на диске обычно не сохраняются.
Сборки являются минимальной единицей внедрения, контроля версий, повторного использования и системы безопасности. Каждая сборка содержит специальные метаданные, называемые манифестом. В манифесте содержится информация о классах, типах и ссылках на другие сборки. Сборки могут содержать более одного класса — так, библиотека классов .NET состоит из десятка сборок, каждая из которых содержит несколько десятков классов.
На рис. 6 показаны сборки, состоящие из одного и нескольких файлов.
Для просмотра манифеста сборки можно использовать утилиту ILDASM, о которой было сказано выше. На рис. 7, например, показан манифест для нашей тестовой программы.
Как видно из рисунка, манифест содержит следующую информацию о сборке:
- имя сборки;
- версию;
- файлы в данной сборке;
- сборки, используемые данной сборкой.
Помимо стандартных полей в манифесте могут присутствовать дополнительные поля, задаваемые программистами.
Использование сборок и номеров версии может решить проблему совместимости между различными версиями DLL, известную как DLL Hell. Теперь .NET-программы ищут сборки в локальном каталоге, что позволяет одновременно использовать несколько версий одной и той же сборки без всяких проблем. (Дополнительную информацию можно получить, прочитав спецификацию Common Language Infrastructure, Partition 2. Metadata Definition and Semantics, доступную на Web-сайте фирмы Microsoft.)
Global Assembly Cache
Хотя компоненты .NET не требуют регистрации, необходимо некоторое хранилище для сборок, используемых более чем одним приложением. Common Language Runtime поддерживает два таких хранилища — Download Cache и Global Assembly Cache (GAC). Сборка, используемая более чем одним приложением, помещается в GAC. Если сборка не обнаружена в локальном каталоге или в GAC, то Common Language Runtime пытается прочитать файл конфигурации. В этом файле можно указать местонахождение сборки (code base), и тогда среда выполнения загрузит сборку и сохранит ее в Download Cache. Для просмотра содержимого GAC можно использовать утилиту GACUtil.
Утилита GACUtil также может использоваться для просмотра содержимого Download Cache, для установки и удаления сборок. Отметим, что GAC представляет собой специализированный каталог, располагаемый в C:\ WINNT\assembly (рис. 8).
В следующей части статьи мы рассмотрим последний компонент Common Language Runtime — Common Type System. (Дополнительная информация о Common Language Runtime имеется в спецификации Common Language Infrastructure, Partition 1. Concepts and Architecture на Web-сайте фирмы Microsoft.)
КомпьютерПресс 11'2001