C#: сборки (Assemblies)

Сборка представляет собой одиночный файл Portable Executable (PE) с расширением .exe в случае приложения или .dll в случае многократно используемой библиотеки.

Содержимое сборки

Сборка содержит:

  • Манифест сборки — содержит сведения для исполняющей среды .NET: имя сборки, версия, необходимые права доступа, ссылки на другие сборки.
  • Манифест приложения — содержит сведения для операционной системы: способ развертывания сборки, необходимость администраторских полномочий.
  • Скомпилированный типы — скомпилированный код IL и метаданные типов, включенных в сборку.
  • Ресурсы — другие данные, включенные в сборку: изображения, локализуемые тексты.

Обязательным является только манифест сборки (хотя как правило содержатся скомпилированные типы. Исполняемые файлы и библиотеки имеют одинаковую структуру, основное отличие в том, что исполняемые файлы содержат точку входа.

Манифест сборки

Манифест сборки обязательно должен содержать:

  • простое имя сборки
  • номер версии (AssemblyVersion)
  • открытый ключ и подписанный хэш сборки, если она имеет строгое имя
  • список ссылаемых сборок (включая их версии и открытые ключи)
  • список модулей сборки
  • список типов, определенных в сборке, и модулей, содержащих каждый тип
  • необязательный набор прав доступа, требуемых или отклоняемых сборкой (SecurityPermission)
  • целевая культура если это подчиненная сборка (AssemblyCulture)

Также манифест сборки может содержать:

  • полный заголовок и описание (AssemblyTitle и AssemblyDescription)
  • копирайт и информацию о компании (AssemblyCompany и AssemblyCopyright)
  • дополнительные сведения о версии (AssemblyInformationalVersion)
  • дополнительные атрибуты для специальных данных

Некоторые из этих данных выводятся из параметров, переданных компиляторы. Остальные берутся из атрибутов сборки (указаны в скобках):

Атрибуты сборки обычно определяются в одном файле в проекте. Visual Studio, например, автоматически создает для этих целей файл AssemblyInfo.cs в папке Properties в каждом проекте и заполняет его начальной информацией.

Манифест приложения

Манифест приложения — это XML-файл, который сообщает информацию о сборке операционной системе. Если он определен, он обрабатывается перед загрузкой сборки и может повлиять на запуск процесса приложения со стороны ОС.

Корневой элемент манифеста — assembly в пространстве имен urn:schemas-microsoft-com:asm.v1:

С помощью следующего примера можно затребовать прав администратора для приложения:

Манифест приложения может быть как отдельным файлом, так и встроенным в саму сборку. Как отдельный файл он должен находиться в одной папке с самой сборкой и иметь расширение .manifest (например, для сборки MyApp.exe — MyApp.exe.manifest).

Встроить манифест в саму сборку (для этого ее надо сначала скомпилировать) можно с помощью утилиты mt такой командой:

Модули

Содержимое сборки на самом деле упаковано в один или несколько контейнеров, называемых модулями. Модуль соответствует одному файлу сборки и дает возможность создавать сборки, состоящие из нескольких файлов. В многофайловой сборке главный модуль всегда содержит манифест, а дополнительные модули могут содержать код и/или ресурсы. Манифест описывает относительное местоположение всех модулей сборки.

Класс Assembly

Класс System.Reflection.Assembly позволяет получить доступ к метаданным сборки во время выполнения. Получить объект сборки можно несколькими способами:

  • с помощью свойства Assembly класса Type:
  • с помощью статических методов класса Assembly:
    • GetExecutingAssembly — возвращает сборку, содержащую выполняемую функцию (метод)
    • GetCallingAssembly — возвращает сборку, содержащую функцию, которая вызвала текущую выполняемую функцию
    • GetEntryAssembly — возвращает сборку, содержащую точку входа в приложение

Получив объект Assembly с помощью его методов и свойств можно получить метаданные сборки и выполнять рефлексию ее типов.

Члены класса Assembly:

  • FullName, GetName — возвращает полностью заданное имя или объект AssemblyName
  • CodeBase, Location — местоположение файла сборки
  • Load, LoadFrom, LoadFile — вручную загружает сборку в текущий домен приложения
  • GlobalAssemblyCache — указывает, находится ли сборка в GAC
  • GetSatelliteAssembly — находит подчиненную сборку с заданной культурой
  • GetType, GetTypes — возвращает тип или все типы, определенные в сборке
  • EntryPoint — возвращает метод точки входа в приложение как объект MethodInfo
  • GetModules, ManifestModule — возвращает все модули или главный модуль сборки
  • GetCustomAttributes — возвращает атрибуты сборки

Имена сборок

Идентификатор сборки состоит из четырех частей:

  • простое имя
  • версия («0.0.0.0» если не задана)
  • культура («neutral» для не подчиненных сборок)
  • маркер открытого ключа («null» если сборка не имеет строгого имени)

Простое имя берется из имени файла, в который сборка была первоначально скомпилирована. Расширение отбрасывается, например, простое имя для сборки System.Xml.dll выглядит как System.Xml. Переименование файла не изменяет простое имя сборки.

Номер версии берется из атрибута AssemblyVersion и представляет собой строку, разделенную на 4 части:

Задать номер версии можно так:

Культура берется из атрибута AssemblyCulture и применяется к подчиненным сборкам.

Маркер открытого ключа берется из пары ключей, передаваемых компилятору с помощью параметра /keyfile.

Полностью заданные имена (Fully Qualified Names)

Полностью заданное имя сборки — это строка, включающая все 4 идентифицирующих компонента в следующем формате:

Например, для сборки System.Xml.dll:

Класс AssemblyName

Класс AssemblyName содержит свойства для каждого из 4 компонентов полностью заданного имени. Получить объект AssemblyName можно несколькими способами:

  • создать экземпляр типа AssemblyName, передав ему полностью заданное имя; можно также создать экземпляр не передавая конструктору аргументов, а затем установить все свойства вручную (в этом случае объект AssemblyName будет изменяемым)
  • вызвать метод GetName объекта Assembly
  • вызвать метод AssemblyName.GetAssemblyName, передав ему путь к файлу сборки

Основные свойства и методы объекта AssemblyName:

Свойство Version возвращает объект типа Version, который сам обладает свойствами Major, MinorBuild и Revision. Поскольку версия сборки, задаваемая атрибутом AssemblyVersion, является частью имени сборки, ее изменение в некоторых случаях не желательно. Помимо атрибута AssemblyVersion существуют еще два атрибута задающие версию,но  носящие чисто информативный характер и никак не учитываемые средой CLR:

  • AssemblyInformationalVersion — информационная версия — задает версию, отображаемую конечному пользователю. Она видна в диалоговом окне свойств файла в поле Product Version (Версия продукта). Здесь может находиться любая строка, например, 5.1 Beta 2. Обычно все сборки в приложении имеют одинаковую информационную версию.
  • AssemblyFileVersion — файловая версия — ссылается на номер компоновки (build) сборки. Отображается в диалоговом окне свойств файла в поле File Version (Версия файла). Как и AssemblyVersion должна содержать строку разделенную на 4 части.

Строгие имена (Strong Names)

Строгие имена помогают защитить сборку от подделки. Строго именованная сборка имеет уникальный и не подделываемый идентификатор, что достигается путем добавления в манифест сборки следующих метаданных:

  • уникальный номер, принадлежащий автору сборки
  • подписанный хэш сборки, доказывающий, что сборка создана владельцем уникального номера

Достигается это с помощью пары открытого и секретного ключей. Открытый ключ как раз и представляет собой уникальный номер автора; он указывается в полностью заданном имени сборки. Секретный ключ используется для создания подписанного хэша.

Чтобы назначить сборке строгое имя необходимо сначала сгенерировать файл с парой ключей, например, с помощью утилиты sn.exe. Утилита создаст файл с расширением .snk, содержащий пару ключей. Затем сборку нужно скомпилировать с параметром /keyfile:

В Visual Studio эти два шага можно проделать проще с помощью окна Свойства проекта.

Подпись Authenticode

Строгие имена сборок позволяют установить, что они принадлежат одному автору, но не дают возможности узнать кому именно. Система подписания кода Authenticode как раз позволяет установить, кто именно является автором сборки.

Чтобы добавить к сборке такую подпись необходимо, во-первых, получить сертификат для подписания кода в центре сертификации (услуга платная) и установить его в системе, а затем подписать сборку с помощью утилиты signtool.exe.

Windows автоматически при первом запуске проверяет подпись Authenticode, однако это можно сделать и программно:

Класс Publisher в пространстве имен System.Security.Policy содержит свойство Certificate. Если это свойство возвращает значение отличное от null, значит сборка подписана системой Authenticode.

Подпись Authenticode не является частью имени сборки.

GAC — глобальный кэш сборок (Global Assembly Cache)

При установке .NET Framework создает на компьютере централизованный репозиторий сборок — глобальный кэш сборок (Global Assembly Cache — GAC). GAC содержит сборки самой платформы .NET Framework, и в него также можно добавлять собственные сборки. Основной особенностью GAC является централизованная поддержка версий (управляется администратором на уровне машины). Если такая поддержка действительно нужна, можно добавить сборки в GAC, во всех остальных случаях это только усложнит разработку. развертывание и поддержку приложения.

Чтобы установить сборку в GAC ей прежде всего нужно задать строгое имя. После этого ее можно добавить в GAC с помощью утилиты gacutil:

Если в GAC уже существует сборка с тем же самым открытым ключом и версией — она обновиться. Если версия сборки отличается от установленной в GAC, то новая сборка будет установлена параллельно, при этом старые приложения будут ссылаться на старую сборку, а в новых приложения можно будет выбирать с какой версией сборки им работать.

Удалить сборку можно следующей командой:

Установку сборок в GAC можно задать как часть проекта установки в Visual Studio.

После загрузки сборки в GAC приложения могут ссылаться на нее, не нуждаясь в локальной копии. При этом если локальная копия присутствует, она будет проигнорирована в пользу сборки из GAC.

Ресурсы

Сборка помимо исходного кода может содержать ресурсы: текст, изображения и XML-файлы. Обычно в качестве ресурсов в сборку добавляются изображения или локализуемые данные. Ресурс сборки в конечном итоге представляет собой байтовый поток с именем. Сборка хранит их в словарях со строковыми ключами.

Локализуемое содержимое сначала добавляется в файлы .resources. При компиляции они упаковываются как индивидуальные подчиненные сборки, которые автоматически выбираются во время выполнения на основе языка ОС.

Непосредственное встраивание ресурсов

Чтобы встроить ресурс напрямую в сборку нужно скомпилировать ее с одним или несколькими параметрами /resource:

При этом можно задать другое имя для ресурса:

В Visual Studio для добавления ресурса в сборку нужно добавить файл к проекту и установить для него в качестве действия сборки Embedded Resource (Встроенный ресурс). Visual Studio всегда предваряет имя ресурса стандартным пространством имен проекта и именами всех подпапок, в которых находится файл (через точку). Имена ресурсов чувствительны к регистру.

Для извлечения ресурса вызывается метод GetManifestResourceStream объекта сборки, содержащей ресурс. Метод возвращает поток:

Метод GetManifestResourceNames возвращает имена всех ресурсов сборки.

Локализуемые данные

Локализуемые данные добавляются в сборку с помощью файлов .resources, которые в конечном итоге добавляются в сборку как встроенные ресурсы точно также как и остальные файлы.

Эти файлы являются двоичными и редактировать их непосредственно нельзя. Поэтому обычно на этапе разработки создаются файлы .resx, которые затем с помощью Visual Studio или утилиты resgen преобразуются в файлы .resources (которые в свою очередь уже встраиваются в сборку).

Файл .resx использует XML и структурирован в виде пар имя-значение:

Файл .resx можно создать с помощью Visual Studio, добавив в проект элемент типа Resources File (Файл ресурсов). Visual Studio сама создаст корректную структуру файла, а в дальнейшем автоматически преобразует его в файл .resources и встроит в сборку при компиляции. 

Также при создании файла .resx Visual Studio автоматически создает класс с тем же именем; этот класс имеет свойства для извлечения каждого элемента. Однако можно работать с данными из файла .resources и напрямую, с помощью класса ResourceManager. Класс содержит два метода GetString и GetObject, возвращающие конкретный элемент (GetObject следует использовать с приведением):

Подчиненные сборки

Локализация данных осуществляется с помощью подчиненных сборок. Подчиненные сборки содержат локализованные файл .resources, переведенные на различные языки. При этом основная сборка должна содержать файл .resources для языка по умолчанию. Когда приложение запускается, платформа .NET выясняет язык текущей ОС, и каждый раз при обращении к классу ResourceManager платформа ищет соответствующую локализованную сборку. Если она найдена и содержит соответствующий ключ ресурса, она будет использована вместо главной сборки, в противном случае будет использована версия из главной сборки.

Подчиненная сборка не может содержать исполняемого кода, а только локализованные ресурсы. Подчиненные сборки развертываются в подкаталогах папки сборки:

Здесь XX — двухбуквенный код языка (например, de) либо код языка и региона (например, en-GB).

Чтобы добавить подчиненную сборку нужно добавить в проект еще один файл .resx, название которого должно содержать код локализации (отделенный точкой), например, welcome.de.resx. При компиляции в Visual Studio будет автоматически создан подкаталог (например, de), а в нем подчиненная сборка (например, MyApp.resources.dll).

Культуры

Культуры разделяются на собственно культуры и подкультуры. культура представляет конкретный язык, а подкультура — региональный вариант этого языка. Культура обозначается с помощью двухбуквенного кода (например, en, de), а подкультуры — с помощью четырехбуквенного(en-AU, de-AT).

В .NET культура представлена классом System.Globalization.CultureInfo. Текущую культуру можно получить следующим способом:

Свойство CurrentCulture отражает региональные настройки Windows, а свойство CurrentUICulture — язык пользовательского интерфейса.

Разрешение сборок

Обычно приложение состоит из основной исполняемой сборки (.exe) и нескольких связанных библиотечных сборок (.dll). Разрешение сборок — это процесс нахождения сборок, на которые производится ссылка. Оно происходит как на этапе компиляции, так и во время выполнения. На этапе компиляции компилятору известно, где находится ссылаемая сборка, поскольку разработчик (или Visual Studio) предоставляет полный путь к сборкам, находящимся вне текущего каталога.

Во время выполнения схема несколько сложнее. При первом обращении к какому либо типу CLR сначала определяет, находится ли он в текущей сборке или во внешней. Если тип находится во внешней сборке, CLR проверяет загружена ли уже эта сборка в память. Если сборка не загружена, CLR пытается ее найти. Первым делом проверяется GAC, затем базовый каталог приложения и другие пути поиска (если заданы). Если сборку найти не удалось, генерируется событие AppDomain.AssemblyResolve, которое можно перехватить и загрузить сборку вручную. Если после этого сборка все равно не найдена CLR генерирует исключение.

Событие AssemblyResolve

Событие AssemblyResolve позволяет вмешаться в процесс и вручную загрузить сборку. Внутри обработчика события AssemblyResolve производится поиск сборки и ее загрузка с помощью одного из статических методов класса AssemblyLoad, LoadFrom или LoadFile. Эти методы возвращают ссылку на загруженную сборку, и эта ссылка, в свою очередь, возвращается вызывающему коду:

Событие AssemblyResolve имеет возвращаемый тип, и при наличии множества обработчиков преимущество получит первый из них, который вернет отличный от null объект Assembly.

Загрузка сборок

Методы LoadLoadFrom или LoadFile класса Assembly можно использовать не только в обработчике события AssemblyResolve, но и за его пределами.

Загрузить сборку с полностью заданным именем (без указания местоположения) можно с помощью метода Assembly.Load. В этом случае CLR будет пытаться найти сборку по обычной автоматической схеме (среда CLR сама использует метод Load для нахождения ссылаемых сборок).

Загрузить сборку из файла можно с помощью методов LoadFrom и LoadFile, из URI — с помощью метода LoadFrom, а из байтового массива (если сборка добавлена в другую сборку в качестве ресурса) — с помощью метода Load.

Для выяснения какие сборки загружены в память в текущий момент нужно вызвать метод GetAssemblies класса AppDomain:

Методы LoadFrom и LoadFile позволяют загрузить сборку из файла. При повторной загрузке сборки с тем же самым идентификатором, но из другого местоположения, метод LoadFrom вернет предыдущую копию сборки, а метод LoadFile вернет новую копию:

При двукратной загрузке сборки из одно и того же местоположения оба метода вернут ранее загруженную сборку. А двукратная загрузка сборки из одного и того же байтового массива вернет два разных объекта Assembly.

Типы из двух идентичных сборок в памяти являются несовместимыми. По этой причине стоит избегать дублирования сборок и соответственно пользоваться методом LoadFrom вместо LoadFile.

Метод LoadFrom (в отличие от метода LoadFile) запоминает местоположение загружаемых сборок и сообщает его CLR, чтобы среда могла в дальнейшем автоматически загружать сборки из этого местоположения.

Метод ExecuteAssembly

Класс AppDomain содержит метод ExecuteAssembly, который позволяет загрузить исполняемую сборку (так как это делает метод LoadFrom) и после этого вызвать ее метод точки входа с передачей дополнительных аргументов командной строки:

Метод ExecuteAssembly работает синхронно, т.е. вызывающий метод блокируется до тех пор, пока не завершиться выполнение вызываемой сборки.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *