Принципы работы СОМ
Технология СОМ
упрощает создание программ,
сохраняющих совместимость в разных
версиях платформы Windows и более или
менее независимых от языка
программирования. Компоненты СОМ
могут создаваться на разных языках,
включая классический С (вариант для мазохистов), C++, Delphi, VB5 и 6. Технология СОМ с большим успехом применялась для создания объектов, предназначенных для решения специализированных задач, таких как элементы VB OCX.
Технология СОМ
была задумана как механизм, при
помощи которого программные
компоненты получают информацию о
возможностях других компонентов и
обращаются к ним с запросами, не
беспокоясь о подробностях
внутренней реализации [ Существуют
и другие технологии,
ориентированные на повторное
использование программного кода (например,
CORBA), но пока наибольшего успеха
добилась именно модель СОМ. ]. Для
этого был выработан стандартный
протокол получения информации об
интерфейсах, поддерживаемых
другими компонентами, наряду со
стандартизацией средств для
обращения к конкретной реализации
интерфейса в экземплярах.
Тем не менее у
СОМ были свои недостатки. Во-первых,
реализация СОМ для Windows требовала,
чтобы в системном реестре
хранилась вся информация обо всех
компонентах в системе.
Пользователю приходилось
регистрировать компоненты при
установке программ и стирать
соответствующую информацию при
удалении программ. При попытке
удаления программ возникала
опасность того, что изменения,
внесенные в реестр, повлияют на
работу других программ. Стоило
серьезно повредить реестр, и
система вообще переставала
работать. Более того, установка
новой версии компонента нередко
нарушала работу программ,
рассчитанных на более раннюю
версию компонента.
Давайте
посмотрим, что происходит на уровне
реестра при регистрации
компонентов СОМ.
- Разработчик
создает для компонента
глобально-уникальный
идентификатор (GUID).
- Разработчик
создает для компонента
программный идентификатор (ProgID).
- Утилита
регистрации связывает ProgID
компонента с GUID, создавая
соответствующую запись в
реестре.
- Утилита
регистрации заносит полный
путь к двоичному файлу
компонента в реестр и
связывает его с GUID компонента.
- Утилита
регистрации также может
сохранить в реестре
дополнительные сведения о
компоненте — например, тип
потоковой модели.
При попытке
использования компонента
происходит следующее:
- Разработчик
приложения создает экземпляр
компонента, используя ProgID.
- СОМ
ищет в реестре GUID компонента.
- СОМ
находит двоичный файл
компонента.
- СОМ
создает экземпляр компонента.
Несмотря на
большое количество выполняемых
операций, главные проблемы
возникают при копировании в
систему нового файла с компонентом,
не сопровождающимся обновлением
реестра, и при смене GUID. Приложение,
которое раньше благополучно
работало, работать перестает. Это
связано с тем, что в механизме
установки СОМ не предусмотрено
нормальных средств для контроля
версии компонентов.
Как было
сказано в начале этой главы,
процесс установки в .NET часто
сводится к простому копированию
файлов, после чего программа готова
к немедленному запуску. Если
удалить скопированные файлы, то
работать перестает только эта
конкретная программа. В этом
процессе не используется реестр и
не учитываются зависимости между
компонентами. Чтобы эта схема
нормально работала, в .NET
используется концепция сборки.
С технической
точки зрения сборка (assembly) в .NET
представляет собой минимальную
устанавливаемую единицу
программного кода. Сборка
оформляется в виде автономного ЕХЕ-файла
или в виде библиотеки DLL, на которую
можно ссылаться из других
приложений. Однако сборка содержит
нечто большее, чем обычный IL-код,
компилируемый и выполняемый
исполнительной средой .NET. Как
минимум, сборка состоит из одного
или нескольких модулей и классов,
откомпилированных в IL-код, и
метаданных (данных, описывающих
данные [ Префикс «мета» для
подобных абстракций второго
порядка позаимствован из
метаматематики — области
математики, посвященной описанию
самих математических объектов. ]),
которые описывают сборку и
функциональность входящих в нее
классов. Метаданные являются
частью сборки, поэтому в
документации сборки названы
самодокументируемыми. Во многих
ситуациях сборка состоит из одного
файла, но встречаются и
многофайловые сборки. Например, в
сборку могут входить ресурсные
файлы, графические изображения и
даже дополнительные EXE/DLL-файлы. В
любом случае сборка является
минимальным объектом .NET, для
которого производится контроль
версии или задаются привилегии.
Сборки бывают
закрытыми (private) и общими (shared). Закрытые
сборки всегда находятся в
каталоге приложения или в одном из
его подкаталогов. Общие сборки
хранятся в глобальном кэше сборок
(GAC, global assembly cache). Начнем с
закрытых сборок, поскольку именно
они используются по умолчанию для
решений, построенных в VS .NET IDE. С
общими сборками дело обстоит
сложнее, и мы займемся ими позже.
Обычно у
закрытых сборок не бывает проблем с
несовместимостью версий, однако
они требуют дополнительных затрат
дискового пространства, если в
системе приходится хранить
несколько копий одного файла в
разных каталогах [ В наше время
дисковое пространство обходится
так дешево, что эти затраты с
избытком компенсируются
удобствами, связанными с
использованием закрытых сборок. ].
При создании ссылок на сборку
командой Project > Add Reference по
умолчанию в каталоге приложения
создается новый экземпляр закрытой
сборки. Мы рекомендуем по
возможности ограничиваться
использованием закрытых сборок.
Манифест
Теоретически
сборка может быть устроена весьма
сложно, поэтому в нее включается манифест
— совокупность всех сведений о
сборке, необходимых исполнительной
среде (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.*")>
В каталоге \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 позволяют
создавать многофайловые сборки.
Хотя в книге данная тема не
рассматривается, мы хотим обратить
ваше внимание на одну интересную
особенность многофайловых сборок:
они тоже могут устанавливаться
простым копированием, причем
устанавливать сразу все файлы на
компьютер конечного пользователя
не обязательно — компоненты могут
копироваться по мере надобности.
Эта возможность очень удобна для
установки по Интернету, где
приходится учитывать объем
пересылаемой информации. Например,
в многофайловой сборке прием
компонента справочной системы
можно отложить до того момента,
когда пользователь захочет вызвать
справку.
Общие сборки и GAC
Общие сборки .NET
хранятся в глобальном кэше сборок (GAC).
Наличие глобального кэша экономит
дисковое пространство и память,
поскольку на стадии выполнения
программы на диске или в памяти
достаточно хранить лишь один
экземпляр сборки. Конечно, при
совместном использовании сборок
возникают некоторые проблемы,
присущие старому механизму
совместного использования DLL на
базе реестра. К счастью, средства
контроля версии .NET позволяют
хранить в GAC разные версии одной
сборки, поэтому каждое приложение
может использовать нужную версию.
Постарайтесь как можно реже
использовать глобальный кэш сборок
и ограничиться следующими
ситуациями:
- если
использование сборки в разных
приложениях вызвано
абсолютной необходимостью, но
по соображениям экономии места
хранение нескольких локальных
копий нежелательно;
- если
сборка требует особого уровня
защиты (удаление сборок из 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 —
например, последней версией пакета
Microsoft Installer (MSI). Описание этой
программы выходит за рамки книги,
но мы укажем, что эту программу
можно бесплатно загрузить с сайта
MSDN (http://msdn.microsoft.com).
На стадии
разработки вместо программы
установки обычно используется
утилита
gacutil.exe.
Синтаксис командной строки:
gacutil -1 <имя_сборки>
Сборка с
заданным именем помещается в GAC.
Сильные имена и
совместное использование сборок
Сильные имена,
как и GUID, должны быть уникальными в
пространстве и времени. В отличие
от GUID, которые теоретически могут
быть похищены, сильные имена
защищены от несанкционированного
использования при помощи механизма
шифрования с открытым ключом [ При
условии надежного хранения
закрытого ключа. ]. Конкретные
схемы шифрования бывают очень
сложными, однако в целом шифрование
с открытым ключом построено на
довольно простой идее — в
некоторых ситуациях вернуться от
конечного результата к исходным
данным бывает очень, очень сложно.
Например, вы можете легко
перемножить два целых числа и
получить результат, но узнать
исходные множители по произведению
невозможно — для этого нужно знать
хотя бы один из них [ Этот принцип
заложен в основу RSA,
распространенного алгоритма
шифрования с открытым ключом. ].
Во всех схемах с
открытым ключом используется пара
ключей: открытый и закрытый. Открытый
ключ (public key) может свободно
распространяться, поскольку без
знания закрытого ключа
зашифрованное сообщение
невозможно восстановить за сколько-нибудь
приемлемый промежуток времени.
Применение закрытого ключа к
данным манифеста позволяет сертифицировать
их. Другие пользователи при
помощи открытого ключа убеждаются
в том, что сборка поступила именно
от вас, а не из постороннего
источника. А в некоторых случаях (например,
при использовании Verisign) они даже
могут убедиться в том, что открытый
ключ принадлежит именно вам, а не
кому-то другому (шифрование с
открытым ключом защищает
целостность данных, но для проверки
открытого ключа необходимы услуги
третьей стороны).
Построение ключей
При
программировании в .NET ключи (открытый
и закрытый) обычно создаются
утилитой sn.exe, входящей в .NET SDK (сокращение
«sn» означает «strong name», то есть «сильное
имя»).
Ключи хранятся
в двоичных файлах. Команда
построения ключей имеет следующий
синтаксис:
sn -k <имя_файла>
Файлам должно
быть присвоено расширение .snk. В
нашем примере пара ключей была
создана командой
sn -k c:\keys\pair.snk
При наличии
пары ключей в виде файла .snk можно
сертифицировать сборку закрытым
ключом. При этом .NET включает
открытый ключ в манифест сборки [ Точнее
говоря, в манифест включается
хэшированная версия ключа, которая
в .NET называется образцом (token)
открытого ключа. Вероятно,
хэширование применяется для
экономии места, хотя нам кажется,
что в манифесте следовало бы просто
опубликовать открытый ключ. ] и
вычисляет хэш-код всех данных
сборки по закрытому ключу.
Результат тоже включается в
манифест. По этим данным другие
сборки, использующие ваш код,
смогут убедиться в том, что сборка
не подверглась постороннему
вмешательству. Для этого открытый
ключ применяется к зашифрованному
хэш-коду, а результат сравнивается
с приведенным в манифесте.
Чтобы
сертифицировать сборку, включите в
программу атрибут AssemblyKeyFile-Attribute с
именем файла .snk после всех команд
импортирования или же
воспользуйтесь вкладкой Sharing
диалогового окна Project Settings. Пример:
Imports System.Reflection
<Assembly:AssemblyKeyFi1eAttribute("c:\keys\pai r. snk")>
\
COM Interop и вызовы
функций DLL
Несмотря на
появление .NET, существующий код на
базе СОМ еще не собирается умирать.
К счастью, уровень взаимодействия с
СОМ в .NET работает очень хорошо. С
другой стороны, использование СОМ в
проектах .NET снижает быстродействие
и затрудняет сопровождение
программ, поэтому эти технологии
объединяются лишь при крайней
необходимости.
В Visual Studio .NET
взаимодействие с СОМ почти не
требует усилий со стороны
программиста. Просто выберите
нужный объект СОМ на вкладке СОМ
диалогового окна ссылок, и с ним
можно будет работать как с классом .NET.
Для этого IDE читает библиотеку
типов СОМ и создает для объекта
вспомогательный класс («обертку») .NET.
Открытыми членами этого класса
являются все открытые члены
объекта СОМ. Кстати, технология
IntelliSense работает и для экземпляров
этих классов.
Хотя при вызове
функций 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
позволяет лучше управлять
отдельными аспектами вызова. В
частности, при вызове функции можно
указать конвенцию передачи
параметров, и кодировку для
передачи строковых параметров.