В завершение
мы решили остановиться на проблемах установки и размещения программ, а также
на использовании готовых программ, использующих модель COM (Component Object
Model). Вообще-то на эту тему можно написать целую книгу, но мы надеемся, что
это короткое вступление поможет вам перейти к самостоятельному изучению этой
темы.
Установка
большинства приложений .NET сводится к простому копированию каталога, содержащего
необходимые файлы, на любой компьютер с установленной исполнительной средой
.NET. Программа запускается двойным щелчком на имени ЕХЕ-файла в окне Проводника
(Windows Explorer).
Выбирая
значок Setup and Deployment Project в диалоговом окне New Project, вы получите
доступ к весьма нетривиальным возможностям установки. Мастер Setup Wizard чрезвычайно
прост в использовании, но для большинства стандартных ситуаций его возможностей
оказывается вполне достаточно.
Тем не менее
даже в .NET иногда встречаются ситуации, когда простое копирование не подходит,
а программа-мастер слишком ограничивает вашу свободу действий. Чтобы разобраться
в принципах установки приложений .NET, необходимо знать, как работают сборки
(assemblies), поскольку приложения .NET распространяются в виде сборок.
Во многих
устанавливаемых приложениях хотя бы часть работы выполняется традиционными объектами
СОМ, поэтому в этой главе будут кратко затронуты вопросы использования объектов
СОМ в .NET [И наоборот — объекты .NET могут использоваться в СОМ, однако
эта возможность выглядит несколько экзотически.]. А поскольку одной из целей
разработки .NET было исправление недостатков СОМ, мы начнем с краткого обзора
СОМ и основных проблем, связанных с этой технологией.
Технология
СОМ упрощает создание программ, сохраняющих совместимость в разных версиях платформы
Windows и более или менее независимых от языка программирования. Компоненты
СОМ могут создаваться на разных языках, включая классический С (вариант для
мазохистов), C++, Delphi, VB5 и 6. Технология СОМ с большим успехом применялась
для создания объектов, предназначенных для решения специализированных задач,
таких как элементы VB OCX.
Технология
СОМ была задумана как механизм, при помощи которого программные компоненты получают
информацию о возможностях других компонентов и обращаются к ним с запросами,
не беспокоясь о подробностях внутренней реализации [Существуют и другие технологии,
ориентированные на повторное использование программного кода (например, CORBA),
но пока наибольшего успеха добилась именно модель СОМ.]. Для этого был выработан
стандартный протокол получения информации об интерфейсах, поддерживаемых другими
компонентами, наряду со стандартизацией средств для обращения к конкретной реализации
интерфейса в экземплярах.
Тем не менее
у СОМ были свои недостатки. Во-первых, реализация СОМ для Windows требовала,
чтобы в системном реестре хранилась вся информация обо всех компонентах
в системе. Пользователю приходилось регистрировать компоненты при установке
программ и стирать соответствующую информацию при удалении программ. При попытке
удаления программ возникала опасность того, что изменения, внесенные в реестр,
повлияют на работу других программ. Стоило серьезно повредить реестр, и система
вообще переставала работать. Более того, установка новой версии компонента нередко
нарушала работу программ, рассчитанных на более раннюю версию компонента.
В
Windows 98 была впервые представлена концепция параллельного выполнения (side-by-side
execution); это означало, что приложение могло использовать локальный экземпляр
компонента СОМ, находящийся в каталоге приложения, вместо экземпляра, зарегистрированного
в системе. Справедливости ради следует сказать, что параллельное выполнение
так и не решило проблемы с «кошмаром DLL», вдобавок оно работает
только в Windows 98, 2000 и ХР — и то если об этом специально позаботится разработчик
программы.
Давайте посмотрим,
что происходит на уровне реестра при регистрации компонентов СОМ.
При попытке
использования компонента происходит следующее:
Несмотря
на большое количество выполняемых операций, главные проблемы возникают при копировании
в систему нового файла с компонентом, не сопровождающимся обновлением реестра,
и при смене GUID. Приложение, которое раньше благополучно работало, работать
перестает. Это связано с тем, что в механизме установки СОМ не предусмотрено
нормальных средств для контроля версии компонентов.
Как было
сказано в начале этой главы, процесс установки в .NET часто сводится к простому
копированию файлов, после чего программа готова к немедленному запуску. Если
удалить скопированные файлы, то работать перестает только эта конкретная программа.
В этом процессе не используется реестр и не учитываются зависимости между компонентами.
Чтобы эта схема нормально работала, в .NET используется концепция сборки.
С технической
точки зрения сборка (assembly) в .NET представляет собой минимальную
устанавливаемую единицу программного кода. Сборка оформляется в виде автономного
ЕХЕ-файла или в виде библиотеки DLL, на которую можно ссылаться из других приложений.
Однако сборка содержит нечто большее, чем обычный IL-код, компилируемый и выполняемый
исполнительной средой .NET. Как минимум, сборка состоит из одного или нескольких
модулей и классов, откомпилированных в IL-код, и метаданных (данных, описывающих
данные [Префикс «мета» для подобных абстракций второго порядка
позаимствован из метаматематики — области математики, посвященной описанию самих
математических объектов.]), которые описывают сборку и функциональность
входящих в нее классов. Метаданные являются частью сборки, поэтому в документации
сборки названы самодокументируемыми. Во многих ситуациях сборка состоит из одного
файла, но встречаются и многофайловые сборки. Например, в сборку могут входить
ресурсные файлы, графические изображения и даже дополнительные EXE/DLL-файлы.
В любом случае сборка является минимальным объектом .NET, для которого производится
контроль версии или задаются привилегии.
В
большинстве случаев создаются однофайловые сборки, состоящие из одного ЕХЕ-или
DLL-файла.
Сборки бывают
закрытыми (private) и общими (shared). Закрытые сборки всегда находятся
в каталоге приложения или в одном из его подкаталогов. Общие сборки хранятся
в глобальном кэше сборок (GAC, global assembly cache). Начнем с закрытых
сборок, поскольку именно они используются по умолчанию для решений, построенных
в VS .NET IDE. С общими сборками дело обстоит сложнее, и мы займемся ими позже.
Обычно у
закрытых сборок не бывает проблем с несовместимостью версий, однако они требуют
дополнительных затрат дискового пространства, если в системе приходится хранить
несколько копий одного файла в разных каталогах [В наше время дисковое пространство
обходится так дешево, что эти затраты с избытком компенсируются удобствами,
связанными с использованием закрытых сборок.]. При создании ссылок на сборку
командой Project > Add Reference по умолчанию в каталоге приложения
создается новый экземпляр закрытой сборки. Мы рекомендуем по возможности ограничиваться
использованием закрытых сборок.
Для
управления сборками используются конфигурационные файлы в формате XML. Конфигурационный
файл должен находиться в одном каталоге с файлом, содержащим точку входа в сборку.
С его помощью можно управлять привилегиями, назначать каталоги для поиска зависимых
DLL, а также указывать другие сведения, необходимые для загрузки сборки.
Теоретически
сборка может быть устроена весьма сложно, поэтому в нее включается манифест
— совокупность всех сведений о сборке, необходимых исполнительной среде
(CLR) для загрузки, компиляции (при необходимости) и выполнения сборки. В манифест
входят следующие сведения:
Именно благодаря
наличию манифеста появляется возможность создания сборок, состоящих из нескольких
файлов. Кроме того, данные манифеста заменяют сложную систему регистрации компонентов
в реестре. Первое представление о сборке и ее манифесте дает файл Assemblylnfo.vb;
чтобы просмотреть содержимое этого файла, дважды щелкните на соответствующей
строке окна решения VS .NET. Как видно из приведенного ниже примера, этот текстовый
файл содержит многочисленные атрибуты сборки. Большинство атрибутов (например,
название организации) можно редактировать вручную, хотя чаще значения задаются
в IDE при помощи диалоговых окон свойств проекта.
Imports System.Reflection
Imports System.Runtime.InteropServices
'Следующая
группа атрибутов содержит общую информацию о сборке. ' Измените значения этих
атрибутов, чтобы изменить данные. ' связанные со сборкой.
'Review the values of the assembly attributes
<Assembly:AssemblyTitle("Sample")>
<Assembly:AssemblyDescription("")>
<Assembly:AssemblyCompany("Apress")>
<Assemblу:AssemblуProduct("")>
<Assembly:AssemblyCopyright("2001")>
<Assembly:AssemblyTrademark("")>
<Assembly:CLSCompliant(True)>
' Следующий QUID используется для идентификации библиотеки типов.
' если проект будет использоваться в СОМ
<Assembly:Guid("5D7BAFDE-EACA-4653-9C55-BA619E13D447")>
' Данные версии
для сборки состоят из следующих четырех величин:
' Основная версия
' Дополнительная
версия
'
Ревизия
' Номер построения
' Вы можете задать значения всех атрибутов или задать номера построения и ревизии по умолчанию.
' Для этого используется знак '*', как показано ниже.
<Assembly:AssemblyVersion("1.0.*")>
Если
задать эти атрибуты и построить сборку, такая информация становится доступ-ной
в Проводнике Windows. Щелкните правой кнопкой мыши на значке ЕХЕ-файла в окне
Проводника, выберите в контекстном меню команду Properties (Свойства) и перейдите
на вкладку Version (Версия).
В каталоге
\bin .NET SDK находится полезная программа ILDASM, которая может использоваться
для исследования сборок и их манифестов. На рис. 13.1 показано, какую информацию
выдает ILDASM для программы Employee из главы 4.
При двойном
щелчке на строке Manifest из рис. 13.1 открывается окно, показанное на рис.
13.2. Обратите внимание на перечисление всех сборок, от которых зависит данная
сборка, а также на описание класса Employee.
Манифест
сборки всегда содержит два обязательных атрибута, указанных в верхней и нижней
части рис. 13.2:
В качестве
имени сборки может использоваться любое допустимое имя файла. Обычно имя сборки
задается в диалоговом окне — выполните команду Project > Properties
и перейдите на страницу General в категории Common Properties.
Рис.
13.1. Программа ILDASM в действии
Номера версии
(основной, дополнительный, ревизия и построение) хранятся в следующем формате:
<0сн>.<дополн>.<ревизия>.<построение>
Эти значения
можно задать прямо в файле Assemblylnfo.vb. Чтобы включить режим автоматической
нумерации, введите версию в формате «х.у .*». Знак «*»
указывает VS на то, что номера ревизии и построения должны генерироваться автоматически.
Для
закрытых сборок версия не проверяется.
Во многих сборках также встречаются еще два атрибута:
Локальный
контекст (culture) содержит информацию о национальных стандартах, поддерживаемых
сборкой. Не путайте локальный контекст с языком. Например, и в Великобритании
и в США говорят на\нглийском языке, но локальные контексты в этих странах различаются
(так, в них используются разные форматы вывода дат и денежных сумм).
Рис.
13.2. Манифест класса Employee
Сильное
имя (strong name) можно считать аналогом GUID, хотя оно устроено несколько
сложнее. Сильные имена используются лишь для общих сборок. Дополнительная информация
приведена в следующем разделе.
Многофайловые
сборки
На момент
написания книги в среде программирования не поддерживалось создание сборок,
состоящих из нескольких файлов. Если такая необходимость возникала, программисту
приходилось обращаться к документации и использовать соответствующие утилиты
.NET SDK. Все компиляторы командной строки .NET позволяют создавать многофайловые
сборки. Хотя в книге данная тема не рассматривается, мы хотим обратить ваше
внимание на одну интересную особенность многофайловых сборок: они тоже могут
устанавливаться простым копированием, причем устанавливать сразу все файлы на
компьютер конечного пользователя не обязательно — компоненты могут копироваться
по мере надобности. Эта возможность очень удобна для установки по Интернету,
где приходится учитывать объем пересылаемой информации. Например, в многофайловой
сборке прием компонента справочной системы можно отложить до того момента, когда
пользователь захочет вызвать справку.
Общие сборки
.NET хранятся в глобальном кэше сборок (GAC). Наличие глобального кэша экономит
дисковое пространство и память, поскольку на стадии выполнения программы на
диске или в памяти достаточно хранить лишь один экземпляр сборки. Конечно, при
совместном использовании сборок возникают некоторые проблемы, присущие старому
механизму совместного использования DLL на базе реестра. К счастью, средства
контроля версии .NET позволяют хранить в GAC разные версии одной сборки, поэтому
каждое приложение может использовать нужную версию. Постарайтесь как можно реже
использовать глобальный кэш сборок и ограничиться следующими ситуациями:
Список сборок,
находящихся в GAC, выводится утилитой gacutil.exe из каталога \bin .NET SDK.
Команда имеет следующий синтаксис: gacutil.exe -1
Ниже приведено
начало списка для одного из наших компьютеров. Полный список имеет внушительные
размеры; даже на этой ранней стадии существования .NET он занимает около трех
страниц:
Microsoft (R).NET Global Assembly Cache Utility.Version 1.0.2914.16
Copyright (C)Microsoft
Corp,1998-2001,All rights reserved.
The Global Assembly Cache contains the following assemblies:
Accessibili ty.Version=1.0.2411.0,Culture=neutral,
PublicKeyToken-b03f5f7flld50a3a,Custom=null
ADODB,Version=2. 7.0.0.Culture-neutral,
PublicKeyToken-b03f5f7fIld50a3a,
Custom=null
CRVsPackageLib.Version=1.0.0.0.Culture-neutral,
PublicKeyToken=4f3430cff154c24c,Custom=nul1
Crystal Deci si ons.Crystal
Reports.Engine.Version=9.1.0.0.Culture-neutral,
PublicKeyToken=4f3430cff154c24c,Custom=nul
1
Для общих
сборок контроль версии играет гораздо более важную роль, чем для закрытых сборок,
поэтому в списке указаны номера версий каждой сборки. Последнее из Четырех чисел
в номере версии определяет номер ежедневного построения, изменение которого
считается непринципиальным. Далее следует номер ревизии, изменение которого
тоже считается непринципиальным. Изменения следующих двух чисел (дополнительного
и основного номеров) принципиальны. Это означает, что если программа запрашивает
сборку версии 2.0.0.0, а в GAC находится только версия 2.5.0.0, программа не
будет работать, если не внести специальные изменения в конфигурационный файл.
С другой стороны, версия 2.0.0.37 считается совместимой с версией 2.0.0.0 и
успешно загружается.
Все
сборки, включаемые в GAC, должны обладать сильными именами. Это связано с тем,
что в GAC могут храниться две разные версии одной сборки, которые различаются
по сильному имени.
Включение
и удаление сборок из GAC
Чтобы общая
сборка автоматически включалась в GAC в процессе установки, проще всего воспользоваться
программой установки с поддержкой GAC — например, последней версией пакета Microsoft
Installer (MSI). Описание этой программы выходит за рамки книги, но мы укажем,
что эту программу можно бесплатно загрузить с сайта MSDN (http://msdn.microsoft.com).
На стадии
разработки вместо программы установки обычно используется утилита
gacutil.exe.
Синтаксис командной строки:
gacutil -1 <имя_сборки>
Сборка с
заданным именем помещается в GAC.
Сильные
имена и совместное использование сборок
Сильные имена,
как и GUID, должны быть уникальными в пространстве и времени. В отличие от GUID,
которые теоретически могут быть похищены, сильные имена защищены от несанкционированного
использования при помощи механизма шифрования с открытым ключом [ При условии
надежного хранения закрытого ключа.]. Конкретные схемы шифрования бывают
очень сложными, однако в целом шифрование с открытым ключом построено на довольно
простой идее — в некоторых ситуациях вернуться от конечного результата к исходным
данным бывает очень, очень сложно. Например, вы можете легко перемножить два
целых числа и получить результат, но узнать исходные множители по произведению
невозможно — для этого нужно знать хотя бы один из них [Этот принцип заложен
в основу RSA, распространенного алгоритма шифрования с открытым ключом.].
Внутренняя
реализация выглядит следующим образом: на основании всей информа-ции, содержащейся
в сборке, .NET вычисляет хэш-код и применяет к нему закрытый ключ. Результатом
является зашифрованный хэш-код. Поскольку схема шифрования должна обеспечивать
возможность восстановления зашифрованных данных, зашифрованный хэш-код расшифровывается
при помощи открытого ключа, при этом получается исходный хэш-код. На последнем
шаге хэш-код заново вычисляется на основании данных, содержащихся в манифесте,
и два числа сравниваются. Если хэш-коды не совпадают, значит, сборка была модифицирована,
и .NET отказывается загружать ее.
Во всех схемах
с открытым ключом используется пара ключей: открытый и закрытый. Открытый
ключ (public key) может свободно распространяться, поскольку без знания
закрытого ключа зашифрованное сообщение невозможно восстановить за сколько-нибудь
приемлемый промежуток времени. Применение закрытого ключа к данным манифеста
позволяет сертифицировать их. Другие пользователи при помощи открытого
ключа убеждаются в том, что сборка поступила именно от вас, а не из постороннего
источника. А в некоторых случаях (например, при использовании Verisign) они
даже могут убедиться в том, что открытый ключ принадлежит именно вам, а не кому-то
другому (шифрование с открытым ключом защищает целостность данных, но для проверки
открытого ключа необходимы услуги третьей стороны).
При программировании
в .NET ключи (открытый и закрытый) обычно создаются утилитой sn.exe, входящей
в .NET SDK (сокращение «sn» означает «strong name»,
то есть «сильное имя»).
Ключи хранятся
в двоичных файлах. Команда построения ключей имеет следующий синтаксис:
sn -k <имя_файла>
Файлам должно
быть присвоено расширение .snk. В нашем примере пара ключей была создана командой
sn -k c:\keys\pair.snk
Файл .snk можно сгенерировать и в VS .NET IDE (команда Strong Name
в диалоговом окне Project Properties), но большинство программистов предпочитает
создавать ключи в отдельном процессе с максимальным уровнем защиты. Файл .snk
должен быть защищен от несанкционированного доступа; если закрытый ключ станет
известен посторонним, проку от него будет немного.
При наличии
пары ключей в виде файла .snk можно сертифицировать сборку закрытым ключом.
При этом .NET включает открытый ключ в манифест сборки [Точнее говоря, в
манифест включается хэшированная версия ключа, которая в .NET называется образцом
(token) открытого ключа. Вероятно, хэширование применяется для экономии места,
хотя нам кажется, что в манифесте следовало бы просто опубликовать открытый
ключ.] и вычисляет хэш-код всех данных сборки по закрытому ключу. Результат
тоже включается в манифест. По этим данным другие сборки, использующие ваш код,
смогут убедиться в том, что сборка не подверглась постороннему вмешательству.
Для этого открытый ключ применяется к зашифрованному хэш-коду, а результат сравнивается
с приведенным в манифесте.
Чтобы сертифицировать
сборку, включите в программу атрибут AssemblyKeyFile-Attribute с именем файла
.snk после всех команд импортирования или же воспользуйтесь вкладкой Sharing
диалогового окна Project Settings. Пример:
Imports System.Reflection
<Assembly:AssemblyKeyFi1eAttribute("c:\keys\pair.snk")>
\
Многие
компании не предоставляют своим работникам доступа к закрытому ключу, поэтому
в .NET была предусмотрена возможность отложить сертификацию сборки (за дополнительной
информацией обращайтесь к документации).
COM
Interop и вызовы функций DLL
Несмотря
на появление .NET, существующий код на базе СОМ еще не собирается умирать. К
счастью, уровень взаимодействия с СОМ в .NET работает очень хорошо. С другой
стороны, использование СОМ в проектах .NET снижает быстродействие и затрудняет
сопровождение программ, поэтому эти технологии объединяются лишь при крайней
необходимости.
В Visual
Studio .NET взаимодействие с СОМ почти не требует усилий со стороны программиста.
Просто выберите нужный объект СОМ на вкладке СОМ диалогового окна ссылок, и
с ним можно будет работать как с классом .NET. Для этого IDE читает библиотеку
типов СОМ и создает для объекта вспомогательный класс («обертку»)
.NET. Открытыми членами этого класса являются все открытые члены объекта СОМ.
Кстати, технология IntelliSense работает и для экземпляров этих классов.
Классы
объектов СОМ также создаются утилитой tlbimp.exe, входящей в. NET SDK. В частности,
эта утилита удобна при одновременном построении нескольких «оберток»
для использования в будущем.
Хотя при
вызове функций DLL можно использовать старый синтаксис Declare, в .NET рекомендуется
использовать другой способ — атрибут Oil Import, позволяющий создавать общие
точки входа. Для этого в программе определяется пустая функция, совпадающая
по сигнатуре с вызываемой функцией. Ниже приведен пример использования атрибута
Dll Import.
Imports System.Drawing
Imports System.Runtime.InteropServices
Module Modulel
' Импортировать функцию CreateDC из Win32 API
<Dll Import("gdi32.dll")>
Public Function CreateDC(ByVal strDhver _
As String. ByVal strDeviceName As String,_
ByVal strOutput As String. ByVal nullDEVICE As Integer _ )
As 'IntPtr
End Function
Sub Main()
' Создать прямоугольник
Dim rctWindow
As Rectangle = New Rectangle(100, 100, 200, 200)
Dim penBlack
As Pen = New PerUColor.Black)
penBlack.Brush
= Brushes.DarkKham
Dim grfx As
Graphics
Dim hDC As IntPtr
= CreateDC("DISPLAY". vbNullString,
vbNullString.
vbNullString)
grfx = Graphics.FromHdc(hDC)
Do While (True)
grfx.FillRectangle(penBlack.Brush, rctWindow)
System.Threading.Thread.Sleep(0)
Loop
End Sub
End Module
Другое принципиальное отличие атрибута Dll Import от ключевого слова Declare заключается в том, что Dll Import позволяет лучше управлять отдельными аспектами вызова. В частности, при вызове функции можно указать конвенцию передачи параметров, и кодировку для передачи строковых параметров.