Компоновка, упаковка, развёртывание и администрирование приложений и типов

Задачи развёртывания в .NET Framework

.NET Framework в значительной степени решает "кошмар DLL" (ситуация, когда установка новой программы может повлиять на работоспособность уже установленных) и делает шаг к решению проблемы с распределением данных по всей системе. Совершенствование системы защиты связано с новой моделью безопасности - безопасностью доступа на уровне кода (основана на правах, которые контролируются хостом приложения).

Компоновка типов в модуль

Есть простая программа:

public sealed class Program
{
  public static void Main()
  {
    System.Console.WriteLine("Hi");
  }
}

Её можно скомпилировать командой csc.exe /out:Program.exe /t:exe /r:MSCorLib.dll Program.cs. Здесь:

  • csc.exe - C Sharp Compiler
  • /out:Program.exe - имя создаваемого файла
  • /t[arget].exe - тип создаваемого файла (в данном случае консольное приложение Win32, также можно использовать /t:winexe для GUI или /t:appcontainerexe для Windows Store)
  • /r[eference]:MSCorLib.dll - сборка для поиска внешних типов

По факту имена исполняемого файла и кода совпадают, можно опустить параметр /t. Как и параметр /r, так как библиотека MSCorLib.dll подгружается автоматически. В итоге получается стандартный PE32.

Файл параметров

Файл параметров (CSC.rsp по умолчанию или другое имя файла, определённого рядом с проектом) используется для вынесения общих параметров компилятора. Таких, например, как ссылки на все стандартные библиотеки. При компиляции конфиги в командной строке переопределяют локальные конфиги (в текущем каталоге), а локальные - глобальные (рядом с CSC.exe).

Несколько слов о метаданных

Управляемый PE-файл состоит из 4 блоков:

  • Заголовок PE32(+) - стандартная информация, ожидаемая Windows.
  • Заголовок CLR - небольшой блок информации, специфичной для модулей, требующих CLR (старший и младший номера версии CLR, ряд флагов, маркер MethodDef, указывающий на точку входа, размер и смещение некоторых таблиц метаданных).
  • Метаданные - блок двоичных данных из нескольких таблиц (определений, ссылок и манифестов).
  • IL-код

Основные таблицы определений:

  • ModuleDef - Одна запись, идентифицирующая модуль (имя файла модуля с расширением и идентификатор версии в виде GUID).
  • TypeDef - По одной записи для каждого типа в модуле (имя типа, базовый тип, флаги сборки и указатели на другие таблицы, описывающие члены типа).
  • MethodDef - По одной записи для каждого метода в модуле (имя метода, флаги, сигнатуру и смещение в модуле, по которому находится IL-код, а также указатели на ParamDef, где хранятся данные о параметрах метода).
  • FieldDef - По одной записи для каждого поля в модуле (тип поля и флаги).
  • ParamDef - По одной записи для каждого параметра в модуле (тип, имя и флаги).
  • PropertyDef - По одной записи для каждого свойства (имя, флаги, тип и вспомогательное поле, которое может быть пустым).
  • EventDef - По одной записи для каждого события (имя и флаги).

Основные таблицы ссылок:

  • AssemblyRef - По одной записи для каждой сборки, на которую ссылается модуль (имя, номер версии, региональные стандарты и маркер открытого ключа).
  • ModuleRef - По одной записи для каждого PE-модуля, реализующего типы, на которые он ссылается (имя файла сборки и расширение).
  • TypeRef - По одной записи для каждого типа, на который ссылается модуль (имя типы и ссылку: TypeRef, ModuleDef, ModuleRef или AssemblyRef).
  • MemberRef - По одной записи для каждого члена типа, на который ссылается модуль (имя и сигнатуру члена, указатель на TypeRef).

Объединение модулей для создания сборки

Получившийся Program.exe - это не просто PE-файл с метаданными, но ещё и сборка, то есть совокупность нескольких (в данном случае одного) файлов с определениями типов и файлов ресурсов. Один из файлов выбирается для хранения манифеста - набора таблиц метаданных, которые содержат имена файлов, составляющих сборку, а также версию, региональные стандарты сборки, её издателя и так далее.

CLR работает со сборками, соответственно, сначала загружается манифест, а затем остальные файлы. Некоторые характеристики сборки:

  • В сборке определены многократно используемые типы.
  • Сборке назначается номер версии.
  • Со сборкой может быть связана информация безопасности.

Чтобы упаковать типы, а также обеспечить их безопасность и управление версиями, нужно поместить типы в модули, объединённые в сборку.

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

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

Сборка - единица многократного использования, управления версиями и безопасности типов. Загрузив файл с манифестом, CLR может определить, какие файлы сборки содержат типы и ресурсы, на которые ссылается приложение. Таким образом можно абстрагироваться от особенностей распределения содержимого сборки по файлам. При работе с многими типами, совместно использующими одну версию и набор параметров безопасности, по соображениям производительности рекомендуется размещать все типы в одном файле.

Для компоновки сборки нужно выбрать один PE-файл, который будет хранителем манифеста. Или это может быть отдельный PE-файл, который не содержит ничего, кроме манифеста. Таблицы метаданных манифеста, которые превращают управляемый модуль в сборку:

  • AssemblyDef - Единственная запись, если модуль идентифицирует сборку (имя сборки без расширения и пути, сведения о версии, региональные стандарты, флаги, алгоритм хеширования и открытый ключ издателя).
  • FileDef - По одной записи для каждого PE-файла и файла ресурсов, кроме файла манифеста (имя и расширение без пути, хеш-код и флаги).
  • ManifestResourceDef - по одной записи для каждого ресурса (имя ресурса, флаги, индекс для FileDef).
  • ExportedTypesDef - Записи для всех открытых типов, экспортируемых всеми PE-модулями (имя типа, индекс для FileDef, индекс для TypeDef).

Файл сборки, содержащий манифест, содержит таблицу AssemblyRef, где хранятся записи с описанием всех сборок, на которые ссылаются файлы. Это делает сборку самоописываемой.

Если компилятор генерирует исполняемый файл, то это создаёт PE-файл с таблицами метаданных манифеста. Можно также создать PE-файл без манифеста - получится DLL в формате PE. В Visual Studio отсутствует поддержка создания многофайловых сборок.

Добавление сборок в проект в среде Visual Studio

image

Использование утилиты Assembly Linker

Вместо компилятора можно использовать компоновщик сборок (assembly linker, AL.exe). Компоновщик генерирует сборку только из манифеста, без IL-кода.

Включение в сборку файлов ресурсов

...

Ресурсы со сведениями о версии сборки

При компоновке через компилятор сведения о сборке задаются через атрибуты в коде, а при assembly linker'ом - через параметры командной строки.

Номера версии

...

Региональные стандарты

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

Развёртывание простых приложений (закрытое развёртывание сборок)

Сборки с закрытым развёртыванием - сборки, которые разворачиваются в том же каталоге, что и приложение.

Простое средство администрирования (конфигурационный файл)

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

Алгоритм поиска файлов сборки

...