Пятница, 28 сентября 2012 16:38

Сборки

Оцените материал
(21 голосов)

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

Файл Program.exe - это не просто РЕ-файл с метаданными, а еще и сборка (assembly), то есть коллекция из одного или нескольких файлов с определения­ ми типов и файлов ресурсов.

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

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

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

У отдельных файлов сборки, кроме файла с таблицами метаданных манифеста, таких атрибутов нет.

Чтобы упаковать типы, сделать их доступными, а также обеспечить безопасность типов и управление их версиями, нужно поместить типы в модули, объединенные в сборку. Чаще всего сборка состоит из одного файла, как приложение Program.exe в рассмотренном примере, но могут быть и сборки из нескольких файлов: РЕ-файлов с метаданными и файлов ресурсов, например GIF- или JРG-файлов. Наверное, проще представлять себе сборку как «логический» ЕХЕ- или DLL-файл.

Уверен, многим читателям интересно, зачем компании Microsoft понадобилось вводить новое понятие - «сборка». Дело в том, что сборка позволяет разграничить логическое и физическое понятия многократно используемых типов. Допустим, сборка состоит из нескольких типов. При этом типы, применяемые чаще всех, можно поместить в один файл, а применяемые реже -в другой. Если сборка развертывается путем загрузки через Интернет, клиент может вовсе не загружать файл с редко используемыми типами, если он никогда их не задействует. Например, независимый поставщик ПО (independent software vendor, ISV), специализирующийся на разработке элементов управления пользовательского интерфейса, может реализовать в отдельном модуле активно используемые типы Active Accessibllity (необходимые для соответствия требованиям логотипа Microsoft). Загружать этот модуль достаточно лишь тем, кому нужны специальные возможности.

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

У меня есть три аргумента в пользу применения многофайловых сборок.

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

·         Можно добавлять к сборке файлы с ресурсами и данными. Допустим, имеется тип для расчета некоторой страховой суммы. Ему может потребоваться доступ к актуарным таблицам. Вместо встраивания актуарных таблиц в исходный текст можно включить соответствующий файл с данными в состав сборки (например, с помощью компоновщика сборок AL.exe, который рас­ смотрен далее). Можно включать в сборки данные в любом формате: в текстовом, в виде таблиц Microsoft Excel или Microsoft Word, а также в любом другом при условии, что приложение способно анализировать содержимое этого файла.

·         Можно создавать сборки, состоящие из типов, написанных на разных языках программирования. При компиляции исходного текста на языке С# компилятор создает один модуль, а при компиляции исходного текста на Visual Basic - другой. Одна часть типов может быть написана на С#, другая - на Visual Basic, остальные - на других языках программирования. Затем при помощи соответствующего инструмента все эти моду ли можно объединить в одну сборку. Использующие такую сборку разработчики увидят в ней лишь набор типов. Разработчики даже не заметят, что применялись разные языки программирования. Кстати, при желании с помощью ILDasm.exe можно получить файлы с исходным текстом всех модулей на языке IL. После этого можно запустить утилиту ILAsm.exe и передать ей полученные файлы, и утилита выдаст файл, содержащий все типы. Для этого компилятор исходного текста должен генерировать только IL-код, поэтому эту методику нельзя использовать с Visual С++.

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

При работе со многими типами, совместно использующими одну версию и набор параметров безопасности, по соображениям производительности рекомендуется размещать все типы в одном файле, не распределяя их по не­ скольким файлам, не говоря уже о разных сборках. На загрузку каждого файла или сборки CLR и Windows тратят значительное время: на поиск сборки, ее загрузку и инициализацию. Чем меньше файлов и сборок, тем быстрее загрузка, потому уменьшение числа сборок способствует сокращению рабочего пространства и степени фрагментации адресного пространства процесса. Ну, и наконец, nGen.exe лучше оптимизирует код, если обрабатываемые файлы больше по размеру.

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

Таблица 2.3. Таблица метаданных манифеста

Имя таблицы мета-данных манифеста

Описание

AssemblyDef

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

FileDef

Содержит по одной записи для каждого РЕ-файла и файла ре- сурсов, входящих в состав сборки. В каждой записи содержит- ся имя и расширение файла (без указания пути), хэш и флаги. Если сборка состоит из одного файла, таблица FileDef пуста

ManifestResourceDef

Содержит по одной записи для каждого ресурса, включенного в сборку. Каждая запись включает имя ресурса, флаги (public или private), а также индекс для таблицы FileDef, указываю- щий файл или поток с ресурсом. Если ресурс не является отдельным файлом (например, JPEG- или GIF-файлом), он хранится в виде потока в составе РЕ-файла. В случае встроен- наго ресурса запись также содержит смещение, указывающее начало потока ресурса в РЕ-файле

ExportedTypesDef

Содержит записи для всех открытых типов, экспортируемых всеми РЕ-модулями сборки. В каждой записи указано имя типа, индекс для таблицы FileDef (указывающий файл сборки, в кота- ром реализован этот тип), а также индекс для таблицы TypeDef

 

 

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

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

Компилятор С# создает сборку, если указан любой из параметров командной строки - /t[arget]:exe, /t[arget]:winexe или /t[arget]:library. Каждый из этих параметров заставляет компилятор генерировать единый РЕ-файл с таблицами метаданных манифеста. В итоге генерируется соответственно консольное приложение, приложение с графическим интерфейсом или DLL-файл.

Кроме этих параметров компилятор С# поддерживает параметр /t[arget]: modu1e, который заставляет компилятор создать РЕ-файл без таблиц мета­ данных. При использовании этого параметра всегда получается DLL-файл в формате РЕ. Для того чтобы получить доступ к типам такого файла, его не­ обходимо поместить в сборку. При указании параметра /t:modu1e компилятор С# по умолчанию присваивает выходному файлу расширение .netmodule.

К сожалению, в интегрированной среде разработки (lntegrated Development Environment, IDE) Microsoft Visual Studio нет встроенной поддержки создания многофайловых сборок- для этого приходится использовать инструменты командной строки.

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

  • файл RUT.cs содержит редко используемые типы;
  • файл FUT.cs содержит часто используемые типы.

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

csc /t:modu1e RUT.cs

Команда заставляет компилятор С# создать файл RUТ.netmodule, который представляет собой стандартную РЕ-библиотеку DLL, но среда CLR не сможет просто загрузить ее.

Теперь скомпилируем в отдельном модуле часто используемые типы и сделаем его хранителем манифеста сборки, так как к расположенным в нем типам обращаются довольно часто. Фактически теперь этот моду ль представляет собой целую сборку, поэтому я изменил имя выходного файла с FUT.dll на Jeftтypes.dll:

csc /out:JeffTypes.d11 /t:library /addmodule:RUT. netmodule FUT.cs

Эта команда приказывает компилятору С# при компиляции файла FUT.cs создать файл Jeftтypes.dll. Поскольку указан параметр /t:library, результирующий РЕ-файл DLL с таблицами метаданных манифеста называется Jeftтypes.dll. Параметр /addmodulе: RUT.netmodulе указывает компилятору, что файл RUT.netmodule должен быть частьюсборки. В частности, параметр /addmodule заставляет компилятор добавить к таблице FileDef в метаданных манифеста сведения об этом файле, а также занести в таблицу ExportedTypesDef сведения об открытых экспортируемых типах этого файла.

Завершив работу, компилятор создаст несколько файлов (рис. 2.1). Модуль справа содержит манифест.

 

 

Рис. 2.1. Многофайловая сборка из двух управляемых модулей и манифеста

Файл RUT.пetmodule содержит IL-код, сгенерированный при компиляции RUT.cs. Кроме того, этот файл содержит таблицы метаданных, описывающие типы, методы, поля, свойства, события и т. п., определенные в RUТ.cs, а также типы, методы и др., на которые ссылается RUT.cs. Jeftтypes.dll - это отдельный файл. Подобно RUT.netmodule, он включает IL-код, сгенерированный при компиляции FUT.cs, а также аналогичные метаданные в виде таблиц определений и ссылок. Однако Jeftтypes.dll содержит дополнительные таблицы метаданных, которые и делают его сборкой. Эти дополнительные таблицы описывают все файлы, составляющие сборку (сам файл Jeftтypes.dll и RUT.netmodule). Таблицы метаданных манифеста также включают описание всех открытых типов, экспортируемых файлами Jeftтypes.dll и RUT.пetmodule.

Примечание. На самом деле в таблицах метаданных манифеста не описаны типы, экспортируемые РЕ-файлом, в котором находится манифест. Цель этой оптимизации - уменьшить число байт, необходимое для хранения данных манифеста в РЕ­ файле. Таким образом, утверждения вроде «таблицы метаданных манифеста включают все открытые типы, экспортируемые JeffТypes.dll и RUT.netmodule» верны лишь отчасти. Однако это утверждение абсолютно точно отражает логический набор экспортируемых типов.

Скомпоновав сборку JeffТypes.dll, можно изучить ее таблицы метаданных манифеста при помощи ILDasm.exe, чтобы убедиться, что файл сборки дей ствительно содержит ссылки на типы из файла RUT.netmodule. Если ском поновать этот проект и затем проанализировать его метаданные при помощи утилиты ILDasm.exe, в выводимой информации вы увидите таблицы FileDef и ExportedTypesDef.

Из этих сведений видно, что RUT.netmodule - это файл, который считается частью сборки с маркером Ох26000001. Таблица ExportedType показывает наличие открытого экспортируемого типа ARare1yUsedType. Этот тип помечен маркером реализации (implementation token) Ох26000001, означающим, что IL-код этого типа находится в файле RUT.netmodule.

Примечание. Для любопытных: размер маркеров метаданных - 4 байта. Старший байт указывает тип маркера (Ox01=TypeRef, Ox02=TypeDef, Ox26=FileRef, Ox27=ExportedType). Полный список типов маркеров см. в перечислимом типе CorTokenType в заголовочном файле CorHdr.h из .NET Framework SDK. Три младших байта маркера просто идентифицируют запись в соответствующей таблице метаданных. Например, маркер реализации Ох26000001 ссылается на первую строку таблицы FileRef (нумерация строк начинается с 1, а не с 0). Кстати, в TypeDef нумерация строк начинается с 2.

Любой клиентский код, использующий типы сборки JeffТypes.dll, должен компоноваться с указанием параметра компилятора /r[eference]: JeffТypes.dll, который заставляет компилятор загрузить сборку JeffТypes.dll и все файлы, перечисленные в ее таблице FileDef. Компилятору необходимо, чтобы все файлы сборки были установлены и доступны. Если бы мы удалили файл RUТ.пetmodule, компилятор С# сгенерировал бы следующее сообщение об ошибке:

fatal error CS0009: Metadata file 'C:\JeffTypes.dll' could not bе<span style="mso-ansi-language: EN-US;" lang="EN-US"> opened- 
'Error importing module 'rut. netmodule' of assembly 'C:\JeffTypes.dll '
- The system cannot find the file specified</span>

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

Во время исполнения клиентский код вызывает разные методы. При первом вызове некоторого метода среда CLR определяет, на какие типы он ссылается как на параметр, возвращаемое значение или локальную переменную. Далее CLR пытается загрузить из сборки, на которую ссылается код, файл с манифестом. Если этот файл описывает типы, к которым обращается вызванный метод, срабатывают внутренние механизмы CLR, и нужные типы становятся доступными. Если в манифесте указано, что нужный тип находится в другом файле, CLR загружает этот файл, и внутренние механизмы CLR обеспечивают доступ к данному типу. CLR загружает файл сборки только при вызове метода, ссылающегося на расположенный в этом файле тип. Это значит, что наличие всех файлов сборки, на которую ссылается приложение, для его работы не обязательно.

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

Если проект создается в среде Visual Studio, необходимо добавить в проект все сборки, на которые он ссылается. Для этого откройте окно Solution Explorer, щелкните правой кнопкой мыши на проекте, на который нужно добавить ссылку, и выберите команду Add Reference. Откроется диалоговое окно Add Reference (рис. 2.2). Для того чтобы добавить в проект ссылки на сборку, выберите ее в списке. Если в списке нет нужной сборки, то для того чтобы ее найти (файл с мани­ фестом), щелкните на кнопке Browse. Вкладка СОМ в диалоговом окне Add Reference позволяет получить доступ к неуправляемому СОМ-серверу из управляемого кода через класс-представитель, автоматически генерируемый Visual Studio. Вкладка Projects служит для добавления в текущий проект ссылки на сборки, созданные в другом проекте этого же решения. Чтобы сборки отображались в списке на вкладке .NET, добавьте в реестр подраздел:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders\MyLibName

MyLibName - это созданное разработчиком уникальное имя, Visual Studio его не отображает. Создав такой подраздел, измените его строковое значение по умолчанию, чтобы оно указывало на каталог, в котором хранятся файлы сборок (например, C:\Program Files\MyLibPath).


Рис. 2.2. Диалоговое окно Add Reference в Visual Studio

Вместо компилятора С# для создания сборки можно задействовать компоновщик сборок (assembly linker) AL.exe. Эта утилита оказывается кстати, если нужно создавать сборки из модулей, скомпонованных разными компиляторами (если компилятор языка не поддерживает параметр, эквивалентный параметру /addmodule из С#), а также в случае, когда требования к упаковке сборки на момент компоновки просто не известны. Утилита AL.exe пригодна и для компоновки сборок, состоящих исключительно из ресурсов (или сопутствующих сборок - к ним мы еще вернемся), которые обычно используются для локализации ПО. Утилита AL.exe может генерировать файлы формата ЕХЕ или DLL РЕ, которые не содержат ничего, кроме манифеста, описывающего типы из других модулей. Чтобы понять, как работает AL.exe, скомпонуем сборку JeffТypes.dll по-другому:

csc /t:module RUT.cs 
csc /t:module FUT.cs 
al /out:JeffТypes.dll /t:library FUT.netmodule RUT.netmodule

Файлы, генерируемые в результате исполнения этих команд, показаны на рис. 2.3. В этом примере два из трех отдельных модулей, RUT.netmodule и FUT. netmodule, сборками не являются (так как не содержат таблиц метаданных манифеста). Третий же - Jeftтypes.dll- это небольшая библиотека РЕ DLL (поскольку она скомпонована с параметром /t[arget]:library), в которой нет IL-кода, а только таблицы метаданных манифеста, указывающие, что файлы Jeftтypes.dll, RUT.netmodule и FUT.netmodule входят в состав сборки. Результирующая сборка состоит из трех файлов: Jeftтypes.dll, RUT.netmodule и FUT.netmodule, так как компоновщик сборок не «умеет» объединять несколько файлов в один.

 


Рис. 2.3. Многофайловая сборка из трех управляемых модулей и манифеста

Утилита AL.exe может генерировать консольные РЕ-файлы и РЕ-файлы с графическим интерфейсом (с помощью параметра /t[аrget]:ехе или /t[arget]:winexe). Однако это довольно необычно, поскольку означает, что будет сгенерирован исполняемый РЕ-файл, содержащий не больше IL-кода, чем нужно для вызова метода из другого моду ля. Можно указать, какой метод должен использоваться в качестве входной точки, задав при вызове компоновщика сборок параметр командной строки /main. Приведем пример вызова AL.exe с этим параметром:

csc /t:module App.cs al /out:App.exe /t:exe /main:Program.Main app.netmodule

Первая строка компонует App.cs в модуль, а вторая генерирует небольшой РЕ-файл Арр.ехе с таблицами метаданных манифеста. В нем также находится небольшая глобальная функция, сгенерированная AL.exe благодаря параметру /main: App.Main. Эта функция, _EntryPoint, содержит следующий IL-код:

{codecitation class="brush: csharp; gutter: true;" width="100%" } .method privatescope static void EntryPoint$PST06000001() cil managed
{
   .entrypoint
   // Code size 8 (0
х8)
   .maxstack 8
   IL_0000: tail
   IL_0002: call void [.module 'Program.netmodule']Program::Main()
   IL_0007: ret
   // end of method 'Global Functions':: EntryPoint

}{/codecitation}

Как видите, этот код просто вызывает метод Main, содержащийся в типе Program, который определен в файле App.netmodule. Параметр /main, указанный при вызове AL.exe, здесь не слишком полезен, так как вряд ли вы когда-либо будете создавать приложение, у которого точка входа расположена не в РЕ­ файле с таблицами метаданных манифеста. Здесь этот параметр упомянут лишь для того, чтобы вы знали о его существовании.

В программном коде для данной книги имеется файл Ch02-3-BuildMultiFile­ Library.bat, в котором инкапсулированы последовательно все шаги, показывающие, как создать много файловую сборку. Ch02-4-AppUsingMultiFilelibrary project в Visual Studio выполняет данный файл на этапе предварительной сборки. Вы можете изучить этот пример, чтобы понять, как интегрировать многофайловую сборку из Visual Studio.

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

Если сборка создается при помощи AL.exe, параметр /embed[resource] позволяет добавить в сборку файлы ресурсов (файлы в формате, отличном от РЕ). Параметр принимает любой файл и включает его содержимое в результирующий РЕ-файл. Таблица ManifestResourceDef в манифесте обновляется, отражая наличие нового ресурса.

Утилита AL.exe поддерживает также параметр /link[resource], который принимает файл с ресурсами. Однако параметр только обновляет таблицы манифеста Mani festResourceDef и FileDef сведениями о ресурсе и о том, в каком файле сборки он находится. Сам файл с ресурсами не внедряется в РЕ-файл сборки, а хранится отдельно и подлежит упаковке и развертыванию вместе с остальными файлами сборки.

Подобно AL.exe, CSC.exe позволяет объединять ресурсы со сборкой, гене­ рируемой компилятором С#. Параметр /resource компилятора С# включает указанный файл с ресурсами в результирующий РЕ-файл сборки и обновляет таблицу ManifestResourceDef. Параметр компилятора /linkresource добавляет в таблицы ManifestResourceDef и FileDef записи со ссылкой на отдельный файл с ресурсами.

И последнее: в сборку можно включить стандартные ресурсы Win32. Это легко сделать, указав при вызове AL.exe или CSC.exe путь к RЕS-файлу и параметр /win32res. Кроме того, можно легко включить стандартный ресурс значка Win32 в файл сборки, указав при вызове AL.exe или CSC.exe путь к IСО-файлу и параметр /win32icon. В Visual Studio файл ресурсов добавляют в сборку на вкладке Applicatioп в диалоговом окне свойств проекта. Обычно значки включают, чтобы Проводник Windows (Windows Explorer) мог отображать значок для управляемого исполняемого файла.

Примечание. В файлах управляемой сборки содержится также файл манифеста Wiп32. По умолчанию компилятор С# автоматически создает файл манифеста, однако ему можно запретить это делать при помощи параметра /nowin32manifest. Программный код манифеста, генерируемого компилятором С# по умолчанию, выглядит следующим образом:

{codecitation class="brush: xml; gutter: true;" width="100%" }// Создание и инициализация массива String
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion=”1.0”>
<assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
<trustinfo xmlns="urn: schemas-microsoft-com:asm.v2">
    <security>
        <requestedPrivileges xmlns="urn: schemas-microsoft-com:asm.v
З">
            <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
        </requestedPrivileges>
    </security>
</trustinfo>
</assembl
у>

{/codecitation}

Прочитано 174030 раз
   

Поиск  

   

Материалы