Добавлен: 23.05.2023
Просмотров: 175
Скачиваний: 3
СОДЕРЖАНИЕ
Глава 1. История технологии СОМ
Краткий обзор концепций программирования
1.2 Предпосылки возникновения COM
1.4 Составляющие приложений COM
Глава 2. Реализация технологии СОМ
2.1 Способы реализации СОМ серверов
2.2 Пример реализации и использования COM класса в С++
- по соглашению, имя интерфейса должен начинаться с заглавной буквы I, например, IMalloc или IPersist;
- интерфейс имеет гарантированный уникальный идентификатор GUID (Globally Unique Identifier), который представляет собой 128-битное случайное число. Эти идентификаторы называют Interface Identifiers (IIDs). Использование таких идентификаторов ограничивает возможность конфликтов между разными версиями или продуктами;
- интерфейсы обладают языковой независимостью. Для реализации интерфейса может быть использован любой язык программирования, который поддерживает указатели и структуры и позволяет вызвать функцию по указателю на нее;
- сами по себе интерфейсы не являются объектами, а предоставляют способ получения доступ к объекту, а клиент получает доступ к данным объекта посредством вызова функций;
- любой интерфейс является наследником базового интерфейса IUnknown;
- обращения к интерфейсам могут перенаправляться между потоками, процессами и сетевыми компьютерами невидимо для клиента.
Базовый интерфейс IUnknown –‘тот интерфейс, который должны поддерживать все COM объекты, включает следующие подпрограммы:
1) QueryInterface;
2)AddRef;
3) Release.
Метод QueryInterface, объявленный как
Function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
возвращает клиенту указатель на запрошенный интерфейс IID. С помощью полученного указателя клиент может вызвать любой из реализованных методов интерфейса. В качестве IID клиент может указать тип класса интерфейс – в этом случае компилятор самостоятельно извлекает соответствующий GUID.
Методы AddRef и Release используются для того, чтобы объект COM мог самостоятельно отслеживать продолжительность своего существования. Эти методы просто изменяют число ссылок на объект. Когда число ссылок на объект становится равным нулю, объект удаляется из памяти COM сервером.
Сервер COM является приложением или библиотекой, которая предоставляет сервис клиентскому приложению (или библиотеке). Сервер включает по крайней мере один объект COM, который в свою очередь представляет собой совокупность методов и свойств. Клиент не обязан знать, где в памяти располагаются объекты COM.
Когда клиент запрашивает сервис у объекта COM, он (клиент) должен передать идентификатор класса CLSID (class identifier). Идентификатор класса CLSID создается на основе GUID интерфейса объекта COM.
По идентификатору класса CLSID COM определяет соответствующий сервер, загружает его в память и сервер создает экземпляр объекта COM. Экземпляры объектов COM создает фабрика классов (class factory), к которой обращается сервер. Фабрика классов имеет свой интерфейс IClassFactory.
COM объект является экземпляром класса CoClass, в котором реализованы один или более интерфейсов COM. Объект COM обеспечивает те сервисы, которые определены в интерфейсах класса CoClass.
Экземпляры класса CoClass создаются специальным типом объекта, который называется фабрикой класса. Когда клиент обращается к COM объекту, фабрика класса создает экземпляр объекта и регистрирует экземпляр объекта для этого конкретного клиента. Если в это время другой клиент обращается к объекту, фабрика классов для него также создает (другой) экземпляр объекта.
Любой класс CoClass должен иметь фабрику классов и идентификатор класса CLSID, так что экземпляр COM объекта этого класса может быть создан извне, т.е. из другого модуля. Благодаря наличию у классов CoClass уникального идентификатора CLSID, они могут быть обновлены в любое время, как только для класса разработан новый интерфейс. Новый интерфейс может использовать модифицированные или новые методы и это не оказывает никакого влияния на прежние версии. В случае использования обычных библиотек DLL подобная ситуация прежде была типичной проблемой.
Мастера Delphi избавляют программиста от необходимости заниматься созданием объектов, которые обеспечивают функционирование фабрики классов.
Таким образом, COM - многообещающая и перспективная технология, находящаяся в русле общего направления развития программных технологий. С помощью COM можно разрабатывать программы и программные комплексы разного масштаба, функциональности и назначения. COM - социально-ориентированная технология.
Технология COM - сложная технология. Сложная как в концепции, так и в реализации. Она - намного сложнее, чем технология С++, реализуемая компилятором. Эта сложность не должна пугать - к настоящему времени разработано достаточное количество инструментов и средств разработки, которые позволяют "легко писать" и "легко использовать" COM-компоненты. Но эта сложность есть. На её преодоление тратятся ресурсы компьютера во время выполнения, а слабо понимающий концепции COM программист, несмотря на все эти редакторы, не сможет создать что-то приемлемо работающее, большее, чем примитивный пример. К счастью, это стандартная проблема - умение программировать есть не умение писать код, а только умение мыслить соответствующими категориями и конструкциями. И на VB можно написать плохо работающую программу, хотя на C++ сделать это значительно легче. Никакая технология не в состоянии предложить приемлемого решения этой проблемы - технические средства могут только избавить от рутины, но не от необходимости думать и понимать.
Глава 2. Реализация технологии СОМ
2.1 Способы реализации СОМ серверов
При работе с объектами COM клиент не знает, где находится объект. Он просто обращается к объекту посредством его интерфейса. Далее библиотека COM выполняет все необходимые шаги для удовлетворения вызова клиента.
Эти шаги зависят от конкретного местонахождения объекта COM: в том же процессе, что и клиент, в другом процессе на машине клиента или на другой машине в сети. В зависимости от этого различают три вида серверов COM.
Сервер в клиентском процессе (In-process server). Это библиотека DLL, которая выполняется в адресном пространстве процесса клиента. Например, элемент ActiveX, внедренный в Web страницу, выполняется в Internet Explorer или другом браузере. Это значит, что объект ActiveX загружается на машину клиента и выполняется в том же процессе, что и Web браузер. Клиент обращается к объекту COM путем прямого вызова интерфейса COM.
Рис. 2. Сервер в клиентском процессе [10]
Локальный сервер (local server). Он представляет собой другое приложение (файл *.exe), которое выполняется в другом адресном пространстве, но на том же компьютере, что и клиентское приложение. Например, обработка таблицы Excell, внедренной в документ Word, выполняется приложением Excell. Локальный сервер связывается с клиентом посредством COM библиотек.
Рис.3. Локальный сервер [10]
Когда объект COM принадлежит другому процессу на том же компьютере, что и клиентское приложение, или вовсе на другом компьютере (в сети), COM использует так называемого "заместителя" (proxy) для того, чтобы инициировать удаленный вызов процедуры (remote procedure call – RPC). Так как заместитель находится в клиентском процессе, то с точки зрения клиента обращения к интерфейсам выглядят так же, как и для случая размещения сервера в клиентском процессе. Заместитель перехватывает вызовы клиента и отправляет их туда, где находится сервер. Механизм, который позволяет клиенту получить доступ к объекту, находящемуся в другом процессе или на другом компьютере (невидимо для клиента), называется маршалингом или маршализацией.
Рис. 4. Маршализация [10]
Удаленный сервер (remote server). Он представляет собой библиотеку (DLL или OCX) или другое приложение, которые выполняются на другом компьютере, а не на машине клиента. Например, клиентское приложение, использующее базу данных, связывается с приложением, выполняемым на другом компьютере в сети. В этом случае удаленный сервер использует интерфейсы DCOM.
Рис. Удаленный сервер
Различие между локальным и удаленным сервером состоит в применяемом способе (и инструментальных средствах) взаимодействия клиента и сервера: в первом случае используется COM, а во втором – DCOM.
Если сервер реализован как библиотека (а библиотека не может выполняться как самостоятельное приложение), то COM создает специальное приложение-суррогат (surrogate), в рамках которого и выполняется библиотека.
Маршалинг. Механизм маршалинга позволяет клиентскому приложению делать вызовы методов интерфейса для объекта COM, который находится в другом процессе или на другом компьютере.
При любом вызове функций посредством интерфейса клиентское приложение помещает фактические параметры функции в стек и выполняет ее вызов. Если функция не находится в клиентском процессе, то вызов передается заместителю. Заместитель упаковывает параметры в пакет и передает их удаленному объекту. Заглушка COM объекта распаковывает пакет, помещает аргументы в стек и вызывает объект COM. Таким образом объект COM обрабатывает запрос клиента в своем собственном адресном пространстве.
Какой именно маршалинг будет реализован, зависит от реализации COM объекта. Стандартный механизм реализован интерфейсом IDispatch. Кроме того, объекты могут самостоятельно выполнять маршалинг, однако это довольно сложно.
Отметим, что технология Microsoft Transaction Server (MTS) обеспечивает дополнительную поддержку для удаленных объектов.
2.2 Пример реализации и использования COM класса в С++
В этом подразделе рассматривается весь процесс создания локального СОМ объекта, реализованного непосредственно в приложении.
Описание класса IUnknown в файле ..\Vc98\Include\Unknwn.h:
struct IUnknown
{
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)=0 ;
virtual ULONG STDMETHODCALLTYPE AddRef( void) =0;
virtual ULONG STDMETHODCALLTYPE Release( void) =0;
};
Смысл использованных макросов:
HRESULT – long;
STDMETHODCALLTYPE – _stdcall
REFIID – const GUID &
В файле Unknwn.h можно найти также много других описаний, определяющих структуру таблицы vtable, заголовки функций для вызова proxy и stub и т.д. [12]
Описание (базового) класса IAccount – наследника IUnknown – как абстрактного класса:
class IAccount : public IUnknown
{
public:
// IAccount methods
STDMETHOD(GetBalance)(int* pBalance) = 0;
STDMETHOD(Deposit)(int amount) = 0;
};
8. Здесь STDMETHOD – virtual HRESULT _stdcall.
Описание (конкретного) класса СAccount:
class CAccount : public IAccount
{
public:
CAccount() // конструктор
{
m_nRef = 0;
m_nBalance = 100;
}
// методы IUnknown
STDMETHOD(QueryInterface)(REFIID, void**);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
// IAccount methods
STDMETHOD(GetBalance)(int* pBalance);
STDMETHOD(Deposit)(int amount);
protected:
ULONG m_nRef; // число ссылок
int m_nBalance; // счет в банке
};
Использованные макросы:
9. REFIID – GUID (структура из 16 байт);
10. STDMETHODIMP – HRESULT _stdcall.
11.
Реализация методов класса CAccount:
STDMETHODIMP
CAccount::QueryInterface(REFIID iid, void** ppv)
{
if (iid == IID_IUnknown)
*ppv = (IAccount*) this;
else if (iid == IID_IAccount)
*ppv = (IAccount*) this;
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
Метод QueryInterface в конечном итоге возвращает ссылку на таблицу vtable, которая создается компилятором С++ автоматически, если в классе определены виртуальные функции.
STDMETHODIMP_(ULONG) CAccount :: AddRef()
{
return ++m_nRef;
}
STDMETHODIMP_(ULONG) CAccount :: Release()
{
if(--m_nRef == 0)
{
delete this; // уничтожение экземпляра класса
// Trace("Object destroyed", "CAccount");
return 0;
}
return m_nRef;
}
STDMETHODIMP CAccount :: Deposit(int amount)
{
m_nBalance += amount;
return S_OK;
}
STDMETHODIMP CAccount::GetBalance(int* pBalance)
{
*pBalance = m_nBalance;
return S_OK;
}
Для практического использования класса CAccount требуется описать экземпляр класса IAccount:
IAccount * m_pAccount=NULL;
и получить ссылку на интерфейс. Эту задачу решает (обычная) функция CreateAccount.
BOOL CreateAccount(IAccount ** ppAccount)
{
HRESULT hr;
if (ppAccount == NULL)
return FALSE;
// Create object
CAccount* pAccount = new CAccount;
if (pAccount == NULL)
return FALSE;
// получить интерфейс. При этом в QueryInterface вызывается метод AddRef
hr = pAccount->QueryInterface(IID_IAccount, (void**) ppAccount);
if (SUCCEEDED(hr))
{
Trace("Object created", "CAccount");
return TRUE;
}
else
{
delete pAccount;
return FALSE;
}
}
Идентификатор интерфейса можно получить с помощью GuidGen и описать его как константу, например:
static const IID IID_IDisplay =
{ 0x5723b700, 0x2878, 0x11d1, { 0xa0, 0x1b, 0x0, 0xa0, 0x24, 0xd0, 0x66, 0x32 } };
Идентификатор для интерфейса IUnknown предопределен.
Невзирая на то, что в функции CreateAccount указатель pAccount на экземпляр класса создается локально, объект может быть разрушен, так как ссылка на него сохраняется в параметре ppAccount. Экземпляр СОМ класса уничтожается в методе Release.