January 24, 2019

Что такое CLR

Наиболее важный компонент .NET Framework - это общеязыковая среда выполнения Common Language Runtime (CLR). CLR управляет кодом и исполняет код, написанный на языках .NET, и является фундаментом всей архитектуры .NET, подобно виртуальной машине Java. CLR активизирует объекты, выполняет для них проверку безопаснос­ти, располагает в памяти, исполняет их и выполняет сборку мусора.

Сейчас мы рассмотрим среду CLR, исполняемые файлы, метаданные, сборки, манифесты, CTS и CLS.

Среда CLR

CLR - это инфраструктура, лежащая в основе .NET. В отличие от таких библиотек программного обеспечения, как MFC или ATL, CLR построена с чистого листа. CLR управляет исполнением кода в .NET Framework.

(Сборка (asseтbly) - это основная единица для развертывания и контроля версий, состоящая из манифеста, набора из одного или более модулей и необязательного набора ресурсов.)

На рис. 2.1 показаны две части среды .NET: нижняя - это CLR, а верхняя - исполняемые файлы CLR или переносимые исполняемые файлы (Portable Executable, РЕ), являющиеся сборками .NET или единицами развертывания. CLR - это механизм времени выполнения, загружающий требуемые классы, выполняющий оперативную (just-in-time) компиляцию необходимых методов, проверку безопасности и много других функций времени выполнения. Исполняемые файлы CLR, показанные на рис. 2.1, являются ЕХЕ- или DLL-файлами, состоящими в основном из метаданных и кода.Рис. 2.1. Среда CLR

Переносимый исполняемый файл .NET

Исполняемый файл Windows, ЕХЕ или DLL, должен соответствовать формату, который называется форматом РЕ и является производным от формата Мiсrоsоft Common Object File Format (COFF). Для обоих этих форматов существуют и свободно доступны полные спецификации. ОС Windows знает, как загружать и исполнять DLL- и ЕХЕ-файлы, т. к. понимает формат РЕ-файла. Поэтому любой компилятор должен генерировать исполняемый файл Windows в соответствии со спецификацией PE/COFF.

Стандартные РЕ-файлы Windows делятся на две основных секции. Первая включает в себя заголовки PE/COFF со ссылками на содержимое внутри РЕ-файла. Кроме секции заголовков в РЕ-файле имеется несколько секций двоичных образов, включающих секции .data, .rdata, .rsrc и .text. Это стандартные секции типичного исполняемого файла Windows, однако компилятор Мiсrоsоft С и С++ позволяет добавлять в РЕ-файл собственные секции с помощью директивы компилятора pragma. Например, можно создать собственные секции, содержащие зашифрованные данные, которые сможете читать только вы сами. Пользуясь этой возможностью, Мiсrоsоft добавила несколько новых секций в обычный РЕ-файл специально для поддержки функциональности CLR. Среда CLR понимает и управляет новыми секциями. Например, она читает эти секции и определяет, каким образом загружать классы и выполнять ваш код.

Как показано на рис. 2.2, разделы, добавленные Мicrоsоft к обычному формату РЕ, - это заголовок CLR и раздел данных CLR. Заголовок CLR содержит информацию, указывающую, что РЕ-файл является исполняемым файлом .NET, а раздел данных CLR содержит метаданные и IL­, код, которые вместе определяют, как будет выполняться программа.

Рис. 2.2. Формат РЕ-файла .NET

Метаданные

Метаданные (metadata) - это данные о ресурсах, или «данные о данных», предназначенные для чтения машиной. Это могут быть подробные сведения о содержимом, формате, размере или других характеристиках источника данных. В .NET метаданные включают определения типов, сведения о версии, ссылки на внешние сборки и другую стандартизованную информацию.

В Microsoft .NET метаданные служат для описания всех типов, используемых и предоставляемых данной сборкой .NET. В этом смысле метаданные описывают сборку в деталях, включая ее идентификатор (комбинация имени сборки, версии, культуры и открытого ключа), типы, на которые она ссылается, экспортируемые типы и требования безопасности для исполнения. Метаданные включают описания сборки и модулей, классов, интерфейсов, методов, свойств, полей, событий, глобальных методов и т. д.

Метаданные предоставляют достаточно информации для любой среды выполнения, утилиты или программы, чтобы выяснить буквально все, что требуется для интеграции компонентов.

Метаданные гарантируют возможность межъязыкового взаимодействия, основного элемента .NET, т. к. все языки должны использовать одни и те же типы, чтобы сгенерировать корректный РЕ-файл для .NET, которая не сможет во время выполнения поддерживать функции управления памятью, безопасности, распределения памяти, проверки типов, отладки и т. д. без метаданных. Вот почему метаданные - крайне важная часть .NET, столь важная, что мы спокойно можем утверждать, что без метаданных не было бы .NET.

Сборки и манифесты

Как мы только что видели, типы должны предоставлять свои метаданные, чтобы утилиты и программы могли обращаться к ним и пользоваться предоставляемыми ими сервисами. Но одних метаданных недостаточно. Чтобы упростить программный plug-and-play, а также настройку и установку компонента или программного обеспечения, нам также потребуются метаданные о компонентах, которые содержат эти типы. Сейчас мы поговорим о сборках .NET (единицах развертывания) и манифестах (метаданных, описывающих сборки).

Сборки и компоненты

В эпоху СОМ в документации Мiсrоsоft термин компонент (сотропent) непоследовательно применялся для обозначения как класса СОМ, так и СОМ-модуля (DLL или ЕХЕ), заставляя читателей или разработчиков каждый раз рассматривать контекст, окружающий термин. В .NET Microsoft разрешила эту путаницу, введя новую концепцию сборки (asseтbly), являющейся компонентом программного обеспечения, поддерживающим plug-and-play, во многом аналогично аппаратному компоненту. Теоретически .NЕТ-сборка примерно эквивалентна СОМ-модулю. Практически сборка может содержать несколько типов и физических файлов (в том числе файлов растровой графики, РЕ-файлов .NET и т. д.), необходимых для ее успешного запуска, или ссылаться на них. Кроме хранения IL-кода сборка является базовой единицей контроля версии, развертывания, управления безопасностью, одновременного исполнения версий, совместного и повторного использования кода, что мы обсудим далее.

(Для справки: сборка - это логический DLL- или ЕХЕ-файл, а манифест - описание (метаданные) сборки (в том числе ее версия, используемые в ней другие сборки и т. д.).)

Уникальная идентификация

Все сборки, совместно используемые многими приложениями (и называемые совместно используемыми или разделяемыми сборками (shared asseblies)), должны содержать пару из открытого и закрытого ключа. Создатель сборки может подписать сборку своим закрытым ключом, а любой другой - проверить эту цифровую подпись с помощью открытого ключа создателя сборки.

Для цифровой подписи сборки необходимо при создании последней использовать открытый и закрытый ключи. На этапе компиляции компилятор сгенерирует хеш-код файлов сборки, подпишет его с помощью закрытого ключа и сохранит получившуюся цифровую подпись в зарезервированном разделе РЕ-файла. Открытый ключ будет также сохранен в сборке.

Чтобы проверить цифровую подпись сборки, CLR нужен открытый ключ сборки для расшифровки ее цифровой подписи и получения исходного, рассчитанного ранее хеш-кода. Кроме того, CLR опирается на информацию из манифеста сборки для динамической генерации хеш-кода. Сгенерированное значение сравнивается с исходным значением хеш-кода. Эти значения должны совпадать, в противном случае предполагается, что сборка поддельная.

Теперь, когда мы знаем как подписать и проверить подлинность сборки в среде .NET, поговорим о том, как CLR гарантирует, что данное приложение загружает именно ту сборку, с которой оно компилировалось. Когда вы или кто-то еще компилирует приложение, для работы которого требуется совместно используемая сборка, в манифест сборки приложения будет включен 8-байтовый хеш-код, полученный с помощью открытого ключа совместно используемой сборки. При запуске приложение динамически вычисляет 8-байтовый хеш-код по открытому ключу сборки и сравнивает его со значением, хранящимся в манифесте сборки приложения. Если эти значения совпадают, CLR предполагает, что была загружена корректная версия сборки.

Контроль версий

В .NET существует четыре типа сборок:

Статические сборки

В .NET существуют РЕ-файлы, создаваемые при компиляции. Статические сборки могут быть созданы с помощью вашего любимого компилятора: свс, cl или иЬс.

Динамические сборки

Это сборки в памяти, имеющие формат РЕ, динамически создаваемые во время выполнения с помощью классов пространства имен System.Reflection.Emit.

Закрытые сборки

Это статические сборки, используемые конкретным приложением.

Совместно используемые или разделяемые сборки

Это статические сборки, которые должны иметь уникальное разделяемое имя и доступны любому приложению

В .NET сборка является минимальной единицей, которой может быть присвоен номер версии, имеющий следующий формат:

<основная-версия>. <подверсия>. <номер-построения>. <ревизия>

Развертывание

Поскольку манифест сборки клиентского приложения (что мы кратко обсудим далее) содержит информацию о внешних ссылках - в том числе местоположение внешней сборки и версию сборки, используемую приложением, - больше не требуется хранить в реестре указания по активизации компонентов и маршалингу, как в СОМ. Основываясь на информации о версии и безопасности, записанной в манифесте приложения, CLR загрузит корректную совместно используемую сборку. I CLR выполняет «ленивую» (отложенную) загрузку внешних сборок и извлечет их код по запросу, когда приложению потребуются типы из них. По этой причине можно уменьшать размер загружаемых приложений с помощью многочисленных небольших внешних сборок. Когда потребуется определенная внешняя сборка, среда времени выполнения загрузит ее автоматически, не требуя регистрации или перезагрузки компьютера.

Безопасность

Концепция идентификации пользователя является общей для всех платформ разработки и операционных систем, однако концепция идентичности кода (code identity), в соответствии с которой может идентифицироваться даже фрагмент кода, нова для индустрии коммерческого программного обеспечения. В среде .NET сама сборка представляет собой единицу идентификации кода, включающую такую информацию, как имя совместно используемой сборки, номер версии, информацию о культуре (для локализации приложений) и открытый ключ. Опираясь на эту концепцию, CLR может проверить, имеет ли сборка полномочия на доступ к системным ресурсам и на вызовы других сборок.

Для того чтобы соответствовать концепции идентичности кода, CLR поддерживает концепцию управления доступом к коду (code access). Другими словами, среда времени выполнения определяет возможность доступа к определенной сборке на основе набора разрешений. CLR проверяет эти разрешения и определяет, удовлетворять ли запросы на исполнение на уровне сборки. При создании сборки можно указать набор разрешений, которые клиентское приложение должно иметь для доступа к вашей сборке. Во время выполнения, если клиентское приложение имеет права на доступ к коду вашей сборки, оно может выполнять вызовы ее объектов, а в противном случае оно не сможет их использовать.

Одновременное исполнение версий

Мы говорили, что сборка - это единица контроля версий и развертывания, и кратко обсудили DLL Hell - проблему, сведение которой к минимуму является задачей среды .NET. CLR позволяет любым версиям одной и той же разделяемой DLL (совместно используемой сборки) работать одновременно, на одной системе и даже в одном процессе. Эта концепция известна как одновременное исполнение версий (side-by-side execution). Microsoft .NET осуществляет одновременное исполнение версий, применяя средства контроля версий и развертывания, присущие всем совместно используемым сборкам. Эта концепция позволяет устанавливать различные версии одной совместно используемой сборки на одной машине без DLL Hell или конфликтов версий. Следует только учесть, что сборки должны быть совместно используемыми или разделяемыми, т.е. что необходимо зарегистрировать их в GAC при помощи утилиты глобального кэша .NЕТ-сборок (gacutil.exe). После регистрации различных версий одной совместно используемой сборки в GAC пользовательское имя сборки уже не имеет значения - имеет значение только информация, предоставляемая средствами контроля версий и развертывания .NET.

Вспомним, что при компиляции приложения, использующего определенную разделяемую сборку, информация о версии сборки присоединяется к манифесту приложения. Кроме того, 8-байтовый хеш-код открытого ключа совместно используемой сборки также присоединяется к манифесту приложения. Основываясь на этой информации, CLR может найти в точности ту совместно используемую сборку, которая необходима данному приложению, и даже убедиться, что указанный 8-байтовый хеш-код действительно совпадает с хеш-кодом совместно используемой сборки. Если CLR сможет идентифицировать и загружать в точности ту сборку, которая требуется, внедрение среды .NET будет означать, что конец «ада DLL» близок.

Совместное и повторное использование кода

Для того чтобы использовать свою сборку совместно с остальным миром, необходимо дать ей разделяемое (или строгое) имя и зарегистрировать ее в GAC. Точно так же, если требуется использовать или расширить определенный класс, находящийся в определенной сборке, необходимо не просто импортировать этот класс, а импортировать в приложение всю сборку. Следовательно, единицей совместного использования кода является сборка в целом.

Манифесты: метаданные сборки

Манuфест сборки - это метаданные, целиком описывающие сборку, включая ее идентификатор, список файлов, относящихся к сборке, ссылки на внешние сборки, экспортируемые типы и ресурсы, а также требуемые разрешения. Короче говоря, в нем описаны все детали, необходимые для осуществления компонентного plug-and-play. Поскольку сборка содержит все эти детали, не требуется хранить данную информацию в реестре, как принято в мире СОМ.

В .NET вместо поиска в реестре CLR обращается непосредственно к манифесту сборки, определяет, какие требуются внешние сборки, загружает именно ту сборку, которая необходима вашему приложению, и создает экземпляр класса.

Создание сборок

Сборка может быть либо одномодулъной (single-тodиle) либо многомодульной (тиlti-тodиle). В одномодульной сборке все, что требуется собрать, размещено в одном ЕХЕ- или DLL-файле; примером может быть приложение hello.exe, разработанное нами раньше. Такую сборку легко создать, потому что компилятор сам заботится о ее создании.

Для создания многомодульной сборки, содержащей много модулей и файлов с ресурсами, есть несколько вариантов. Первый - применение утилиты связывания сборок Assembly Linker (al.exe), предоставляемой .NET SDK. Эта утилита формирует файл с манифестом сборки из одного или более IL-файлов или файлов ресурсов.

Промежуточный язык IL

В проектировании программного обеспечения концепция абстракции (abstractioп) исключительно важна. Мы часто применяем абстракциюдля скрытия сложности системы или прикладных сервисов, предоставляя потребителю простой интерфейс к ним. Сохраняя интерфейс неизменным, мы можем изменять скрытые внутренние детали, а различные клиенты смогут по-прежнему использовать тот же интерфейс.

Мiсrоsоft называет свой уровень языковой абстракции Common Intermediate Language (CIL, общий промежуточный язык). IL поддерживает все средства объектно-ориентированного программирования, включая абстракцию данных, наследование, полиморфизм и такие полезные концепции, как исключения и события. Кроме этих возможностей IL поддерживает и другие концепции, например свойства, поля и перечисления. Любой язык среды .NET может быть преобразован в IL, поэтому .NET поддерживает несколько языков и, возможно, в дальнейшем будет поддерживать несколько платформ (при условии, что на целевых платформах будет CLR).

IL-инструкции являются стековыми, что позволяет компиляторам легче генерировать IL-код. Директива .maxstack определяет максимальное количества слотов стека, необходимых для этого метода. Информация о стеке необходима для любого метода в IL.