Файл: Технология COM (Принцип работы технологии COM).pdf

ВУЗ: Не указан

Категория: Курсовая работа

Дисциплина: Не указана

Добавлен: 18.06.2023

Просмотров: 62

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

СОМ-объект поддерживает несколько интерфейсов.

Класс в СОМ рассматривается как описание конкретной реализации набора интерфейсов.

СОМ-объект поддерживает только наследование интерфейса, т.е. потомок самостоятельно определяет код методов родителя.[6]

OLE (Object Linking and Embedding) - это технология для создания составного документа путем связывания и внедрения. Документ сервера являетсясвязанным либо внедреннным в документ контейнера. Метод создания составного документа: копирование и вставка с помощью буфера обмена или методаdrag-and-drop.

Контейнер составного документа должен поддерживать следующие интерфейсы: IOleClientSite (обращение сервера к своему контейнеру) и IAdviseSink (использование сервера для информирования контейнера о происходящих с ним действиях).[11]

ActiveX - это технология создания приложения на основе СОМ. Спецификация управляющего элементаActiveX определяет четыре аспекта для их функционирования: обеспечение интерфейса пользователя, обеспечение вызова метода управляющего элемента контейнером, отправка событий контейнеру, получение данных о свойствах среды контейнера и предоставление доступа к свойствам управляющих элементов и их модификация.[12]

Интерфейс СОМ используется для связи между несколькими компонентами и содержит в себе набор функций, реализуемых компонентами и используемых клиентами. Свойства СОМ-интерфейса следующие: у каждого интерфейса имеется два идентификатора (пользовательский идентификатор и глобальный уникальный идентификатор); интерфейсы после своего опубликования не могут быть изменены; добавление новой функциональности требует определения нового интерфейса; для интерфейса определен стандартный двоичный формат; каждый интерфейс наследует стандартный интерфейс IUnknown; СОМ-объект может описывать свои интерфейсы на любом языке.[12]

Назначение интерфейса Iunknown заключается в следующем - запрос указателя на иной интерфейс и отслеживание количества клиентов СОМ-объекта. Описание данного интерфейса Iunkown выглядит данным образом:

#define interface struct 
interface IUnknown 

virtual HRESULT__stdcallQueryInterface(const IID& iid, void** ppv)=0; 
// запрос интерфейса 
virtual ULONG __stdcallAddRef()=0; // увеличение счетчика ссылок 
virtual ULONG __stdcallRelease()=0; // уменьшение счетчика ссылок 
};

GUID(GloballyUniqueIDentifier) или UUID(UniversallyUniqueIdentifiers) - это величина размером 16 байт, которая генерируется специальными программными утилитами UUIDGEN.EXE или GUIDGEN.EXE. Каждый GUID является уникальным во времени и пространстве. Он содержит в себе время (60 бит - число 100-наносекундных интервалов, прошедших с 00:00:00:00 15 октября 1582 года) и адрес сетевой платы (48 бит). Например:[11]


// {B32093E0-CC12-11d2-BD11-00AA00264239} 
static const IID IID_IA = 
{ 0xb32093e0, 0xcc12, 0x11d2, { 0xbd, 0x11, 0x0, 0xaa, 0x0, 0x26, 0x42, 0x39 } };

Описание рассмотренного интерфейса на С++ выглядит следующим образом:

interface IА : IUnknown 

virtual void __stdcall Fx() =0; 
};

Ключевое слово _stdcallпоказывает, что функции с фиксированным числом аргументов в СОМ-интерфейсах применяет вызов в стиле Pascal.[11]

В IUnknownвходит функция - QueryInterface, спомощью которой клиент может определить, поддерживается интерфейс или нет. QueryInterfaceперемещает указатель на интерфейс в том, если его поддерживает компонент; в ином случае возвращается код ошибки (клиентымогутзапрашивать указатели на разные интерфейсы или аккуратно выгружать компоненты).

УQueryInterface имеетсядвапараметра:

Virtual HRESULT__stdcall QueryInterface(const IID& iid, void** ppv);

Первый параметр - это идентификатор интерфейса, которая называется IID-структура.

Второй параметр - это адрес, с помощью которогоQueryInterface помещает указатель на нужныйинтерфейс.[11]

QueryInterface возвращает HRESULT (32-разрядный код результата, записанный в определенном формате). QueryInterfaceвозвращает либо S_OK, либо E_NOINTERFACE. Клиенту не нужно сравнивать возвращаемое QueryInterface значение с рассмотренными константами; в этом случае для проверки необходимоприменять макросы SUCCEEDED или FAILED.

Допустим, что имеется указатель на IUnknown, pI. Для определения того, можно ли применять некоторый интерфейс, нужно вызватьQueryInterface, при этом передав ей идентификатор требуемого интерфейса. Если QueryInterfaceвыполнилась успешно, томожноиспользовать указатель:[12]

void foo(IUnknown* pI) 

// Определитьуказательнаинтерфейс IX* pIX = NULL; 
// Запроситьинтерфейс IX 
HRESULT hr = pI->QueryInterface(IID_IX, (void**)&pIX); 
// Проверитьзначениерезультата if (SUCCEEDED(hr)) 

// Использоватьинтерфейс pIX->Fx(); 

}

В данном фрагменте программного кода запрашивается у pI интерфейс, который идентифицируется с помощью IID_IX. Описание IID_IX находится в файле заголовка, который предоставляется компонентом (или извлекается из библиотеки типа). При неудачном запросеQueryInterface устанавливает возвращаемый указатель в значение NULL. Но, поскольку QueryInterfaceвыполняется программистом компонента, то в некоторых выполнениях это наверняка не сработает. Для безопасности следует устанавливать указатель в положение NULL самостоятельно. Ниже показан пример использованияQueryInterface:[11]

HRESULT__stdcall QueryInterface(const IID& iid, void** ppv) 

if (iid == IID_IUnknown) 
{ // КлиентзапрашиваетинтерфейсIUnknown. 
*ppv = (*IX)this; 

else if (iid == IID_IX) 
{ // Клиентзапрашиваетинтерфейс IX 
*ppv = (*IX)this; 

else if (iid == IID_IY) 
{ // Клиентзапрашиваетинтерфейс IY 
*ppv = (*IY)this; 

else 
{ // Нетдругихинтерфейсов 
*ppv=NULL; 
return E_NOINTERFACE; 

(IUnknown*)(*ppv)->AddRef(); // Увеличениесчетчикассылок 
return S_OK; 
}


QueryInterface и другие функции COM возвращают значение HRESULT. HRESULT - это 32-разрядное число, которое разбито на три секции: источник ошибки (биты с 16 по 30), код ошибки (биты с 0 по 15) и признак ошибки (31 бит, 0 означает, что ошибки нет).[12]

Бывают следующие коды возврата:

- S_OK или NOERROR - функция отработала успешно, значение 0

- S_FALSE - функция отработала успешно, значение 1

- E_UNEXPECTED - неожиданная ошибка

- E_NOTIMPL - функция не реализована

- E_NOINTERFACE - объект не поддерживает интерфейс

- E_OUTOFMEMORY - объект не может выделить память

- E_FAIL - ошибка по неуказанной причине

А источники ошибки такие:

- FACLITY_WINDOWS 8

- FACLITY_STORAGE 3

- FACLITY_SSPI 9

- FACLITY_RPC 1

- FACLITY_Win32 7

- FACLITY_CONTROL 10

- FACLITY_DISPATCH 2

МакросыSTDMETHODиSTDMETHOD_ используютсяприобъявленииметодаинтерфейса, амакросыSTDMETHODIMPиSTDMETHODIMP_ используются приописаниивыполненияметода.[6,11]

Вместо 
virtual HRESULT__stdcall QueryInterface(const IID& iid, void** ppv); 
можнозаписать 
STDMETHOD(QueryInterface(const IID& iid, void** ppv));

Вместо 
virtual void __stdcall Fy() =0; 
можнозаписать 
STDMETHOD_(void, Fy()) PURE;

Приописанииреализациивместо 
virtual void __stdcall Fy() { cout<< "Fy" << end1;} 
можнозаписать 
STDMETHODIMP_(void) Fy() { cout<< "Fy" << end1;}

Далее представлен пример COM-объекта на языке C++ и пример организации взаимодействия клиентов с СОМ-объектом.[12]

#include<iostream.h> 
#include<objbase.h> 
interface IX : IUnknown 

virtual void __stdcall Fx() =0; 
}; 
interface IY : IUnknown 

virtual void __stdcall Fy() =0; 
}; 
// класс СОМ -объекта 
class CA : public IX, public IY 

// Реализация методов IUnknown 
virtual HRESULT__stdcallQueryInterface(const IID& iid, void** ppv); 
virtual ULONG __stdcallAddRef() {return 0;}; // пока ничего не делает 
virtual ULONG __stdcallRelease() {return 0;}; // пока ничего не делает 
// Реализация интерфейса IX 
virtual void __stdcall Fx() { cout<< "Fx" <<end1;} 
// Реализация интерфейса IY 
virtual void __stdcall Fy() { cout<< "Fy" << end1;} 
};

Пример взаимодействия клиента с СОМ-объектом представлен ниже:

// Функция создания объекта 
IUnknown* CreateInstance() 

IUnknown * pI=(IX*)new CA; 
pI->AddRef(); 
return pI; 

void main() 

HRESULThr; 
// Создание объекта 
IUnknown* pIUnknown = CreateInstance(); 
IX* pIX=NULL; 
// Получить интерфейс IX 
hr=pIUnknown->QueryInterface(IID_IX, (void**)&pIX); 
if (SUCCEEDED(hr)) 
{ // вызвать метод Fx 
pIX->Fx(); 

else cout<< "Объект не имеет интерфейса " <<end1; 
IY* pIY=NULL; 
// Получить интерфейс IY 
hr=pIUnknown->QueryInterface(IID_IY, (void**)&pIY); 
if (SUCCEEDED(hr)) 
{ // вызвать метод Fy 
pIX->Fy(); 

else cout<< "Объект не имеет интерфейса " <<end1; 
// Получить интерфейс IY через интерфейс IX 
IY* pIY_IX =NULL; 
hr=pIX->QueryInterface(IID_IY, (void**)&pIY_IX); 
if (SUCCEEDED(hr)) 
{ // вызвать метод Fy 
pIY_IX->Fy(); 

else cout<< "Объект не имеет интерфейса " <<end1; 
// Получить интерфейс IUnknown через интерфейс IY 
IUnknown* pIUknown_IY =NULL; 
hr=pIY->QueryInterface(IID_IUnknown, (void**)&pIUnknown_IY); 
if (SUCCEEDED(hr)) 
if (pIUnknown_IY == pIUknown) 

cout<< "указатели совпадают " <<end1; 

else cout<< "указатели не совпадают " <<end1; 
// Удалить объект 
delete pIUnknown; 
}


Для того чтобы воспользоваться объектом СОМ, клиенты должны явно инициировать начало выполнения работы экземпляра данного объекта. Здесь появляется вопрос: «Когда оканчивается работа объекта?» Кажется, решение очевидное - возложение на клиента, который запустил объект на выполнение, обязанности сообщить объекту, когда емунеобходимо остановиться. Однако это решение не работает, потому что данный клиент может в последствии оказаться не один, ктоиспользует этот объект. Часто распространена практика, когда клиенты запускают выполнение объекта, получают указатель на его интерфейс и затем передают один из указателей другим клиентам. Последний может применять указатель для исполнения метода в том же объекте и в свою очередь также передать указатель другому клиенту. Если бы первый клиент мог «убивать» экземпляр объекта по своему желанию, то положение остальных клиентов было бы незавидным.[6]

В то время как один объект может пользоваться несколькими клиентами одновременно, никто из них не в состоянии узнать, когда все остальные завершатся. Так что разрешить клиенту убивать объект напрямую - небезопасно. Только сам объект может знать, когда он может безопасно завершить свою работу, и только в том случае, если все клиенты сообщают объекту, что они завершили работу с ним. Такой контроль объекты осуществляют с помощью механизма подсчета ссылок (referencecounting), поддерживаемого двумя методами интерфейса IDnknown.[11]

Каждый исполняющийся объект поддерживает счетчик ссылок. Всякий раз, выдав вовне указатель на один из своих интерфейсов, объект увеличивает счетчик ссылок на 1. Если один клиент передает указатель интерфейса другому клиенту, т.е. увеличивает число пользователей объекта без ведома последнего, то клиент, получающий указатель, должен вызвать с помощью этого указателя AddRef. В результате объект увеличивает свой счетчик ссылок. Независимо от того, как он получил указатель на интерфейс, клиент всегда обязан вызвать для этого указатель Release, закончив с ним работу. Исполнение этого метода объектом состоит в уменьшении числа ссылок на 1. Обычно объект уничтожает сам себя, когда счетчик ссылок становится равным 0.

Подсчет ссылок может вызывать проблемы. Если не все клиенты следуют правилам, то экземпляр объекта может либо существовать неопределенно долго, либо, что еще хуже, быть преждевременно удаленным. И все-таки подсчет ссылок выглядит единственным работающим способом управления временем жизни объектов в динамичной среде, которую позволяет создать СОМ.


Каждый объект СОМ реализован внутри некоторого сервера, содержащего код, который реализует методы интерфейсов объекта, а также контролирует данные объекта, пока тот активен. Один сервер может поддерживать более одного объекта некоторого класса. Рассмотрим три основные типа серверов.[12]

Сервер «в процессе»: объекты реализуются в динамически подключаемой библиотеке, и, таким образом, исполняются в том же процессе, что и клиент. Локальный сервер: объекты реализованы в отдельном процессе, исполняющемся на той же машине, что и клиент. Удаленный сервер: объекты реализованы в DLL либо в отдельном процессе, которые расположены на удаленном по отношению к клиенту компьютере. Возможность создания таких серверов поддерживает распределенная СОМ (DCOM).[12]

С точки зрения клиента, объекты, реализованные любой из трех разновидностей серверов, выглядят одинаково; доступ к методам объектов клиент по-прежнему осуществляет через указатели интерфейсов. При необходимости он может проводить различие между разными типами серверов, но это не обязательно. Запуск объекта, получение указателей на его интерфейсы, вызов их методов и освобождение указателей выполняются клиентом одинаково независимо от того, каким сервером реализован объект: «в процессе», локальным или удаленным.

В любой системе, поддерживающей СОМ, обязательно имеется некоторая реализация библиотеки СОМ. Эта библиотека содержит функции, предоставляющие базовые сервисы объектам и их клиентам. Но гораздо важнее то, что библиотека предоставляет клиентам способ запуска серверов объектов. Доступ к сервисам библиотеки СОМ осуществляется через вызовы обычных функций, а не методов интерфейсов СОМ-объектов. Обычно имена функций библиотеки СОМ начинаются с «Со» - например, CoCreateInstance.

Запрашивая создание объекта, клиент передает библиотеке СОМ идентификатор класса данного объекта, используя который, библиотека должна найти сервер этого класса. Здесь не обойтись без некоего системного реестра - таблицы, отображающей CLSID в местоположение исполняемого кода сервера. Классы всех объектов, которые будут создаваться на данной машине посредством библиотеки СОМ, должны быть зарегистрированы в такой базе данных.[6, 11]

СОМ реализована на разных системах, и точный формат системного реестра может быть разным. Microsoft Windows использует стандартную системную таблицу - она так и называется: Реестр. Другие реализации СОМ могут использовать другие схемы, которые, однако, должны включать: