Технология COM (Component Object Model)
Содержание
- Технология COM (Component Object Model)
- Intro
- История возникновения COM
- Основные понятия COM
- COM и DirectX
- Физическая реализация интерфейсов COM
- Инициализация COM. Функции CoInitialize и CoInitializeEx
- Создание неинициализированного компонента. Функция CoCreatelnstance
- Интерфейс IUnknown
- Создание собственных COM-функций
- Идентификация компонентов и интерфейсов COM
- Значения, возвращаемые функциями COM
- Источники
Intro
По данной теме написана не одна сотня книг (многие из которых на русском языке). А так как данная статья не претендует на полноту изложения, настоятельно рекомендуем прочесть хотя бы пару из них. Не факт, что всё усвоишь, но основные моменты всё равно запомнишь.Приложение, созданное, например, на языке C++, постоянно производит вызовы функций стандартной библиотеки Visual C++ а также функций операционной системы.1 Сервисы, предоставляемые средой выполнения (в нашем случае ОС Windows) многочисленны и разнообразны. Вот далеко неполный список:
- библиотечные функции;
- функции программного интерфейса ОС (API);
- средства межпроцессорной связи (например DDE);
- компоненты, разработанные для конкретной ОС (например VBX).
- Описывает правила создания программных компонентов, при использовании которых нет необходимости учитывать, в какой среде программирования создавался тот или иной модуль.
- Описывает архитектуру предоставления программных сервисов, скрывая реализацию этих сервисов. Это позволяет создавать более сложные программные комплексы за существенно более короткие сроки.
- На COM основана технология создания элементов управления ActiveX.3
- Управление другими программами через автоматизацию OLE.
- Определяет единый стандартный способ, при помощи которого одна часть ПО может воспользоваться "услугами" другой части ПО при условии, что эти части соответствуют принятым соглашениям, которые описывает COM.
- Определяет бинарный (двоичный) стандарт для построения компонентов приложений, а также принципов их взимоотношений друг с другом.
- На основе COM построена библиотека DirectX.
- Расширением спецификации COM является технология DCOM (Distributed COM - распределённая COM). Она позволяет взаимодействовать компонентам, расположенным на разных компьютерах в сети.
- Использование компонентов COM можно рассматривать как объектно-ориентированное, но выполняющееся на уровне операционной системы. Для СОМ также справедливы такие принципы ООП, как инкапсуляция (сокрытие реального кода), полиморфизм (предоставление двумя разными объектами одинакового набора интерфейсов) и наследование.
- Имеет расширение DCOM (Distributed COM), описывающее взаимодействие компонентов, расположенных на разных компьютерах в сети.
Использование COM-компонентов можно рассматривать как объектно-ориентированное, но выполняющееся на уровне операционной системы (ОС). Для COM также справедливы такие принципы ООП, как
- инкапсуляция (сокрытие реального кода),
- полиморфизм (ничто не мешает двум разным объектам пердоставлять одинаковый набор интерфейсов),
- наследование (в какой-то степени).
Компонентами COM-парадигмы являются бинарные (=двоичные) объекты, которые могут взаимодействовать друг с другом, используя различные интерфейсы, содержащиеся в них. Объекты могут быть легко заменены на более новые или исправленные версии, причём без внесения изменений в ранее созданные на их базе приложения.
История возникновения COM
До появления COM существовало множество разрозненных сервисов, выполняемых в среде MS Windows. Каждый из них выполнял или выполняет лишь свои узкие (или не очень) задачи. Чаще всего эти сервисы были совершенно несовместимы друг с другом и требовали от программиста знания сразу нескольких технологий (например Windows API, средства межпроцессорной связи, компоненты, разработанные для конкретной среды программирования).Например, при статической компоновке с исполняемым модулем, запуске приложением другого приложения или вызовом программным модулем функций операционной системы, - везде используются принципиально разные механизмы доступа составных частей ПО друг к другу. Таким образом, из разрозненных частей, таких как Windows API, DDE, VBX и др., практически невозможно построить единую, устойчиво работающую систему, обеспечивающую реальный рефакторинг (повторное использование исходного кода).
С приходом COM всё изменилось. COM представляет собой универсальный способ взаимодействия программных модулей. В этой спецификации, разработанной компанией Microsoft, описаны требования, следуя которым становится возможным создание компонентов, предоставляющие свои сервисы единообразно. Причём вне зависимости от языка программирования или используемой ИСР (интегрированной среды разработки, IDE).
Модель COM определяет бинарный (двоичный) стандарт для построения компонентов приложений, а также принципов их взаимодействия друг с другом. Физическим воплощением является скомпилированный (в любой среде программирования!) двоичный модуль, полностью соответствующий требуемым стандартам.
Способ взаимодействия, который определяет COM, даёт программисту прочный фундамент для создания легко расширяемого, моудльного, переносимого программного обеспечения.
Многие службы ОС Windows основаны на использовании COM. Например, к Windows Explorer можно получить доступ через COM.
Основные понятия COM
ОБЪЕКТ (или КОМПОНЕНТ) - закрытая сущность, скрывающая в себе нужную функциональность. Компонент может логически представлять собой реально существующее устройство (например, звуковая карта), либо быть некоторым абстрактным понятием.
Напрямую обратиться к объекту нельзя. Связь с ним возможна лишь при помощи интерфейсов, указатели на которые и может получить конкретная программа. Команды, которые программа может отправлять компоненту COM через интерфейс, называются МЕТОДАМИ. Каждый интерфейс содержит фиксированное число методов. Зная имя интерфейса соответствующего компонента, программа (или другой компонент) может получить указатель на этот интерфейс и, пользуясь им для вызова конкретного метода, заставить компонент выполнить действия, определённые этим методом (см Рис. 1).
В данном случае:
- клиент - это программа, использующая возможности компонента;
- сервер - это программный модуль, содержащий компонент COM.
ТИП СЕРВЕРА | ОПИСАНИЕ |
---|---|
Внутрипроцессный сервер (in-process server) | Представляет собой библиотеку DLL, проецируемую на адресное пространство клиентского процесса, то есть использующую его адресное пространство. |
Внепроцессный сервер (out-process server) | Представляет собой исполняемый файл EXE, выполняющийся в собственном адресном пространстве, однако на том же компьютере, что и клиент. |
Удалённый сервер (remote server) | Выполняется на удалённом компьютере и доступен благодаря DCOM. |
После того, как интерфейсы COM были созданы и опубликованы, они не должны изменяться. Требование неизменности опубликованных интерфейсов является обоснованным и защищает существующее ПО от возможных проблем: если производителю нужно добавить какую-то новую функциональность к уже существующему объекту, он просто создаст новый интерфейс, не затрагивая старые. Этот принцип также обеспечивает обратную совместимость с ранее созданными приложениями. Так например, игра, созданная под DirectX версии 6 или 7, будет работать и под управлением более поздних версий этой библиотеки (8 или 9 версии). А всё из-за того, что каждая новая версия DirectX полностью сохраняет функциональность своих предыдущих версий.
Как результат, вновь созданные программы смогут воспользоваться новейшими возможностями компонента, а старые программы сохранят свою работоспособность - ведь старый интерфейс остался без изменений и всё ещё работает. В отдельных случаях такой подход даже позволяет изменять внутреннюю реализацию старого интерфейса (хотя это делать не рекомендуется). Программа "не почувствует" изменения, так как в ответ на вызовы методов будут выполняться всё те же ранее оговорённые действия.
COM и DirectX
Компоненты DirectX, появившиеся в первых версиях этой бибилотеки, как правило, пытались скрыть свою COM-сущность (например, путём внедрения специальных функций, которые брали на себя всю работу по инициализации среды и созданию нужного интерфейса). Появившийся позднее DirectMusic и некоторые другие компоненты не имеют подобных функций, и для работы с ними требуется понимание основ используемой модели COM вцелом.Все компоненты DirectX реализованы как внутрипроцессные серверы и физически содержатся в DLL-файле. Такой подход позволяет обеспечить самую высокую производительность. А всё из-за того, что при работе с интерфейсом отсутствуют посредники, выполняющие, например, преобразование адресов. Библиотеки DLL, содержащие компоненты DirectX, подключаются напрямую к адресному пространству процесса (выполняемого приложения).
В документации DirectX SDK прототипы методов для всех интерфейсов приведены в виде объявления методов классов. То есть указатель на экземпляр интерфейса для методов не указывается. В языках, отличных от C++, необходимо явно передавать методу указатель на его интерфейс.
Физическая реализация интерфейсов COM
COM - это двоичный стандарт, обеспечивающий построение компонентов. Физически двоичная структура интерфейса COM аналогична двоичной структуре, которую компиляторы C++ генерируют для абстрактного класса C++.
Абстрактный класс (в C++) - класс, содержащий только виртуальные функции-методы (методы, для которых отсутствует реализация). Причиной создания таких методов является возможность вызывать в базовом классе методы, которые будут определены лишь в производных классах. Невозможно создать экземпляр абстрактного класса. Каждый класс, производный от абстрактного класса, должен определить свою реализацию чисто виртуальных методов. Лишь после этого появляется возможность создания экземпляров производного класса. Чисто виртуальные методы, которые производный класс переопределяет, для него становятся просто виртуальными методами.
Для каждого класса, содержащего виртуальные методы, компилятор создаёт специальную таблицу, в которой содержатся указатели на конкретные реализации виртуальных методов. Если класс переопределяет виртуальный метод базового класса, то в таблицу заносится указатель на переопределённый метод. Физически, для класса, содержащего виртуальные методы, указатель на таблицу виртуальных методов (обычно называемую vtable) хранится в первых четырёх байтах конкретного экземпляра класса.
Структура класса, содержащего виртуальные методы, приведена на Рис. 2.
Библиотека COM содержит функции, которые выполняют наиболее распространённые и общие действия по созданию компонентов и управлению интерфейсами. Пример - запуск сервера клиентом. Обрати внимание на то, что это именно функции, а не методы, принадлежащие какому-либо интерфейсу. Большинство функций библиотеки COM выполняют специфичные действия и применяются лишь в очень редких случаях. Такие функции, как правило, всегда имеют в названии префикс Co, что указывает на их принадлежность к библиотеке COM.
Инициализация COM. Функции CoInitialize и CoInitializeEx
Прежде чем начать использовать COM-объекты, необходимо сперва инициализировать саму систему COM. Интересно, что для этого применяются две разные функции, в зависимости от присутствия/отсутствия в приложении многопоточности. Для инициализации COM в однопоточном приложении вызывается функция CoInitialize:HRESULT CoInitialize( LPVOID pvReserved);
Для инициализации COM в многопоточном приложении вызывается функция CoInitializeEx:
HRESULT CoInitializeEx ( void *pvReserved, // NULL DWORD dwCoInit // Модель взаимодействия (concurrency model) );
При использовании функции CoInitializeEx в многопоточном приложении, во втором параметре необходимо указать т.н. режим (модель) взаимодействия для корректной работы системы COM. Параметр dwCoInit может принимать следующие значения:
ЗНАЧЕНИЕ | ОПИСАНИЕ |
---|---|
COINIT_APARTMENTTHREADED | При инициализации не создаётся нового клиентского подразделения. Все действия происходят в клиентском подразделении STA. Аналогично вызову CoInitialize. |
COINIT_MULTITHREADED | При инициализации создаётся новое клиентское подразделение MTA. Инициализирует поток для работы в многопоточном режиме. |
COINIT_DISABLE_OLE1DDE | Отключает DDE для поддержки OLE1. |
COINIT_SPEED_OVER_MEMORY | При создании потока COM увеличивает объём используемой памяти для улучшения быстродействия. |
Самый нормальный флаг (значение параметра dwCoInit) здесь - COINIT_MULTITHREADED.4
Клиентское подразделение в COM
- граница, разделяющая синхронные и асинхронные объекты; это воображаемая линия, нарисованная вокруг объектов и потоков их клиентов, которая разделяет COM-объекты с разными потоковыми характеристиками. Главная идея подразделений состоит в том, чтобы позволить COM выстроить в очередь вызовы методов для тех объектов, которые не являются потокобезопасными (thread-safe). Если вы не укажете COM, что объект является потокобезопасным, COM не позволит более чем одному вызову за раз достичь этот объект. Если же пометить объект как потокобезопасный, COM с легкостью позволит объекту обслуживать параллельные вызовы методов из разных потоков. Более подробно об этом читаем здесь: http://rsdn.ru/article/com/apartmnt.xml
По окончании работы с COM во всех случаях вызывается функция CoUninitialize, не требующая указания каких-либо параметров:
void CoUninitialize();
Данная функция вызывается столько раз, сколько было создано систем COM. Например, при инициализации системы COM дважды, необходимо дважды вызвать завершающую функцию CoUninitialize:
// Инициализируем систему COM CoInitialize(NULL); // Инициализируем систему COM в многопоточном режиме CoInitializeEx(NULL, COINIT_MULTITHREADED); // Освобождаем COM (дважды) CoUninitialize(); CoUninitialize();
Для большинства компонентов DirectX вызовы этих функций являются избыточными, так как их инициализация происходит внутри специальных "создающих" функций (например DirectSoundCreate). В то же время, во многих примерах DirectX SDK такая инициализация всё равно производится. Выполнять инициализацию библиотеки COM или нет, остаётся на усмотрение программиста.
Создание неинициализированного компонента. Функция CoCreatelnstance
После успешной инициализации библиотеки COM, функция CoCreateInstance создаёт неинициализированный компонент и получает интерфейс для работы с ним:STDAPI CoCreateInstance( REFCLSID rclsid, // GUID компонента LPUNKNOWN pUnkOuter, // Необходим для агрегирования DWORD dwClsContext, // Контекст запуска REFIID riid, // GUID интерфейса LPVOID * ppv // Адрес указателя на интерфейс );
Вот описание её параметров:
ПАРАМЕТР | ОПИСАНИЕ |
---|---|
REFCLSID rclsid | CLSID, ассоциированный с данными и выполняемым кодом, которые будут использоваться для создания объекта. Обычно содержит GUID создаваемого компонента. |
LPUNKNOWN pUnkOuter | Значение NULL указывает на то, что объект создаётся без агрегирования. В остальных случаях здесь стоит указатель на другой компонент (обычно с интерфейсом IUnknown), в который будет агрегирован вновь создаваемый компонент. |
DWORD dwClsContext | Флаг (или комбинация флагов), указывающий в каком контексте (каким способом) будет запускаться код создаваемого объекта. Возможные значения указаны в Таблице 1. |
REFIID riid | Ссылка на идентификатор интерфейса, который будет использоваться для коммуникации с объектом. Должен содержать GUID необходимого интерфейса. |
LPVOID * ppv | Адрес указателя на запрашиваемый интерфейс, который впоследствии и будет использоваться. |
При успешном завершении функция возвращает значение S_OK.
Таблица 1. Возможные значения параметра DWORD dwClsContext функции CoCreateInstance
ЗНАЧЕНИЕ | ОПИСАНИЕ |
---|---|
CLSCTX_INPROC_SERVER | Код, который создаёт объекты данного класса и управляет ими, представляет собой DLL-библиотеку (внутрипроцессный сервер), которая выполняется в том же процессе, что и вызывающая функция, описывающая содержимое класса. Обычно применяется при использовании компонентов DirectX, так как все компоненты этой библиотеки реализованы как внутрипроцессные серверы. Поскольку все компоненты DirectX реализованы как внутрипроцессные серверы, именно этот флаг почти всегда указывается при создании игровых приложений. |
CLSCTX_INPROC_HANDLER | Код, который создаёт объекты данного класса и управляет ими, представляет собой внутрипроцессный хэндлер ( дескриптора1 потока в ОС). По сути это та же DLL-библиотека, которая выполняется в клиентском процессе и имплементирует структуры данного класса, выполняющиеся на стороне клиента, в то время как доступ к экземплярам этого класса осуществляется удалённо. |
CLSCTX_LOCAL_SERVER | Код исполняемого EXE-файла, который создаёт объекты данного класса и управляет ими, выполняется на одном и том же компьютере, но загружаются они при этом в разных процессных пространствах (процессах). |
CLSCTX_INPROC_SERVER16 | Устаревший, вышедший из употребления флаг. |
CLSCTX_INPROC_HANDLER16 | Устаревший, вышедший из употребления флаг. |
CLSCTX_REMOTE_SERVER | Удалённый контекст. Код LocalServer32 или LocalService, который создаёт объекты данного класса и управляет ими, запускается на другом компьютере. |
CLSCTX_RESERVED1 | Зарезервирован. |
CLSCTX_RESERVED2 | Зарезервирован. |
CLSCTX_RESERVED3 | Зарезервирован. |
CLSCTX_RESERVED4 | Зарезервирован. |
CLSCTX_RESERVED5 | Зарезервирован. |
CLSCTX_ENABLE_CODE_DOWNLOAD | Разрешает загрузку программного кода из directory service или Интернета. Не может одновременно использоваться с флагом SCTX_NO_CODE_DOWNLOAD. |
SCTX_NO_CODE_DOWNLOAD | Отключает загрузку программного кода из directory service или Интернета. Не может одновременно использоваться с флагом CLSCTX_ENABLE_CODE_DOWNLOAD. |
CLSCTX_NO_CUSTOM_MARSHAL | Запрещает использование стороннего маршалинга. |
CLSCTX_NO_FAILURE_LOG | Отключает протоколирование ошибок во время выполнения функции CoCreateInstanceEx. |
CLSCTX_DISABLE_AAA | MS Windows XP и более позднии версии. Отключает использование AAA-активации (activate-as-activator - англ "активировать в качестве активатора") для данной активации. При AAA-активации любая активация на стороне сервера будет производиться от имени вызывающей функции. Отключение AAA-активации позволяет приложению, которое запущено под привелегированным аккаунтом (например LocalSystem), предотвратить использование его идентификационных данных для запуска непроверенных (недоверенных) компонентов. Библиотечные приложения, которые используют вызовы активации, должны всегда использовать данный флаг всякий раз при осуществлении вызова. Это помогает предотвратить использование библиотечных приложений для осуществления eop (escalation-of-privilege - англ. "повышение привелегий") атак. Не может одновременно использоваться с флагом CLSCTX_ENABLE_AAA. |
CLSCTX_ENABLE_AAA | MS Windows XP и более позднии версии. Включает использование AAA-активации (activate-as-activator - англ "активировать в качестве активатора") для данной активации. При AAA-активации любая активация на стороне сервера будет производиться от имени вызывающей функции. Включение данного флага позволяет приложению передавать свои идентификационные данные активированному компоненту. Переопределяет действие флага CLSCTX_DISABLE_AAA. |
SCTX_FROM_DEFAULT_CONTEXT | Начинает данную активацию с контекста по умолчанию в данном потоке. |
CLSCTX_ACTIVATE_32_BIT_SERVER | Активирует или подключается к 32-битной версии сервера. Если сервер не зарегистрирован, функция завершит работу, выдав ошибку. |
CLSCTX_ACTIVATE_64_BIT_SERVER | Активирует или подключается к 64-битной версии сервера. Если сервер не зарегистрирован, функция завершит работу, выдав ошибку. |
CLSCTX_ENABLE_CLOAKING | MS Windows Vista и более поздние версии. При указании данного флага, для активации запроса, созданного потоком, COM использует impersonationn token (от англ. "маркер олицетворения") потока, если таковой имеется. Когда данный флаг не указан, или поток не имеет маркера олицетворения, для активации запроса, созданного потоком, COM использует маркер процесса данного потока. |
CLSCTX_APPCONTAINER | Указывает на то, что активация производится для контейнера приложения. Данный флаг зарезервирован для служебного пользования и не предназначен для использования при создании приложений. |
CLSCTX_ACTIVATE_AAA_AS_IU | Указывает на активацию в качестве интерактивного пользователя (Interactive User activation) для серверов типа As-Activator. Например, приложение Windows Store app (где все имена процессов строго заданы и которое является приложением среднего уровня интегрированности (Medium IL app)) может использовать данный флаг для запуска COM-сервера типа As-Activator без строго заданного имени. Также данный флаг можно применять для привязки процесса к COM-серверу, запущенному ранее настольным Windows-приложением. Клиент при этом должен являться Medium IL app (приложение со средним уровнем интегрированности), должен иметь строго заданное имя (то есть поле SysAppID в его пользовательском маркере должно быть заполнено), не должен иметь нулевую сессию (session 0), и он должен быть запущен от того же имени пользователя, что указано в поле session ID его пользовательского маркера (client token). Если сервер имеет тип отличный от As-Activator или не функционирует, клиент запускает сервер с маркером сессии пользователя, взятого из пользовательского маркера. Этот маркер не будет иметь строго заданного имени. Флаг не работает для внутрипроцессных (in-process) серверов. |
CLSCTX_PS_DLL | Используется для загрузки Proxy/Stub DLL (DLL с серверной заглушкой). Данный флаг зарезервирован для служебного пользования и не предназначен для использования при создании приложений. |
Интерфейс IUnknown
- Является базовым интерфейсом (вообще, это класс), который наследуют любые другие интерфейсы COM.
- Определяется в файле unknwn.h.
- Методы, определённые для IUnknown (всего из 3), должны иметься в наличии в каждом интерфейсе.
- Основное требование COM: 3 базовых метода, определённых для IUnknown (QueryInterface, AddRef, Release), должны присутствовать в виде первых трёх методов любого интерфейса.
Опрос интерфейсов. Метод QueryInterface
- Применяется для обнаружения интерфейсов в COM-объектах. Включаяя их более новые версии.
- Осуществляет контроль версий.
Там принцип примерно следующий. Ты всё ещё можешь применять старый интерфейс (здесь - версию DirectX), но для того, чтобы получить доступ к более новому интерфейсу, просто запроси его. Если новый интерфейс присутствует, то в этом случае передаётся указатель на его объект. В остальных случаях возвращается NULL (для информирования об отсутствии интерфейса или об ошибке).
- Позволяет, имея один интерфейс компонента, запросить другой поддерживаемый интерфейс:
HRESULT IUnknown::QueryInterface( REFIID iid, // GUID (=ссылочный идентификатор, reference identifier) запрашиваемого интерфейса void ** ppvObject // Адрес указателя на запрашиваемый интерфейс );
В случае успешного завершения работы метода, по адресу void ** ppvObject будет записан реальный указатель на запрашиваемый интерфейс. При этом метод возвратит S_OK. Иначе - возвращается E_NOINTERFACE.
Данный метод очень важен, так как осуществляет контроль версий (ведь напрямую COM контроль версий не поддерживает). В следующем примере мы обнаруживаем (obtain) интерфейс IAdd и затем опрашиваем интерфейс IAdd2:
IAdd *Add; IAdd2 *pAdd2; // Сперва получаем интерфейс IAdd if(FAILED(CoCreateInstance(CLSID_MATH, NULL, CLSCTK_INPROC, IID_IAdd, (void**)&pAdd))) { //Обрабатываем ошибку } // Опрашиваем (query) на предмет наличия интерфейса IAdd2 if(SUCCEEDED(pAdd->QueryInterface(IID_IAdd2, (void**)&pAdd2))) { // Интерфейс обнаружен. Освободим интерфейс IAdd IAdd->Release(); }
Метод AddRef
- Увеличивает на 1 значение счётчика ссылок на данный интерфейс:
ULONG AddRef(void);
Метод Release
- Уменьшает на 1 значение счётчика ссылок на данный интерфейс:
ULONG Release(void);
Доп. инфо по методам QueryInterface, AddRef и Release
Методы AddRef и Release возвращают текущее значение счётчика ссылок. Программер должен следить, чтобы кол-во ссылок на данный COM-объект совпадало с числом функций Release, освобождающих ресурсы от более неиспользуемых COM-объектов ^COM-интерфейсов).Все 3 перечисленных выше метода позволяют управлять временем существования компонента. Таким образом COM возлагает всю ответственность по удалению компонента на сам компонент. Каждый компонент COM должен поддерживать счётчик количества ссылок на себя со стороны клиентов. Если счётчик становится равен нулю, компонент COM должен себя удалить. После этого указатели на его интерфейсы становятся недействительными. Такой подход позволяет избежать ситуации, когда один из клиентов попытается удалить компонент, а второй будет продолжать с ним работать.
При создании компонента его счётчик количества ссылок принимает значение 1. Любая функция, возвращающая указатель на интерфейс, должна увеличивать значение этого счётчика вызовом AddRef. Например, именно так поступает метод QueryInterface. Поэтому для интерфейса, получаемого через QueryInterface, метод AddRef вызывать не нужно. Необходимость этого возникает лишь в случае, когда происходит копирование указателя на интерфейс в другую переменную, то есть, когда компонент не имеет возможности отследить увеличение числа ссылок.
Вообще, метод AddRef в программах, лишь использующих компоненты COM, встречается довольно редко, а иногда и вовсе отсутствует. Вызов же метода Release встречается всегда. Общее правило гласит: как только интерфейс перестаёт быть нужным, для него необходимо вызвать метод Release. Впрочем, оно соблюдается далеко не всегда.
Обрати внимание
Не путай классы, интерфейсы и объекты. Объекты являются экземплярами (инстансами) классов. Интерфейсы - это коллекции функций внутри данных объектов. Другими словами, интерфейсы дают возможность получить доступ к данному объекту.
Создание собственных COM-функций
Для построения своих собственных функций, необходимо сперва создать дочерний класс (объект) от объекта IUnknown и уже в него вносить дополнительные функции. Напомним, что согласно стандарту Microsoft, COM-объект не может экспонировать свои переменные. Экспонируются только функции (в рамках данного интерфеса).Функции COM возвращают значение типа HRESULT, в котором содержится информация об успешном выполнении, либо код ошибки в случае неудачи.
Для запроса значения (любого типа) у COM-объекта необходимо передать указатель запрашиваемой переменной (он должен иметь тип WORD или DOUBLE WORD - байтовые значения здесь запрещены) в соответствующую функцию. В следующем примере мы создаём простой объект (ветвящийся от класса IUnknown), который принимает два числа, складывает (суммирует) их и возвращает результат в третью, заранее заготовленную переменную:
class IMyComObject : public IUnknown { public: HRESULT Add(long *Num1, long *Num2, long *Result); HRESULT IMyComObject::Add(long *Num1, long *Num2, long *Result) { // Суммирует 2 числа и сохраняет результат *Result = *Num1 + *Num2; // Возвращает код успешного завершения return S_OK; }
Обрати внимание
Имена всех COM-объектов начинаются с большой буквы I, которая означает, что данный объект является COM-интерфейсом.
Идентификация компонентов и интерфейсов COM
Поскольку компоненты хранятся в DLL или EXE файлах, должен существовать способ, позволяющий операционной системе узнать, в каком конкретно модуле хранится требуемый компонент. Однако возлагать ответственность за правильную идентификацию модуля на клиента неразумно. Не исключено, что поставщик компонента захочет изменить имя файла, в котором содержится компонент, в результате чего все старые клиенты не смогут правильно выполняться. COM предлагает совершенно другой подход. Для каждого компонента клиент должен знать уникальный номер, который однозначно и полностью идентифицирует этот компонент. Для этих целей используется GUID (Globally Unique IDentifier -Глобально уникальный идентификатор), который представляет собой специальную величину длиной 16 байт (в документации Microsoft чаще можно встретить значение 128 бит, что, в принципе, одно и то же), обеспечивающую необходимую уникальность. Гарантируется, что GUID, сгенерированный на конкретном компьютере в определённое время, не имеет себе аналогов во всём мире. Для этих целей в GUID помещаются сведения о времени его генерации и уникальных особенностях компьютера, на котором он был сгенерирован (чаще всего это MAC-адрес сетевого адаптера). В каталоге с установленной MS Visual C++ 6.0 расположены две утилиты для генерации GUID:УТИЛИТА | ОПИСАНИЕ |
---|---|
GUIDGEN.EXE | Генерирует GUID в одном из необходимых форматов и позволяет скопировать его в буфер обмена. |
UUIDGEN.EXE | Генерирует GUID в режиме командной стройки. |
В бесплатных версиях последних выпусков MSVC++ (2008, 2010, 2012 и др.) утилита GUIDGEN расположена в каталоге "Microsoft SDKs" (в нашем случае полный путь выглядит так: C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A \bin\guidgen.exe). В любом случае её нетрудно найти в Интернете. Библиотека Microsoft SDK устанавливается вместе с MSVC++ и содержит массу интересных утилит и вспомогательных компонентов. Пример GUID: 597507A3-8CB8-11d4-B079-94B45BEE2A64
GUID конкретного устройства хранится в реестре Windows в разделе HKEY_CLASSES_ROOT\CLSID. Внутри него создаются разделы, получающие имя в виде соответствующего GUID. Для каждого из таких разделов имеются подразделы, описывающие особенности компонента. Например, подраздел InprocServer32 описывает расположение DLL, содержащей компонент, и поддерживаемые им модели многопоточности.
Кроме идентификатора GUID используемого компонента программе-клиенту необходимо также знать идентификатор запрашиваемого интерфейса (напомним, что один компонент может поддерживать несколько интерфейсов). Для идентификации интерфейсов также используется GUID.
Значения GUID для компонентов и интерфейсов клиент (написанный на C/C++) узнаёт из заголовочных файлов, предназначенных для работы с нужным комопонетом. Согласно общепринятым соглашениям, имена значений GUID комопнентов начинаются с префикса CLSID_, а значения GUID интерфейсов c IID_.
Например, файл dsound.h, предназначенный для работы с DirectSound и расположенный в каталоге с установленным DirectX SDK (путь по умолчанию в нашем случае: C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include), содержит следующие определения GUID:
// DirectSound Component GUID {47D4D946-62E8-11CF-93BC-444553540000} DEFINE_GUID(CLSID_DirectSound, 0x47d4d946, 0x62e8, 0x11cf, 0x93, 0xbc, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0); // DirectSound 8.0 Component GUID {3901CC3F-84B5-4FA4-BA35-AA8172B8A09B} DEFINE_GUID(CLSID_DirectSound8, 0x3901cc3f, 0x84b5, 0x4fa4, 0xba, 0x35, 0xaa, 0x81, 0x72, 0xb8, 0xa0, 0x9b); DEFINE_GUID(IID_IDirectSound, 0x279AFA83, 0x4981, 0x11CE, 0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60);
Макрос DEFINE_GUID позволяет использовать вместо конкретных значений GUID символьные имена, например IID_IDirectSound. Это существенно облегчает чтение исходного кода программы. Этот макрос используется во многих заголовочных файлах DirectX для определения GUID компонентов и интерфейсов. Соответственно, чтобы нужные GUID получили свои значения, необходимо определять макрос INITGUID ДО включения нужных заглловочных файлов DirectX и только внутри одного файла с исходным кодом.
Большей части программ, работающих с компонентами DirectX, потребуется знать лишь GUID нужного интерфейса, так как для всех остальных компонентов предусмотрены функции, получающие в качестве параметра GUID интерфейса и скрывающие внтури себя GUID компонента.
Однако для некоторых компонентов, например DirectMusic, потребуется знание обоих GUID.
Значения, возвращаемые функциями COM
- Обычно изучаются для того, чтобы понять каким образом COM сигнализирует об ошибках.
- Как правило, тип возвращаемого значения COM-функций - HRESULT (32-битное знаковое значение).
#define FACDS 0x878
Последний 31-й бит возвращаемого значения содержит флаг критичности, который указывает, произошла ошибка или нет. Если этот бит равен 0, то ошибки не было, а если он равен 1, то произошла ошибка. Так как 31-й бит является знаковым, то наиболее простой способ определить, произошла ошибка или нет - это сравнить возвращаемое значение с нулём: отрицательное возвращаемое значение всегда означает наличие ошибки.
Такими сравнениями как раз и занимаются макросы, определённые в WINERROR.H:
... #define SUCCEEDED(Status) ((HRESULT)(Status) >>= 0) #define FAILED(Status) ((HRESULT)(Status) <<= 0) ...
Первый из них проверяет возвращаемое значение на успешность а второй - на неудачу. Microsoft рекомендует проверять возвращаемые значения только с использованием этих макросов.
Вот пример:
if (SUCCEEDED(CoInitialize(NULL)))
Такой подход объясняется тем тем, что кодов успешного завершения функции может быть несколько. И все они будут иметь в 31-м бите значение 0 (то есть их величины окажутся неотрицательными). В примерах DirectX SDK эти макросы используются повсеместно .
Для построения возвращаемого значения из упомянутых частей используется макрос MAKE_HRESULT. Например, так он используется в DirectSound при построении возвращаемого значения:
#define MAKE_DSHRESULT(code) MAKE_HRESULT(1, _FACDS, code) #define DS_OK S_OK #define DSERR_BADFORMAT MAKE_DSHRESULT(10 0)
Здесь определяется единственный код успешного завершения, а все остальные строятся из расчёта, что они неуспешны (в макрос MAKE_HRESULT передаётся значение 1 для 31-го бита).
Источники
1. Гончаров Д., Салихов Т. DirectX 7.0 для программистов. Учебный курс (+CD). - СПб.: Питер, 2001. - 528 с.: ил.
2. Adams J. Programming Role Playing Games with DirectX, 2nd Ed. - Premier Press, 2004
3. Кальверт Ч. Базы данных в Delphi 4. - DiaSoft-Киев, 1999
4. Adams J. Programming Role Playing Games with DirectX 8.0. - Premier Press, 2002
Последние изменения страницы Понедельник 27 / Июнь, 2022 07:56:54 MSK
Последние комментарии wiki