Разделяемые сборки

69

Подобно приватной сборке, любая разделяемая сборка представляет собой коллекцию типов и (необязательно) ресурсов. Самое очевидное отличие между разделяемой и приватной сборкой состоит в том, что одна копия разделяемой сборки может использоваться сразу в нескольких приложениях на одной и той же машине.

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

Как не трудно догадаться, разделяемые сборки не развертываются внутри того же самого каталога, что и приложения, в которых они должны использоваться. Вместо этого они устанавливаются в так называемом глобальном кэше сборок (Global Assembly Cache — GAC). Этот кэш размещен в каталоге Windows внутри подкаталога под названием Assembly (например, путем к нему может быть С:\Windows\Assembly), как показано на рисунке:

Глобальный кэш сборок

Строгие имена

Прежде чем развертывать сборку в GAC, ей обязательно необходимо назначить строгое имя (strong name), которое позволяет уникальным образом идентифицировать издателя данного двоичного файла .NET. Следует иметь в виду, что в роли "издателя" может выступать как отдельный программист, так и подразделение компании или вообще целиком вся компания.

В некотором отношении строгое имя является современным .NET-эквивалентом глобально уникальных идентификаторов (GUID), которые применялись в СОМ. Те, кому приходилось работать с СОМ, наверняка помнят, что глобально уникальными идентификаторами приложений называются идентификаторы, которые характеризуют конкретные СОМ-приложения. В отличие от GUID-значений в СОМ (которые представляют собой 128-битные числа), строгие имена в .NET основаны (отчасти) на двух взаимосвязанных криптографических ключах, называемых открытым (public) и секретным (private) ключом и являющихся гораздо более уникальными и устойчивыми к подделке по сравнению с простыми идентификаторами GUID.

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

Для создания строгого имени сборки сначала генерируются данные открытого и секретного ключей с помощью поставляемой в составе .NET Framework 4.0 SDK утилиты sn.exe. Эта утилита генерирует файл, который обычно оканчивается расширением *.snk (Strong Name Key — ключ строгого имени) и содержит данные для двух разных, но математически связанных ключей — "открытого" и "секретного". После указания местонахождения этого файла *.snk компилятору C# тот запишет полное значение открытого ключа в манифест сборки с использованием дескриптора .publickey.

Кроме того, компилятор C# генерирует на основе всего содержимого сборки (CIL-кода, метаданных и т.д.) соответствующий хеш-код. Хеш-кодом называется числовое значение, которое является статистически уникальным для фиксированных входных данных. Следовательно, в случае изменения какого-то аспекта сборки .NET (даже одного символа в строковом литерале), компилятор выдает другой хеш-код. Далее этот хеш-код объединяется с содержащимися внутри файла *.snk данными секретного ключа для получения цифровой подписи, вставляемой в сборку внутрь данных заголовка CLR. На рисунке схематично показано, как выглядит процесс создания строгого имени:

Генерирование цифровой подписи

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

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

Генерирование строгих имен в командной строке

Рассмотрим теперь процесс назначения строгого имени на примере сборки FontInfo, которая была создана в предыдущей статье. В нынешнее время предпочтение практически наверняка будет отдаваться генерированию необходимого файла *.snk в Visual Studio 2010. Однако в прежние времена (примерно до 2003 г.) назначать сборке строгое имя можно было только в командной строке. Давайте посмотрим, как это делается.

В первую очередь должны быть сгенерированы необходимые данные ключей с помощью утилиты sn.ехе. Хотя эта утилита обладает множеством опций командной строки, в настоящий момент основной интерес представляет только флаг -k, который заставляет эту утилиту генерировать новый файл с информацией об открытом и секретном ключах. Для примера введем следующую команду, чтобы сгенерировать файл KeyPair.snk:

sn -k KeyPair.snk

Теперь, имея данные ключей, необходимо проинформировать компилятор C# о том, где расположен файл KeyPair.snk. Как уже рассказывалось ранее, при создании любого нового проекта на C# в Visual Studio 2010 в числе первоначальных файлов (отображаемых в узле Properties окна Solution Explorer) всегда автоматически создается файл по имени AssemblyInfo.cs. В этом файле содержится набор атрибутов, описывающих саму сборку. С помощью атрибута AssemblyKeyFile можно сообщить компилятору о местонахождении действительного файла *.snk. Путь должен быть указан в виде строкового параметра, например:

[assembly: AssemblyKeyFile(@"С:\KeyPair\KeyPair.snk")]

Когда значение атрибута [AssemblyKeyFile] задается вручную, Visual Studio 2010 будет генерировать предупреждение, которое информирует о том, что необходимо использовать для csc.exe опцию /keyfile или создать файл ключей в окне Properties. Об этом речь пойдет позже, а пока генерируемое предупреждение можно проигнорировать.

Из-за того, что в состав строгого имени должен обязательно входить номер версии разделяемой сборки, указание версии для сборки fontinfo.dll является важной деталью. В файле AssemblyInfo.cs доступен еще один атрибут под названием AssemblyVersion. Первоначально в нем в качестве номера версии для сборки устанавливается значение 1.0.0.0:

[assembly: AssemblyVersion ("1.0.0.0")]

Вспомните, что в .NET номер версии состоит из четырех частей (старшего номера, младшего номера, номера сборки и номера редакции). Хотя указывать номер версии нужно самостоятельно, за счет использования группового символа (*) вместо конкретных значений можно позволить Visual Studio 2010 автоматически увеличивать номер сборки и редакции во время каждой компиляции.

// Файл AssemblyInfo.cs
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// Управление общими сведениями о сборке осуществляется с помощью 
// набора атрибутов.
[assembly: AssemblyTitle("FontInfo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ProfessorWeb")]
[assembly: AssemblyProduct("FontInfo")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyKeyFile(@"C:\myProject\FontInfo\KeyPair.snk")]

// Параметр ComVisible со значением FALSE делает типы в сборке невидимыми 
// для COM-компонентов.  Если требуется обратиться к типу в этой сборке через 
// COM, задайте атрибуту ComVisible значение TRUE для этого типа.
[assembly: ComVisible(false)]

// Следующий GUID служит для идентификации библиотеки типов, если этот проект будет видимым для COM
[assembly: Guid("bd34bf1f-fe72-41a4-984c-c6158eac4707")]

// Сведения о версии сборки состоят из следующих четырех значений:
//
//      Основной номер версии
//      Дополнительный номер версии 
//      Номер построения
//      Редакция
//
// Можно задать все значения или принять номер построения и номер редакции по умолчанию, 
// используя "*", как показано ниже:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.0.5.9")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Теперь у компилятора C# есть вся необходимая информация для генерации строгого имени (поскольку конкретная культура в атрибуте [AssemblyCulture] не указывалась, компилятор будет использовать культуру, которая установлена на текущей машине).

Генерирование строгих имен с помощью Visual Studio 2010

В Visual Studio 2010 можно как указывать путь к какому-нибудь уже существующему файлу *.snk на странице свойств проекта, так и генерировать новый файл *.snk. Чтобы создать новый файл *.snk для проекта, дважды щелкните на значке Properties (Свойства) в окне Solution Explorer, перейдите на вкладку Signing (Подписывание), отметьте флажок Sign the assembly (Подписать сборку) и выберите в раскрывающемся списке вариант New (Создать), как показано на рисунке:

Создание нового файла .snk в Visual Studio 2010

После этого откроется окно с приглашением указать имя для нового файла *.snk (например, myKeyFile.snk) и флажком Protect my key file with a password (Защитить файл ключей с помощью пароля), отмечать который в рассматриваемом примере необязательно.

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

Пройди тесты
Лучший чат для C# программистов