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

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

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

Добавлен: 25.06.2023

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

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

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
  1. СОМ-объект поддерживает несколько интерфейсов.
  2. Класс в СОМ рассматривается как описание конкретной реализации набора интерфейсов.
  3. СОМ-объект поддерживает только наследование интерфейса, т.е. потомок самостоятельно определяет код методов родителя.[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 __stdcall QueryInterface(const IID& iid, void** ppv)=0; 
// запрос интерфейса 
virtual ULONG __stdcall AddRef()=0; // увеличение счетчика ссылок 
virtual ULONG __stdcall Release()=0; // уменьшение счетчика ссылок 
};

GUID (Globally Unique IDentifier) или UUID (Universally Unique Identifiers) - это величина размером 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 __stdcall QueryInterface(const IID& iid, void** ppv); 
virtual ULONG __stdcall AddRef() {return 0;}; // пока ничего не делает 
virtual ULONG __stdcall Release() {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() 

HRESULT hr; 
// Создание объекта 
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]

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

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

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


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

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

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

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

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

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