Загрузка...
 
Печать

Технология COM (Component Object Model)


По теме COM написана не одна сотня книг. Так как данная статья не претендует на полноту изложения, рекомендуется прочитать хотя бы несколько из них.

COM (или Component Object Module) - это стандарт программирования, разработанный компанией Microsoft.

  • Описывает правила создания программных компонентов, при использовании которых нет необходимости учитывать, в какой среде программирования создавался тот или иной модуль.
  • Описывает архитектуру предоставления программных сервисов, скрывая реализацию этих сервисов. Это позволяет создавать более сложные программные комплексы за существенно более короткие сроки.
  • Определяет единый стандартный способ, при помощи которого одначасть ПО может воспользоваться "услугами" другой части ПО при условии, что эти части соответствуют принятым соглашениям, которые описывает COM.
  • На основе COM построена библиотека DirectX.
  • Расширением спецификации COM является технология DCOM (Distributed COM - распределённая COM). Она позволяет взаимодействовать компонентам, расположенным на разных компьютерах в сети.
  • Использование компонентов COM можно рассматривать как объектно-ориентированное, но выполняющееся на уровне операционной системы. Для COM также справедливы такие принципы ООП, как инкапсуляция(external link) (сокрытие реального кода), полиморфизм(external link) (предоставление двумя разными объектами одинакового набора интерфейсов) и наследование(external link).


Вообще, наследование, в привычном для объектно-ориентированных языков понимании (наследование реализации), в COM не поддерживается. Но поддерживается наследование интефрейсов, которое позволяет наследовать интерфейс базового класса. Чаще всего это происходит через агрегирование (aggregation), то есть, когда один компонент способен наследовать интерфейс другого компонента таким образом, что последний интерфейс может рассматриваться, как относящийся к первому компоненту. Это понятие часто встречается при работе с компонентами DirectX и означает возможность использования интерфейсов создаваемого компонента как своих собственных.

Компонентами COM-парадигмы являются бинарные (=двоичные) объекты, которые могут взаимодействовать друг с другом, используя различные интерфейсы, содержащиеся в них. Объекты могут быть легко заменены на более новые или исправленные версии, причём без внесения изменений в ранее созданные на их базе приложения.

История возникновения COM

До появления COM существовало множество разрозненных сервисов, выполняемых в среде MS Windows. Каждый из них выполнял или выполняет лишь свои узкие (или не очень) задачи. Чаще всего эти сервисы были совершенно несовместимы друг с другом и требовали от программиста знания сразу нескольких технологий (например Windows API, средства межпроцессорной связи, компоненты, разработанные для конкретной среды программирования).
Например, при статической компоновке с исполняемым модулем, запуске приложением другого приложения или вызовом программным модулем функций операционной системы, - везде используются принципиально разные механизмы доступа составных частей ПО друг к другу. Таким образом, из разрозненных частей, таких как Windows API, DDE, VBX и др., практически невозможно построить единую, устойчиво работающую систему, обеспечивающую реальный рефакторинг (повторное использование исходного кода).
С приходом COM всё изменилось. COM представляет собой универсальный способ взаимодействия программных модулей. В этой спецификации, разработанной компанией Microsoft, описаны требования, следуя которым становится возможным сосздание компонентов, предоставляющие свои сервисы единообразно. Причём вне зависимости от языка программирования или используемой ИСР(external link).
Модель COM определяет бинарный (двоичный) стандарт для построения компонентов приложений, а также принципов их взаимодействия друг с другом. Физическим воплощением является скомпилированный (в любой среде программирования!) двоичный модуль, полностью соответствующий требуемым стандартам.
Способ взаимодействия, который определяет COM, даёт программисту прочный фундамент для создания легко расширяемого, моудльного, переносимого программного обеспечения.

Основные понятия COM

Рис. 1.  Взаимодействие клиентского приложения с сервером  COM
Рис. 1. Взаимодействие клиентского приложения с сервером COM

ОБЪЕКТ (или КОМПОНЕНТ) - закрытая сущность, скрывающая в себе нужную функциональность. Компонент может логически представлять собой реально существующее устройство (например, звуковая карта), либо быть некоторым абстрактным понятием.
Напрямую обратиться к объекту нельзя. Связь с ним возможна лишь при помощи интерфейсов(external link), указатели на которые и может получить конкретная программа.
Команды, которые программа может отправлять компоненту COM через интерфейс, называются МЕТОДАМИ. Каждый интерфейс содержит фиксированное число методов. Зная имя интерфейса соответствующего компонента, программа (или другой компонент) можкт получить указатель на этот интерфейс и, пользуясь им для вызова конкретного метода, заставить компонент выполнить действия, определённые этим методом (см Рис. 1).
В данном случае:

  • клиент - это программа, использующая возможности компонента;
  • сервер - это программный модуль, содержащий компонент COM.

Клиенту необходимо каким-то образом инициализировать запуск сервера, содержащего необходимы компонент COM (это может быть динамическая библиотека DLL или исполняемый файл EXE). После этого необходимо получить указатель на нужный интерфейс. Используя этот указатель, можно затем вызывать методы полученного интерфейса.
Различают 3 типа серверов, с использованием которых реализуются 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. Интересно, что для этого применяются две разные функции, в зависимости от присутствия/отсутствия в приложении многопоточности.
Для инициализации COM в однопоточном приложении вызывается функция CoInitialize:

Инициализация системы COM для однопоточного приложения
HRESULT CoInitialize( LPVOID pvReserved); // NULL

Для инициализации COM в многопоточном приложении вызывается функция CoInitializeEx:

Инициализация системы COM для многопоточного приложения
HRESULT CoInitializeEx( void *pvReserved, // NULL
						DWORD dwCoInit);  // Модель взаимодействия (concurrency model)

При использовании функции CoInitializeEx в многопоточном приложении, во втором параметре необходимо указать т.н. режим (модель) взаимодействия для корректной работы системы COM.

По окончании работы с COM во всех случаях вызывается функция CoUninitialize, не требующая указания каких-либо параметров:

Закрытие системы COM
void CoUninitialize();

Данная функция вызывается столько раз, сколько было создано систем COM. Например, при инициализации системы COM дважды, необходимо дважды вызвать завершающую функцию CoUninitialize:

Пример
// Инициализируем систему COM
CoInitialize(NULL);

// Инициализируем систему COM в многопоточном режиме
CoInitializeEx(NULL, COINIT_MULTITHREADED);

// Освобождаем COM (дважды)

CoUninitialize();
CoUninitialize();

Интерфейс IUnknown

  • Является базовым интерфейсом (вообще, это класс), который наследуют любые другие интерфейсы COM.
  • Определяется в файле unknwn.h.
  • Методы, определённые для IUnknown (всего из 3), должны иметься в наличии в каждом интерфейсе.
  • Основное требование COM: 3 базовых метода, определённых для IUnknown, должны присутствовать в виде первых трёх методов любого интерфейса.

Вот эти методы:

Метод QueryInterface позволяет, имея один интерфейс компонента, запросить другой поддерживаемый интерфейс:

Прототип метода QueryInterface
HRESULT QueryInterface(
REFIID iid,		// GUID запрашиваемого интерфейса
void ** ppvObject		// Адрес указателя на запрашиваемый интерфейс
);

В случае успешного завершения работы метода, по адресу void ** ppvObject будет записан реальный указатель на запрашиваемый интерфейс. При этом метод возвратит S_OK. Иначе - возвращается E_NOINTERFACE.
Данный метод очень важен, так как осуществляет контроль версий (ведь напрямую COM контроль версий не поддерживает).
Метод AddRef увеличивает на 1 значение счётчика ссылок на данный интерфейс:

Прототип метода AddRef
ULONG AddRef(void);

Метод Release уменьшает на 1 значение счётчика ссылок на данный интерфейс:

Прототип метода Release
ULONG Release(void);

Оба метода возвращают текущее значение счётчика ссылок.
Все 3 перечисленных выше метода позволяют управлять временем существования компонента. Таким образом COM возлагает всю ответственность по удалению компонента на сам компонент. Каждый компонент COM должен поддерживать счётчик количества ссылок на себя со стороны клиентов. Если счётчик становится равен нулю, компонент COM должен себя удалить. После этого указатели на его интерфейсы становятся недействительными. Такой подход позволяет избежать ситуации, когда один из клиентов попытается удалить компонент, а второй будет продолжать с ним работать.
Когда компонент создаётся, его счётчик количества ссылок принимает значение 1. Любая функция, возвращающая указатель на интерфейс, должна увеличивать значение этого счётчика вызовом AddRef. Например, именно так поступает метод QueryInterface. Поэтому для интерфейса, получаемого через QueryInterface, метод AddRef вызывать не нужно. Необходимость этого возникает лишь в случае, когда происходит копирование указателя на интерфейс в другую переменную, то есть, когда компонент не имеет возможности отследить увеличение числа ссылок.
Вообще, метод AddRef в программах, лишь использующих компоненты COM, встречается довольно редко, а иногда и вовсе отсутствует. Вызов же метода Release встречается всегда. Общее правило гласит: как только интерфейс перестаёт быть нужным, для него необходимо вызвать метод Release. Впрочем, оно соблюдается далеко не всегда.

Закрыть
noteОбрати внимание

Не путай классы, интерфейсы и объекты. Объекты являются экземплярами (инстансами) классов. Интерфейсы - это коллекции функций внутри данных объектов. Другими словами, интерфейсы дают возможность получить доступ к данному объекту.


Для построения своих собственных функций, необходимо создать дочерний класс (объект) от объекта IUnknown и уже в него вносить дополнительные функции. Напомним, что согласно стандарту Microsoft, COM-объект не может экспонировать свои переменные. Экспонируются только функции (в рамках данного интерфеса).
Функции COM возвращают значение типа HRESULT, в котором содержится информация об успешном выполнении, либо код ошибки в случае неудачи.
Для запроса значения (любого типа) у COM-объекта необходимо передать указатель запрашиваемой переменной (он должен иметь тип WORD или DOUBLE WORD - байтовые значения здесь запрещены) в соответствующую функцию.
В следующем примере мы создаём простой объект (ветвящийся от класса IUnknown), который принимает два числа, складывает (суммирует) их и возвращает результат в третью, заранее заготовленную переменную:

Пример работы с COM
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-объектов

Для использования COM-объекта его необходимо сперва создать (также как и библиотеку с кодом, загружаемую Windows) путём вызова функции CoCreateInstance. Вот её прототип:

Прототип функции CoCreateInstance
STDAPI CoCreateInstance(
   REFCLSID rclsid,	// Идентификатор базового класса объекта
   LPUNKNOWN pUnkOuter,	// NULL
   DWORD dwClsContext,	// CLSCTX_INPROC REFIID
   riid, // Ссылка на идентификатор интерфейса
   LPVOID *ppv); // Указатель на получаемый объект

Для применения функции CoCreateInstance необходимо знать название класса объекта и идентификаторы интерфейсов. Идентификатор класса, обозначаемый префиксом CLSID_, это базовый класс объекта, на основе которого был создан данный объект. А ссылка, обозначаемая префиксом IID_, как раз и является искомым интерфейсом.
К примеру, у нас есть класс Math, с идентификатором CLSID_MAT. Данный класс содержит 3 объекта: IAdd (с идентификатором ссылки IID_IAdd), ISubstract (IID_ISubstract) и IAdd2 (IID_IAdd). Для обращения к объекту IAdd2 вызов функции CoCreateInstance будет выглядеть так:

Пример обращения к интерфейсу объекта
IAdd2 *pAdd2;

if(FAILED(CoCreateInstance(CLSID_MATH, NULL, CLSCTX_INPROC, IID_IAdd2, (void**)&pAdd2))) {
// Error occurred
}


Все COM-объекты, инициализированные ранее в приложении, должны быть обязательно освобождены путём вызова функции IUnknown::Release() (да, без параметров):

Прототип функции Release
HRESULT IUnknown::Release();

В нашем примере по завершении обращения к интерфейсу IAdd2 освобождаем его следующим образом:

IAdd2->Release();

Идентификация компонентов и интерфейсов 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++ и содержит массу интересных утилит и вспомогательных компонентов.

Закрыть
noteПример 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:

фрагмент файла dsound.h
...
// 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 аналогична двоичной структуре, которую компиляторы C++ генерируют для абстрактного класса C++.

Закрыть
noteАбстрактный класс (в C++)

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

Рис. 2. Двоичная структура интерфейса COM аналогична структуре обычного абстрактного класса C++
Рис. 2. Двоичная структура интерфейса COM аналогична структуре обычного абстрактного класса C++

Для каждого класса, содержащего виртуальные методы, компилятор создаёт специальную таблицу, в которой содержатся указатели на конкретные реализации виртуальных методов. Если класс переопределяет виртуальный метод базового класса, то в таблицу заносится указатель на переопределённый метод. Физически, для класса, содержащего виртуальные методы, указатель на таблицу виртуальных методов (обычно называемую vtable) хранится в первых четырёх байтах конкретного экземпляра класса.
Структура класса, содержащего виртуальные методы, приведена на Рис. 2.

Библиотека COM

COM - это не только спецификация. В операционных системах семейства MS Windows существует поддержка работы с компонентами COM, называемая библиотекой COM. Здесь все функции этой технологии реализованы в библиотеке OLE32.DLL .

Функции библиотеки COM

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

Прототип функции CoInitialize
HRESULT CoInitialize(
LPVOID pvReserved	// Зарезервированный параметр
);

Её единственный зарезервированный параметр должен быть равен NULL.
В случае успешного завершения возвращается значение S_OK.
Существует также расширенная версия этой функции CoInitializeEx, которая имеет второй параметр dwCoInit типа DWORD, где содержатся опции инициализации потока:

Прототип функции CoInitializeEx
HRESULT CoInitializeEx(
LPVOID pvReserved,	// Зарезервированный параметр
DWORD  dwCoInit		// опции многопоточности
);

Параметр dwCoInit может принимать следующие значения:

Значение Описание
COINIT_APARTMENTTHREADED При инициализации не создаётся нового клиентского подразделения. Все действия происходят в клиентском подразделении STA. Аналогично вызову CoInitialize.
COINIT_MULTITHREADED При инициализации создаётся новое клиентское подразделение MTA. Инициализирует поток для работы в многопоточном режиме.
COINIT_DISABLE_OLE1DDE Отключает DDE для поддержки OLE1.
COINIT_SPEED_OVER_MEMORY При создании потока COM увеличивает объём используемой памяти для улучшения быстродействия.
Закрыть
noteКлиентское подразделение в COM

- граница, разделяющая синхронные и асинхронные объекты; это воображаемая линия, нарисованная вокруг объектов и потоков их клиентов, которая разделяет COM-объекты с разными потоковыми характеристиками. Главная идея подразделений состоит в том, чтобы позволить COM выстроить в очередь вызовы методов для тех объектов, которые не являются потокобезопасными (thread-safe). Если вы не укажете COM, что объект является потокобезопасным, COM не позволит более чем одному вызову за раз достичь этот объект. Если же пометить объект как потокобезопасный, COM с легкостью позволит объекту обслуживать параллельные вызовы методов из разных потоков.
Более подробно об этом читаем здесь http://rsdn.ru/article/com/apartmnt.xml(external link)

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

Прототип функции CoUninitialize
void CoUninitialize();

Для большинства компонентов DirectX вызовы этих функций являются избыточными, так как их инициализация происходит внутри специальных "создающих" функций (например DirectSoundCreate). В то же время во многих примерах DirectX SDK такая инициализация всё равно производится. Выполнять инициализацию библиотеки COM или нет, остаётся на усмотрение программиста.
После успешной инициализации библиотеки COM, функция CoCreateInstance создаёт неинициализированный компонент и получает интерфейс для работы с ним:

Прототип функции CoCreateInstance
STDAPI CoCreateInstance(
REFCLSID	rclsid,		// GUID компонента
LPUNKNOWN	pUnkOuter,		// Необходим для агрегирования
DWORD	dwClsContext,		// Контекст запуска
REFIID riid,			// GUID интерфейса
LPVOID	*	ppv		// Адрес указателя на интерфейс
);

Параметры функции CoCreateInstance:

Параметр Описание
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 Код, который создаёт объекты данного класса и управляет ими, представляет собой внутрипроцессный хэндлер ( дескриптор(external link) потока в ОС). По сути это та же 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 Запрещает использование стороннего маршалинга(external link).
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 (от англ. " маркер олицетворения(external link)") потока, если таковой имеется. Когда данный флаг не указан, или поток не имеет маркера олицетворения, для активации запроса, созданного потоком, 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(external link) (DLL с серверной заглушкой). Данный флаг зарезервирован для служебного пользования и не предназначен для использования при создании приложений.

Значения, возвращаемые функциями COM

Рис. 3. Структура значений, возвращаемых функциями и методами COM
Рис. 3. Структура значений, возвращаемых функциями и методами COM
  • Обычно изучаются для того, чтобы понять каким образом COM сигнализирует об ошибках.
  • Как правило, тип возвращаемого значения COM-функций - HRESULT (32-битное знаковое значение).


В случае успешного завершения работы функции возвращается S_OK.
В случае неудачи возвращается E-FAIL.
Структура возвращаемого значения указана на Рис. 3.
Возвращаемое значение состоит из трёх участков. Младшие 16 бит содержат собственно код возврата и полностью определяются производителем компонента. Следующие 15 бит являются кодом средства, сгенерировавшего возвращаемое значение. Компоненты DirectX определяют собственные коды средства для каждого из них. Например для DirectSound:

...
#define _FACDS 0x878
...

Последний 31-й бит возвращаемого значения содержит флаг критичности, который указывает, произошла ошибка или нет. Если этот бит равен 0, то ошибки не было, а если он равен 1, то произошла ошибка. Так как 31-й бит является знаковым, то наиболее простой способ определить, произошла ошибка или нет - это сравнить возвращаемое значение с нулём: отрицательное возвращаемое значение всегда означает наличие ошибки.
Такими сравнениями как раз и занимаются макросы, определённые в WINERROR.H:

Фрагмент файла 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(100)
...

Здесь определяется единственный код успешного завершения, а все остальные строятся из расчёта, что они неуспешны (в макрос MAKE_HRESULT передаётся значение 1 для 31-го бита).

===

ИСТОЧНИКИ:

  1. Adams J. "Programming Role Playing Games with DirectX, 2nd Ed.", 2004. Premier Press

Contributors to this page: slymentat .
Последнее изменение страницы Вторник 29 / Ноябрь, 2016 13:01:34 MSK автор slymentat.

Помочь проекту

Яндекс-деньги: 410011791055108