Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции OOP c#.doc
Скачиваний:
46
Добавлен:
22.09.2019
Размер:
3.38 Mб
Скачать

2.13. Состав и взаимодействие сборок

Сборка (assembly) – это единица развертывания и контроля версий в .NET Framework. Заметим, что сборка задает границы видимости для типов (модификатор internal в C#). Сборка состоит из одного или нескольких программных модулей и файлов ресурсов. Эти компоненты могут размещаться в отдельных файлах, либо содержаться в одном файле. В любом случае, сборка содержит в некотором из своих файлов манифест, описывающий состав сборки. Будем называть сборку однофайловой, если она состоит из одного файла. В противном случае сборку будем называть многофайловой. Тот файл, который содержит манифест сборки, будем называть главным файлом сборки.

Рис. 5. Однофайловая и многофайловая сборки

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

  1. Ресурсы приложения (текстовые строки, изображения и т.д.) можно хранить вне кода приложения, что позволяет при необходимости изменять их без перекомпиляции приложения.

  2. Если исполняемый код приложения разделен на несколько модулей, то модули загружаются в память только по мере необходимости. Кроме этого, скомпилированный модуль может использоваться в нескольких сборках.

Рассмотрим пример создания и использования многофайловых сборок. К сожалению, IDE Visual Studio не позволяет работать с многофайловыми сборками, поэтому все файлы примера придется компилировать, используя компилятор командной строки csc.exe.

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

Ниже представлен класс, выполняющий чтение строки из подключенного к сборке ресурса. Код, выполняющий требуемые операции, стандартен и взят из примеров MSDN:

using System;

using System.Reflection;

using System.IO;

public class TextClass {

public static string GetText() {

// получаем ссылку на выполняемую сборку

Assembly a = Assembly.GetExecutingAssembly();

// получаем доступ к связанному ресурсу как к потоку

Stream s = a.GetManifestResourceStream("message.txt");

// "оборачиваем" в текстовый поток (специфика ресурса)

StreamReader sr = new StreamReader(s);

return sr.ReadToEnd();

}

}

Файл TextClass.cs с исходным текстом класса TextClass скомпилируем в виде модуля (обратите внимание на ключ компилятора):

C:\Temp\Test>csc /t:module TextClass.cs

После компиляции получим файл-модуль TextClass.netmodule. Далее, создадим главный файл нашего приложения (main.cs):

using System;

class MainClass {

static void Main() {

Console.WriteLine("Text from resource");

Console.WriteLine(TextClass.GetText());

Console.ReadLine();

}

}

Также создадим собственно текстовый файл-ресурс с именем message.txt (внимание: при работе с текстовыми ресурсами предпочтительнее использовать кодировку Unicode).

Теперь соберем нашу многофайловую сборку:

c:\TEMP>csc /linkresource:message.txt

/addmodule:textclass.netmodule main.cs

Обратите внимание на ключи компилятора. Ключ /addmodule позволяет добавить к сборке ссылку на внешний файл-модуль, ключ /linkresource позволяет связать со сборкой внешний файл-ресурс. Ключи могут использоваться произвольное количество раз.

В итоге, мы получили многофайловую сборку main.exe, состоящую из трех файлов: главного файла main.exe, дополнительного файла-модуля textclass.netmodule и файла-ресурса message.txt. Еще раз подчеркнем возможные преимущества многофайловых сборок. Во-первых, мы можем менять содержимое файла message.txt, не перекомпилируя сборку. Во-вторых, мы можем создать новую сборку, в которой используется код из модуля textclass.netmodule, то есть сделать этот модуль разделяемым между несколькими сборками. Важное замечание: предполагается, что все три файла, составляющие нашу многофайловую сборку, размещены в одном каталоге.

Следующий вопрос, рассматриваемый в данном параграфе, это взаимодействие сборок. Как правило, большие проекты состоят из нескольких сборок, ссылающихся друг на друга. Среди этих сборок имеется некая основная (обычно оформленная как исполняемый файл *.exe), а другие сборки играют роль подключаемых библиотек с кодом необходимых классов (обычно такие сборки – это файлы с расширением *.dll). Платформа .NET разделяет сборки на локальные (или сборки со слабыми именами) и глобальные (или сборки с сильными именами).

Представим пример, который будем использовать в дальнейшем. Пусть имеется следующий класс (в файле UL.cs), содержащий «полезную» функцию:

using System;

namespace UsefulLibrary {

public class UsefulClass {

public void Print() {

Console.WriteLine("Useful function");

}

}

}

Скомпилируем данный класс как DLL:

c:\TEMP\Test>csc /t:library UL.cs

Пусть основное приложение (файл main.cs) собирается использовать код из сборки UL.dll:

using System;

// подключаем требуемое пространство имен

using UsefulLibrary;

class MainClass {

static void Main() {

// используем класс

UsefulClass a = new UsefulClass();

a.Print();

Console.ReadLine();

}

}

Если UL.dll рассматривается как локальная сборка, то она должна находиться в том же каталоге1, что и основное приложение main.exe как при компиляции, так и при выполнении приложения. Скомпилируем основное приложение main.cs:

c:\TEMP\Test>csc /r:UL.dll main.cs

Ключ компилятора /r (или /reference) позволяет установить ссылку на требуемую сборку.

Применением локальных сборок достигается простота развертывания приложения (все его компоненты сосредоточены в одном месте) и изолированность компонентов приложения. Имя локальной сборки – слабое имя – это имя файла сборки без расширения.

Хотя использование локальных сборок имеет свои преимущества, иногда необходимо сделать сборку общедоступной. До появления .NET Framework основным был подход, при котором код общих библиотек помещался в системный каталог простым копированием фалов при установке программ. Такой подход привел к проблеме, известной как «ад DLL» (DLL Hell). Новое установленное приложение могло заменить требуемую библиотеку новой версией, причем сделать это так, что приложения, ориентированные на старую версию библиотеки, переставали работать. Для решения проблемы DLL Hell в .NET Framework используется специальное защищенное хранилище сборок (Global Assembly Cache, GAC).

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

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

Name, Version=1.2.0.0, Culture=neutral, PublicKeyToken=1234567812345678

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

Рассмотрим процесс создания строго именованной сборки на примере сборки UL.dll. Первое: необходимо создать пару криптографических ключей для цифровой подписи сборки. Для этих целей служит утилита sn.exe, входящая в состав Microsoft .NET Framework SDK.

sn -k keys.snk

Параметр -k указывает на создание ключей, keys.snk – это файл с ключами. Просмотреть полученные ключи можно, используя команду sn -tp.

Далее необходимо подписать сборку полученными ключами. Для этого используется специальный атрибут уровня сборки AssemblyKeyFile:

using System;

using System.Reflection;

[assembly: AssemblyKeyFile("keys.snk")]

namespace UsefulLibrary { . . . }

Обратите внимание: для использования атрибута необходимо подключить пространство имен System.Reflection; в качестве параметра атрибута указывается полное имя файла с ключами; атрибут должен располагаться вне любого пространства имен.

После подписывания сборку можно поместить в GAC. Простейший вариант сделать это – использовать утилиту gacutil.exe, входящую в состав .NET Framework SDK. При использовании ключа /i сборка помещается в GAC, а ключ /u удаляет сборку из GAC:

gacutil /i ul.dll

Теперь сборка UL.dll помещена в GAC. Ее сильное имя (для ссылки в программах) имеет вид:

UL, Version=0.0.0.0, Culture=neutral, PublicKeyToken= ff824814c57facfe

Компонентом сильного имени является версия сборки. Если программист желает указать версию, то для этого используется атрибут AssemblyVersion. Номер версии имеет формат Major.Minor.Build.Revision. Если номер версии задается, то часть Major является обязательной. Любая другая часть может быть опущена (в этом случае она полагается равной нулю). Часть Revision можно задать как *, тогда компилятор генерирует ее как количество секунд, прошедших с полуночи, деленное на два. Часть Build также можно задать как *. Для нее будет подставлено количество дней, прошедших с 1 февраля 2000 года. Пример использования атрибута AssemblyVersion:

using System;

using System.Reflection;

[assembly: AssemblyVersion("1.2.3.*")]

. . .