]>Интерфейсы в COM

Интерфейсы в COM

Аватар пользователя mabu

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

COM в качестве решения этой проблемы использует указатель на интерфейс: всё, что нужно для работы с объектом — это получить указатель на его интерфейс, и ничего более. Этот указатель представляет собой «чёрный ящик», ссылку на данные, о содержимом которых программисту знать не нужно.

  1. Интерфейс IUnknown
    1. Первая подглава
    2. Вторая подглава
    3. Третья подглава
  2. Вторая глава
  3. Третья глава

Жизнь COM‐интерфейсов

В СОМ интерфейсы — это всё. Для клиента компонент представляет собой набор интерфейсов. Клиент может взаимодействовать с компонентом СОМ только через интерфейс. Компоненты сами по себе есть просто детали реализации интерфейсов.

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

Где обитают интерфейсы

Интерфейсы сами по себе летают в многомерных пространствах порядка и хаоса идеального мира, а их материальные воплощения живут во вполне конкретных местах на твоём компьютере: в динамически подключаемых библиотеках *.dll либо в исполняемых файлых *.exe.

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

Идентификация интерфейсов

В некоторых языках программирования для объявления интерфейсов есть ключевое слово Interface, некоторые языки эмулируют интерфейсы через абстрактные классы и чисто виртуальные функции, другие — через записи, структуры или пользовательские типы данных. Так как по правилам COM требуется независимость от языка программирования, то нужно было ввести единый свод правил, помогающий однозначно идентифицировать конкретный интерфейс.

UUID

Как‐то в один прекрасный день корпорация Open Software Foundation придумала концепцию UUID — Universally Unique Identifier, вселенски уникального идентификатора. Она взяла 128‐битное число и написала для его вычисления специальный алгоритм, более‐менее равномерно выдающий значения из такого огромного диапазона. Из‐за того, что само число очень большое, вероятность выдачи алгоритмом двух одинаковых чисел невелика. На практике можно говорить, что любое новое число UUID будет уникально.

GUID

Корпорация Microsoft взяла на вооружение эту мысль и число UUID без изменения, только назвала его немного по‐другому: GUID, то есть Globally Unique Identifier, глобально уникальный идентификатор.

Microsoft применяет GUID в качестве идентификаторов для классов (CLSID — Class Identifier), интерфейсов (IID — Interface Identifier), библиотек типов (LIBID — Library Identifier).

В тексте GUID записывается в виде строки из тридцати двух шестнадцатеричных цифр, разбитой на группы дефисами и опционально окружённой фигурными скобками. Ты уже наверняка где‐нибудь встречал похожие записи GUID:

Код Простой текст
{90125688-9C3F-4E6F-8D96-A8C71B8251E0}
{6F9619FF-8B86-D011-B42D-00CF4FC964FF}
{717473E7-54B3-4580-A086-2199430834DA}
{4C9E6590-A9C5-40AD-9E13-AB55F6AAC05F}

Объявление GUID

В заголовочном файле guiddef.bi GUID, IID (идентификатор интерфейса) и CLSID (идентификатор класса) объявляются в виде структур и дополнительных имён так:

Код FreeBASIC
Type _GUID
&t;Data1 As ULong
&t;Data2 As UShort
&t;Data3 As UShort
&t;Data4(0 To 7) As UByte
End Type

Type GUID As _GUID
Type IID As GUID
Type CLSID As GUID

Как видно, GUID — это дополнительное имя для структуры _GUID, а CLSID и IID — это дополнительные имена для GUID.

В исходном коде объявлять переменную типа GUID можно через длинный ряд шестнадцатеричных чисел. Посмотрим как объявляется IID интерфейса IXmlHttpRequest и CLSID класса XmlHttpRequest, который его реализует:

Код FreeBASIC
Dim CLSID_XMLHTTPREQUEST As CLSID = Type(&hED8C108E, &h4349, &h11D2, _
&t;{&h91, &hA4, &h00, &hC0, &h4F, &h79, &h69, &hE8})

Dim IID_IXmlHttpRequest As IID = Type(&hED8C108D, &h4349, &h11D2, _
&t;{&h91, &hA4, &h00, &hC0, &h4F, &h79, &h69, &hE8})

ProgID

Корпорация Microsoft посчитала, что запоминать все GUID для классов — это неудобно, и ввела программный идентификатор ProgID. Такой идентификатор представляет собой читаемую строку, определяющую класс. На формат ProgID не накладываются ограничения, поэтому он не может однозначно определять класс, однако ProgID удобен для чтения человеком.

Обычно все ProgID строятся по правилу: <ИмяПрограммы>.<ИмяКласса>.<Версия>, но многие разработчики отступают от этого правила и не указывают версию. Вот несколько примеров ProgID:

Код Простой текст
Visio.Application.3
Visio.Drawing.4
Word.Application.12
Microsoft.XmlHttp

Уникальность ProgID не гарантируется, поэтому существует принципиальная опасность конфликта имен.

COM‐интерфейсы

COM накладывает дополнительные ограничения на интерфейсы:

Интерфейс IUnknown

Интерфейс IUnknown является краеугольным камнем технологии. Любой COM‐объект должен наследоваться либо интерфейса IUnknown, либо от производного от него.

Интерфейс IUnknown концептуально играет две роли. Первая — это обеспечить стандартный способ получения интерфейса данного объекта. Эту возможность предоставляет метод QueryInterface. Вторая роль — это управление временем жизни объекта. Для этого предназначены два метода: AddRef и Release.

Вот описание IUnknown:

Код FreeBASIC
Type IUnknown As IUnknown_

Type IUnknownVirtualTable

&t;Dim QueryInterface As Function( _
&t;&t;ByVal This As IUnknown Ptr, _
&t;&t;ByVal riid As REFIID, _
&t;&t;ByVal ppv As Any Ptr Ptr _
&t;)As HRESULT

&t;Dim AddRef As Function( _
&t;&t;ByVal This As IUnknown Ptr _
&t;)As ULONG

&t;Dim Release As Function( _
&t;&t;ByVal This As IUnknown Ptr _
&t;)As ULONG

End Type

Type IUnknown_

&t;Dim lpVtbl As IUnknownVirtualTable Ptr

End Type

Прежде чем пойти дальше, рассмотрим тип HRESULT, возвращаемый функцией QueryInterface.

Тип данных HRESULT

Исключая методы AddRef и Release, все методы COM‐интерфейсов обязаны возвращать тип данных HRESULT. Он представляет собой 32‐битное число, каждый бит которого зарезервирован для определённых целей. Название возникло по историческим причинам, просто расшифровывай его как «вот результат» (here is the result).

Бит 31
Признак критичности: если установлен, то произошла непоправимая ошибка.
Биты с 30 по 16
Источник ошибки: какая часть операционной системы выдаёт данный код возврата.
Биты с 15 по 0
Собственно код возврата.

В таблице приведены часто используемые типы HRESULT. По соглашению в названиях успешных кодов содержится S_, а в названиях кодов ошибок E_.

КонстантаОписаниеЗначение
S_OKФункция отработала успешно. В некоторых случаях этот код также означает логическую истину.&h00000000
S_FALSEФункция отработала успешно и возвращает логическую ложь.&h00000001
E_FAILОшибка по неуказанной причине.&h80004005
E_ABORTОперация прервана.&h80004004
E_ACCESSDENIEDДоступ запрещён.&h80070005
E_HANDLEОписатель недействителен.&h80070006
E_INVALIDARGОдин или несколько аргументов неправильные.&h80070057
E_NOINTERFACEЗапрашиваемый интерфейс не поддерживается.&h80004002
E_NOTIMPLМетод не реализован.&h80004001
E_OUTOFMEMORYНевозможно выделить память.&h8007000E
E_POINTERУказатель недействителен.&h80004003
E_UNEXPECTEDНеожиданная ошибка.&h8000FFFF

Большой и длинный список HRESULT можно найти в статье на MSDN «HRESULT Values».

Множественность кодов возврата

Для проверки успешного завершения нельзя сравнивать HRESULT с каким‐либо одним кодом, например, S_OK или E_FAIL, необходимо использовать предопределённые макросы SUCCEEDED и FAILED.

Код FreeBASIC
Dim hr As HRESULT

' Не делай так:
If hr = E_FAIL Then
&t;Print "Ошибка"
End If
If hr = S_OK Then
&t;Print "Успешно"
End If

' Следует делать так:
If FAILED(hr) Then
&t;Print "Ошибка"
End If
If SUCCEEDED(hr) Then
&t;Print "Успешно"
End If

Метод QueryInterface

Метод QueryInterface предназначен для получения интерфейса по его идентификатору IID у интерфейса. IID передаётся как указатель вторым параметром (для высокоуровневых языков — первым, в них контекст вызова скрыт от программиста). Если интерфейс реализует и поддерживает конкретный интерфейс, то в третий параметр метод записывает указатель на него. Как минимум, у интерфейса можно запросить указатели на IUnknown и самого себя, потому что любой интерфейс наследуется от IUnknown и реализует самого себя.

Для упрощения понимания функцию QueryInterface можно считать как метод создания объекта, реализующего определённый интерфейс.

Функция QueryInterface может возвратить либо S_OK, либо E_NOINTERFACE, тем не менее, клиент не должен прямо сравнивать возвращаемое значение функции с этими константами; для проверки следует использовать макросы SUCCEEDED или FAILED.

Методы AddRef и Release

За управление временем жизни компонентов отвечают два метода интерфейса IUnknown: AddRef и Release через подсчёт ссылок на объект. Всякий раз, когда клиент запрашивает интерфейс, значение счётчика ссылок будет увеличиваться, а когда клиент завершает работу с интерфейсом — уменьшаться. В конце концов, когда значение счётчика ссылок станет равным нулю, объект будет самоуничтожен. Именно для этого и служат методы AddRef и Release.

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

Поделись ссылочкой в социальных сетях