
- •1)Связь внутри процесса
- •2)Локальная связь
- •3)Удаленный сервер
- •1.3.2. Виды маршалинга в модели сом
- •Кому выгодны облачные вычисления?
- •Экономия за счет масштаба: сопоставление крупных и средних цод
- •2.3.1. Протокол передачи файлов ftp
- •2.3.2. Файловая система nfs
- •2.4.1. Назначение и принципы организации службы каталогов
- •2.4.2. Служба каталогов nds
- •Объектно-ориентированный подход
- •Дерево каталогов
- •Имена и контексты
- •2.4.3. Средства защиты объектов в nds
- •2.5.1 Основные подходы к организации межсетевого взаимодействия
- •2.5.2. Трансляция
- •2.5.3. Мультиплексирование стеков протоколов
- •2.5.4. Инкапсуляция протоколов
- •2.9.1. Системы на базе X.400
- •2.9.2. Системы на базе smtp
- •2.9.3. Системы на основе частных стандартов
- •2.9.4. Гибридные системы (ms Exchange Server)
- •Службы мсвс
- •Домен мсвс
- •Гетерогенные домены
- •1.6.3. Программирование с управлением по сообщениям (событиям)
- •1.6.4. Библиотеки для разработки прикладных программ в X Window
- •Язык и интерпретатор Tcl/Tk
- •2.10.3. Языки и средства создания Web-приложений
- •Примеры телекоммуникационных сетей
- •2.8.4. Стандарты систем управления
Общие сведения о технологии СОМ.
СОМ – это способ организации взаимодействия между программными компонентами.
СОМ – это технология программирования, а также набор сервисных процедур, обеспечивающих взаимодействие компонентов во время выполнения программы. При соблюдении этой технологии любые два компонента будут взаимодействовать независимо от того, на каких машинах они выполняются (если машины связаны одна с другой), какие операционные системы на этих машинах и на каких языках написаны эти программные компоненты (объекты СОМ).
СОМ – это архитектура программного обеспечения, которая позволяет строить программы из готовых откомпилированных в двоичный код программных компонентов.
СОМ в полной мере использует такие свойства языка С++ как наследование, инкапсуляция и полиморфизм.
Главное, ради чего создавалась COM, - предоставить производителям программ возможность встраивать новые функциональные части в существующие приложения без перестраивания этих приложений. Зная как создавать COM объекты и интерфейсы, можно разрабатывать взаимозаменяемые компоненты.
Компоненты связаны друг с другом способом клиент – сервер. Компоненты COM должны быть спроектированы как взаимозаменяемые вставки (plug-ins), независимо от того, является ли компонент COM локально подключаемой DLL или удаленно запускаемым сервером.
Различают три возможных взаимосвязи между клиентом и сервером:
1)связь внутри процесса;
2)локальная связь (между процессами на одной машине);
3)удаленная связь (между процессами на разных машинах).
1)Связь внутри процесса
Сервер загружается в ту же область памяти (процесс), что и клиент, которого он обслуживает. Клиент и сервер общаются напрямую через указатели интерфейса (см. рис 1.1) .
Это самая быстрая схема, но и наименее устойчивая к ошибкам. Если появляется ошибка в сервере, то разрушается весь процесс вместе с клиентом. Другая потенциальная проблема таких серверов – они всегда принимают контекст безопасности клиента, в который они загружаются.
2)Локальная связь
Клиент и сервер находятся на одной физической машине, но загружены в разные области памяти (процессы), каждый со своим контекстом безопасности.
На рис. 1.2 представлена локальная (внепроцессная) связь между клиентом и сервером.
При локальной связи клиент и сервер не связаны друг с другом непосредственно. Данные передаются между заглушками (stubs) и прокси-объектами (proxy – представитель). С точки зрения клиента "представитель" является объектом СОМ, и с ним можно обращаться так, как будто он расположен внутри процесса. LRPC (Lightweight Remote Procedure Call) – это протокол связи по типу удаленного вызова процедур.
3)Удаленный сервер
Это наиболее медленная из всех клиент – серверных связей (см. рис.1.3).
Удаленная связь устанавливается с помощью протокола распределенного СОМ (Distributed COM) – DCOM. Этот протокол использует удаленный вызов процедур (Remote Procedure Call – RPC) для передачи информации между машинами.
Рис.1.1. Связь внутри процесса (in-proc)
Модель СОМ обладает свойствами прозрачности, то есть программа клиента не изменяется в зависимости от физического расположения сервера. Клиент работает с прокси-объектом как с реальным объектом (сервером), как будто объект находится с ним в одном процессе. Аналогично СОМ-объект считает, что заглушка – это реальный клиент.
Рис.1.2. Локальная (out-of-process) связь
Рис.1.3. Удалённый клиент/сервер
Программирование на основе интерфейсов.
COM (Component Object Model) – модель компонентных объектов – это технология программирования на основе интерфейсов. Особенностью СОМ – программирования является отделение интерфейса от реализации.
Основная особенность программирования на основе интерфейсов: пользователи объекта могут получить доступ к функциональным возможностям объекта только через набор поддерживаемых интерфейсов, а не из самого экземпляра объекта. В разработках на базе СОМ клиенты никогда напрямую не создают экземпляры объектов. Вместо этого объекты создаются с помощью функций библиотеки СОМ. Эти библиотечные функции возвращают клиенту запрошенный интерфейс.
С точки зрения синтаксиса интерфейсы представляют собой набор чисто виртуальных функций. Чисто виртуальная функция не имеет никакой реализации. В С++ чисто виртуальные функции объявляются с ключевым словом virtual и приравниваются к нулю:
virtual void Car()= 0; //Чисто виртуальная функция.
Программирование на основе интерфейсов отличается от традиционного объектного тем, что интерфейсы никогда не содержат реализации методов, параметров или закрытых вспомогательных функций. Традиционные абстрактные базовые классы могут иметь все эти элементы наряду с набором виртуальных функций.
Класс, включающий в себя чисто виртуальные функции, называется абстрактным базовым классом. Абстрактный класс 1создается для того, чтобы описать свойства и методы, общие для всех его потомков. Нельзя создать экземпляр абстрактного базового класса, но можно определить указатель на него. Определение значения указателя происходит на этапе выполнения программы, а не при ее компиляции. Это позволяет объектным языкам применять позднее связывание объекта, чтобы определить на основе таблицы виртуальных функций, какую версию виртуальной функции вызвать. В этом заключается свойство виртуальности функций.
Если по каким-либо причинам в производном классе чисто виртуальная функция не определена, то этот класс тоже будет абстрактным. Попытка создать объект такого класса вызовет ошибку. Таким образом, абстрактный базовый класс навязывает определенный интерфейс всем производным из него классам.
Во многих отношениях интерфейс похож на открытый интерфейс (открытую часть класса С++), однако есть два очень существенных отличия: интерфейсы никогда не имеют реализации и никогда не определяют параметров состояния.
Единственный способ обмена информацией между клиентом и сервером в модели СОМ заключается в использовании интерфейсов. Интерфейс – это набор функций, описывающих функциональные возможности СОМ-объекта. Клиенты видят перед собой только интерфейсы. Клиенты получают доступ к функциональным возможностям СОМ-объекта с помощью указателя интерфейса.
Когда класс поддерживает функциональность, задаваемую интерфейсом, мы используем открытое наследование С++. Поддержка одиночного интерфейса выглядит как простой случай создания подкласса на основе классического наследования. Однако наследуются параметры и не наследуется реализация.
Когда класс определяет виртуальные функции, компилятор С++ по умолчанию вставляет в него скрытый компонент vPtr, указывающий на vTable объекта (см. рис.1.4). Эта таблица содержит указатели на адреса реализаций функций. Каждый поддерживаемый интерфейс снабжается своими vPtr/vTable, определяемыми классами. Вызов требуемого метода происходит на этапе выполнения программы по таблице виртуальных функций.
Интерфейс на самом деле – это указатель на vPtr, который указывает на таблицу vTable и на реализацию.
Программирование на основе интерфейсов четко различает интерфейс и соответствующую реализацию.
Рис.1.4. Связь vPtr/vTable для класса CEmployee
Стандартный способ расширения интерфейса – это присоединение нового интерфейса, производного от существующего (наследование интерфейсов). В отличие от классического наследования, мы не наследуем здесь ни параметров, ни методов. Здесь скорее речь идет просто о присоединении к существующему каркасу новых деталей.
interface Idraw2: public Idraw
interface Idraw3: public Idraw2
Здесь Idraw2 строится на основе Idraw, то есть Idraw2 является расширением Idraw. Idraw3, в свою очередь, является расширением Idraw2.
На диаграммах классы, поддерживающие множественные интерфейсы, обозначаются отрезком с кружком на конце. Каждому интерфейсу соответствует отдельный кружок. Прямоугольник со сглаженными углами схематично представляет реализацию интерфейсов.
Библиотека ATL для поддержки множественных интерфейсов использует множественное наследование, а библиотека MFC использует технику вложенных классов.
Каждый СОМ – интерфейс должен быть произведен от основного интерфейса IUnknown.
При этом используется ключевое слово interface. Оно определено в заголовочном файле <objbase.h>:
#define interface struct
При использовании слов interface или struct по умолчанию доступ считается открытым, при использовании class – доступ к элементам структуры – закрытый.
Интерфейс IUnknown является основой любой иерархии интерфейсов, а следовательно, и реализующих их классов. Он определяет три метода: QueryInterface(), AddRef() и Release().
Программист может использовать один из двух видов интерфейсов: пользовательский или стандартный.
Идентификаторы интерфейсов
Интерфейсы должны быть именованы.
Каждый СОМ-интерфейс помечается универсальным уникальным идентификатором GUID. Это 128 – битное число, которое генерируется обычно на основе уникального сетевого адреса и точного времени запроса.
Виды GUID:
IID – идентификаторы интерфейса СОМ;
CLSID – уникальный идентификатор СОМ-класса;
LIBID - уникальный идентификатор библиотеки типов;
AppID - уникальный идентификатор исполняемых файлов COM
GUID является структурой с идущими подряд полями следующих типов: long, short, short, char Data4[8].
Во многие библиотечные функции СОМ передаются GUID с помощью указателя. Для этого удобно использовать директивы препроцессора, описанные в <wtypes.h>:
#define REFGUID const GUID* const
#define REFIID const IID* const
#define REFCLSID const CLSID* const
В <objbase.h> определены функции сравнения двух GUID, например:
BOOL IsEqualCLSID (CLSID c1, CLSID c2);
Кроме того, библиотека СОМ имеет перегруженные операторы С++:
равенства (= =) и неравенства (! = ).
Примеры:
if (g1 = = g2) { ... }
if (g1 ! = g2) { ... }
Создание пользовательских GUID
Каждый стандартный СОМ – интерфейс уже снабжен уникальным IID. Если разрабатывается пользовательский интерфейс, программист использует утилиту guidgen.exe для создания новых GUID. Стандартно guidgen.exe находится в каталоге Program files\Microsoft Visual Studio\Common\Tools. Это диалоговая утилита, можно использовать также утилиту uuidgen.exe в командной строке DOS.
При работе с guidgen.exe надо использовать формат DEFINE_GUID. В программе используется макрос DEFINE_GUID, в который подставляется полученное число GUID, например:
<initguid.h> //Обязательно использовать.
...
// (A533DA30-D372-11D2-B8CF-0020781238D4)
D
EFINE_GUID
(IID_IEngine,
// префикс ___
// имя интерфейса___ 0xa533da30,0xda30,0x11d2,0xb8,0xcf,0x0,0x20,0x78,0x12,0x38,0xd4);
Для создания GUID можно использовать библиотечную функцию СОМ
CoCreateGuid (GUID* pguid);
Ей передается указатель на GUID, и она заполняет требуемые поля.
Интерфейс IUnknown.
Класс СОМ – это тип, определенный пользователем, как минимум содержит IUnknown. Класс СОМ также имеет название кокласс (coclass).
Объект Сом – это экземпляр кокласса.
Кокласс реализует интерфейсы.
IUnknown – это стандартный интерфейс СОМ, от которого производятся все остальные интерфейсы. Во всех случаях первые три метода интерфейса СОМ – это методы, унаследованные от IUnknown. Сокращенное определение из <unknown.h>:
interface IUnknown
{
virtual HRESULT QueryInterface(REFID riid, void** ppv) = 0;
virtual ULONG Addref ( ) = 0;
virtual ULONG Release ( ) = 0;
};
IUnknown – это набор чисто виртуальных функций.
Интерфейс IUnknown обеспечивает каждому объекту СОМ две возможности:
через QueryInterface() клиент получает указатель на любой интерфейс, реализованный в объекте, из другого имеющегося указателя интерфейса;
методы AddRef() и Release() позволяют управлять временем жизни объекта, то есть определять момент, когда объект можно выгружать из памяти. Это связано с тем, что отдельный объект в каждый момент времени может использоваться разными клиентами. Для этого используется счетчик обращений к объекту, который обнуляется в конструкторе класса. В начале работы с указателем интерфейса клиент увеличивает счетчик на 1 (функция AddRef), по окончании работы клиент вызывает Release, уменьшая счетчик на 1. Когда счетчик обнуляется, объект можно удалять.
Поскольку AddRef( ) и Release( ) это чисто виртуальные функции, то программист должен определить реализацию этих методов. В них реализуется счетчик вызовов, значение которого увеличивает AddRef( ) и уменьшает Release( ) (в конструкторе класса счетчик устанавливается в нуль).
Интерфейс Iunknown отвечает за получение ссылки на интерфейс и за цикл жизни объекта.
COM-интерфейс создается на основе IUnknown:
interface IDraw : public IUnknown
{
virtual void Draw() = 0;
}
При попытке вызова Draw в данном случае будет выдано сообщение об ошибке, так как отсутствует его реализация. Поэтому должна быть определена реализация метода Draw (с параметрами, если необходимо):
void Draw( . . . )
{
. . .
}
Правила вызова функций AddRef( ) и Release( )
Объект СОМ самостоятельно вызывает AddRef( ), когда он передает указатель интерфейса клиенту (обычно при вызове QueryInterface).
После получения указателя интерфейса клиент считает, что AddRef была вызвана объектом, и клиент по окончании работы с полученным указателем должен вызвать Release( ).
IDraw* pDraw = NULL;
if (GetIDraw(&pDraw))
// Метод возвращает указатель интерфейса.
{ // Успешно.
pDraw -> Draw( );
// Если нет других вызовов, то объект удаляется
pDraw -> Release( );
} .
Если клиент (или сервер) делает копию указателя интерфейса, клиент должен явно вызвать AddRef() для этой копии.
pIFace2 = pIFace1;
pIFace2 -> AddRef( );
Функции, принимающие интерфейсный указатель в качестве параметров, должны выполнять AddRef() и Release() для этого указателя:
HRESULT DrawMe(IDraw* pDrawObj)
{
pDrawObj -> AddRef( ); // Начало работы
pDrawObj -> Draw( );
// Окончание работы, освобождение объекта.
pDrawObj -> Release( );
}
Получение интерфейсных указателей с помощью метода QueryInterface( )
Метод QueryInterface( ) интерфейса IUnknown реализуется во всех коклассах и позволяет клиенту запрашивать интерфейсный указатель объекта.
Схема использования QueryInterface( ):
HRESULT имя_кокласса :: QueryInterface (
REFIID riid, void** ppv)
{
// ppv – адрес выходной переменной, которая принимает
// интерфейсный указатель, запрошенный в riid
// проверяем по очереди все интерфейсы объекта
if (riid == IID_IUnknown) // Это IUnknown ?
*ppv = (IUnknown* ) this; // Это IUnknown
else if (riid = = IID_IDraw) // Это IDraw ?
*ppv = (IDraw* ) this; // Это Idraw
// this – поскольку ссылается сам объект
if (*ppv) // Есть интерфейс ?
{
((IUnknown * ) (*ppv)) -> AddRef( ); // Да
return S_OK;
}
//Интерфейс не поддерживается
*ppv = NULL
return E_NOINTERFACE;
}
Все методы интерфейса СОМ должны по возможности возвращать 32-разрядную переменную типа HRESULT. Только AddRef и Release возвращают ULONG.
Рис. 1.5 иллюстрирует структуру поля HRESULT.
|
Выдавшее ошибку устройство |
Информация об ошибке |
31 16 15 0
Рис. 1.5 Структура поля HRESULT
Старший бит определяет успешность вызова. Макросы СОМ - FAILED и SUCCEEDED проверяют старший бит HRESULT и возвращают true или false. Тексты сообщений об ошибках содержатся в <winerror.h>. Сообщение об ошибке выводит функция FormatMessage( ).
Пример:
IDraw* pDraw = NULL;
HRESULT hr;
// pUnk – ранее полученный указатель интерфейса IUnknown
hr = pUnk -> QueryInterface(IID_IDraw,
(void**) &pDraw);
if (SUCCEEDED(hr)) // У объекта есть IDraw ?
pDraw -> Draw(); // Есть интерфейс IDraw
Макросы СОМ.
Для определения интерфейса используются следующие макросы СОМ.
Имеются два основных набора интерфейсных макросов СОМ (один - для определения интерфейса, другой – для кокласса, реализующего интерфейс). Они определены в <objbase.h>.
Для определения интерфейса используются следующие макросы СОМ.
DECLARE_INTERFACE_ (имя_интерфейса, имя_базового_интерфейса)
STDMETHOD (метод) (параметры) – используется при описании метода, возвращающего HRESULT, в скобках перечисляются параметры, если они есть.
STDMETHOD_ (тип, метод) (параметры) – используется при описании метода, возвращающего не HRESULT, а другой тип, который указывается в качестве первого элемента в скобках.
PURE – заменяет текст ' = 0 ' в определении чисто виртуальной функции.
Макросам STDMETHOD и STDMETHOD_, определяющим интерфейс, соответствуют макросы STDMETHODIMP и STDMETHODIMP_, которые реализуют интерфейс в коклассе (IMP – от слова implementation – реализация).
Пример определения интерфейса:
DECLARE_INTERFACE_ (IEngine, IUnknown)
{
STDMETHOD (SpeedUp) ( ) PURE;
STDMETHOD (GetMaxSpeed) (int* max_Speed) PURE;
STDMETHOD (GetCurSpeed) (int* max_Speed) PURE;
};
Пример реализации интерфейса. В описании класса, реализующего этот интерфейс, соответствующая часть выглядит следующим образом:
...
STDMETHODIMP SpeedUp ( );
STDMETHODIMP GetMaxSpeed(int* max_Speed);
STDMETHODIMP GetCurSpeed(int* CurSpeed);
...
Это был фрагмент описания класса, который обычно помещается в заголовочный файл. Реализация функции, например GetCurSpeed(), может иметь вид:
STDMETHODIMP CoCar :: GetCurSpeed (
int* CurSpeed)
{
*curSpeed = m_currSpeed;
return S_OK;
}
Строки в модели СОМ
Особенности применения строк в СОМ связаны с тем, что, во-первых, СОМ – независимая от языка технология программирования, а строки в разных языках представляются не одинаково, и, во-вторых, СОМ может применяться на различных операционных системах, где строки тоже представляются различным образом.
Стандартные строковые типы СОМ: OLECHAR и BSTR.
Тип данных OLECHAR определяется в <wtypes.h> и применяется для клиентов на языках С/С++. В зависимости от платформы, OLECHAR:
это – wchar_t (Unicode) – если платформа Win32 и не задано преобразование OLE – ANSI;
это – char (ANSI) – в других случаях.
Если при создании текстовой константы указать префикс L, то строится строка Unicode, например, L "строка".
Используется также макрос OLESTR, преобразующий символы в строку Unicode в нешестнадцатиразрядных приложениях.
Тип данных BSTR – это строка Unicode фиксированной длины и с NULL на конце. Определение BSTR:
typedef OLECHAR* BSTR;
Методы, работающие с BSTR, имеют префикс Sys (system string).
Функции преобразования между строками ANSI и Unicode:
MultiByteToWideChar() и WideCharToMultiByte().
Это функции API для Win32. Библиотека языка С имеет эквивалентные функции: mbstowcs и wcstombs.
Создание объектов СОМ. Объект класса. Фабрика класса.
Речь идет о том, что клиент должен уметь создавать объекты внутри исполняемого сервера, причем сервер может располагаться территориально в любом месте (связь между клиентом и сервером СОМ может быть внутри процессная, локальная и удалённая).
Независимо от языка и местоположения сервера для создания объектов СОМ предоставляет стандартный интерфейс IClassFactory. Основным методом интерфейса IClassFactory является CreateInstance(), создающий требуемый кокласс от имени запрашивающего клиента. Второй интерфейс IClassFactory2, создающий объекты СОМ, обеспечивает лицензионную поддержку модели СОМ.
Новый термин СОМ – объект класса.
Объект класса – это объект СОМ, реализующий интерфейс IClassFactory (или IClassFactory2). Объекты класса называются также фабриками класса. Объекты класса существуют только для создания объектов СОМ другого типа. Если СОМ-совместимый язык имеет доступ к интерфейсу IClassFactory, то клиент получает возможность создать требуемый ему объект (экземпляр кокласса) в исполняемом сервере.
Объекты класса имеют взаимнооднозначное соответствие с объектами, которые они создают. Объекты класса не могут создавать набор объектов СОМ.
Фабрика класса создает соответствующий объект, далее СОМ запрашивает нужный интерфейс из объекта и возвращает его клиенту.
Фабрику класса можно рассматривать как аналог оператора new.
Метод IClassFactory:: CreateInstance()
Этот метод имеет 3 параметра:
первый параметр используется в связи с СОМ агрегацией, пока устанавливаем в NULL;
второй параметр - IID интерфейса, который клиент хочет получить из кокласса после его создания (это либо IID_Unknown, либо один из пользовательских интерфейсов);
третий параметр, void**, служит для размещения указателя интерфейса, вытащенного из кокласса.
Пример определения объекта класса (фабрики класса):
сlass CoCarClassFactory: public IClassFactory
{
public:
CoCarClassFactory( ); //конструктор
Virtual ~ CoCarClassFactory( ); //деструктор
//IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void* * pIFace);
STDMETHODIMP_ (ULONG) AddRef();
STDMETHODIMP_ (ULONG) Release();
//IClassFactory
STDMETHODIMP CreateInstance (LPUNKNOWN pUnkOuter, REFIID riid, void** ppv );
Private:
ULONG m_refCount; //обнуляется в конструкторе
};
Пример создания кокласса
Создадим объект CoCar и запросим нужный интерфейс.
Класс CoCarClassFactory – объект класса, используется для создания объекта кокласса CoCar. Метод CreateInstance принадлежит интерфейсу IClassFactory (классу CoCarClassFactory).
STDMETHODIMP CoCarClassFactory :: CreateInstance (LPUNKNOWN pUnkOuter, REFIID
riid, void** ppv );
{
. . .
CoCar pCarObj = NULL;
HRESULT hr;
pCarObj = new CoCar //Создание объекта
hr = pCarObj -> QueryInterface (riid, ppv); //Интерфейс
if (Failed (hr) //Ошибка
delete pCarObj;
return hr;
}
Метод LockServer интерфейса IClassFactory даёт клиенту возможность удерживать сервер в памяти, даже если в данный момент в сервере отсутствуют активные объекты. Делается это в целях оптимизации клиента.
Каждый кокласс, входящий в сервер, оснащается уникальным CLSID; причём сама фабрика класса не получает CLSID. Каждый СОМ-объект также должен иметь уникальный идентификатор.
Для создания фабрики класса используется метод DllGetClassObject. Он возвращает клиенту указатель интерфейса IClassFactory.
Пример создания фабрики класса
Здесь создается объект класса (фабрика класса) CoCarClassFactory,который будет строить экземпляры класса CoCar.
STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, void** ppv)
{
HRESULT hr;
CoCarClassFactory* pCFact = NULL;
if (rclsid != CLSID_CoCar)
return CLASS_E_CLASSNOTAVAILABLE;
// rclsid – определяет, какой класс создать
pCfact = new CoCarClassFactory; //Создание фабрики класса
hr = pCFact -> QueryInterface(riid, ppv); // Интерфейс
. . .
return hr;
}
Метод DllCanUnloadNow определяет, можно ли выгружать данный DLL-сервер из памяти. В отношении сервера ведётся два счётчика:
число активных объектов сервера на данный момент определяется конструктором и деструктором объекта СОМ;
число блокировок, определяемых методом IClassFactory :: LockServer.
Таким образом, DLL, содержащая сервер, может быть выгружена из памяти процедурой СОМ Runtime только, если сервер не блокирован в памяти и нет активных объектов.
Два последних метода (функции) являются элементами библиотеки DLL. Стандартно они описываются с помощью DEF-файла, который должен быть включён в проект. Имя библиотеки совпадает с именем рабочей области проекта.
LIBRARY “CARSERVER”
EXPORTS
DllGetClassObject @1 PRIVATE
DllCanUnloadNow @2 PRIVATE
Разработка клиента СОМ
Выше рассматривалась разработка внутрипроцессного СОМ-сервера. Теперь рассмотрим библиотечные вызовы СОМ для доступа к этому СОМ-серверу. Вне зависимости от языка программирования на глубинном уровне в клиенте и в сервере используется одна и та же последовательность вызовов. Вызываемые функции СОМ содержатся в модуле ole32.dll.
Взаимодействие этих функций иллюстрирует рис. 1.6.
Первой функцией в программе клиента СОМ должна быть CoInitialize(NULL). По окончании обязательно вызвать CoUninitialize() для выполнения очистки.
Далее следует загрузить сервер, создать новый объект СОМ и получить указатель IClassFactory созданной фабрики класса.
Рис.1.6. SCM, ole32.dll, клиент и сервер
Для этого используется функция CoGetClassObject с параметрами:
1) REFCLSID rclsid – это CLSID создаваемого кокласса, например, СLSID_CoCar. По информации, ранее записанной в системный реестр, SCM отыскивает путь к исполняемому файлу и загружает сервер;
DWORD dwClsContext – контекст класса сервера, позволяет определить, с каким сервером желаем работать:
CLSCTX_INPROC_SERVER – внутрипроцессный;
CLSCTX_LOCAL_SERVER – локальный;
CLSCTX_REMOTE_SERVER – удалённый;
CLSCTX_SERVER – SCM находит ближайший сервер.
COSERVERINFO pServerInfo – информация об удалённой машине (имя машины, параметры безопасности доступа); NULL, если сервер не удалённый;
REFIID riid – IID запрашиваемого интерфейса, здесь IID_IСlassFactory;
LPVOID** ppv – указатель на интерфейс, запрошенный в riid, например,
(void**) &pICF.
Это был последний параметр в CoGetClassObject().
Пример.
Получаем указатель интерфейса фабрики класса.
hr = CoGetClassObject(CLSID_CoCar,
CLSCTX_INPROC_SERVER, NULL,
IID_IClassFactory, (void**) &pICF);
Используем этот указатель на фабрику класса для создания экземпляра кокласса.
hr = pICF ->CreateInstance (NULL, IID_IСreateCar, (void* *) &pICreateCar);
Итак, создан объект CoCar и получен указатель на интерфейс IСreateCar. Теперь можно выполнять методы этого интерфейса и запрашивать другие интерфейсы.
По завершении работы важно не забывать вызывать Release() для каждого полученного указателя интерфейса.
Если требуется только один экземпляр кокласса, то можно использовать метод СоCreateInstance(). Метод СоCreateInstance() находит объект класса и автоматически вызывает CreateInstance() из указателя IСlassFactory. От программиста требуется передать нужные CLSID и IID. При использовании СоCreateInstance() объект каждый раз создаётся и каждый раз удаляется.
Язык определения интерфейса MIDL.
MIDL – (Microsoft Interface Definition Language) является расширением существующего IDL для поддержки СОМ.
Описание СОМ-сервера помещается в файл *.idl и обрабатывается компилятором midl.exe. Использование MIDL обеспечивает языковую независимость и прозрачность местонахождения сервера. Файл *.idl создаётся в среде Visual C++ и автоматически компилируется при сборке проекта.
Основные типы данных IDL (см. табл. 1.1) похожи на их аналоги в языке С. Это язык определений. В нём отсутствуют циклы и условные операторы.
Таблица 1.1
Ключевые слова MIDL
typedef |
Аналогично слову typedef языка С. |
import |
Вносит существующие IDL-определения в IDL-файл. Аналогично директиве #include языка С. Пример: import “ouidl.idl” // включает интерфейсы автоматизации. |
object |
Определяет, что далее следуют определения, относящиеся к СОМ. |
interface |
Объявление нового СОМ-интерфейса. |
cpp-quote |
Вставка строки в программу на С\С++, генерируемую компилятором MIDL (это строка - комментарий). |
uuid |
Универсальный уникальный идентификатор (то же, что и GUID). |
helpstring |
Вставка строки комментария. |
enum, vl_enum |
Создание перечисления, vl_enum означает 32-битные перечисления |
library |
Оператор библиотеки типов с обязательным атрибутом uuid определяет, какие коклассы содержатся в сервере (ЕХЕ или DLL - библиотеке). |
importlib |
Импорт в откомпилированную библиотеку. Должна, как минимум, импортироваться стандартная библиотека типов stdole32.tlb. |
[in] |
Атрибут параметра метода, передаваемого от клиента к серверу (параметр, задаваемый по умолчанию). |
[out] |
Атрибут параметра метода, передаваемого от сервера к клиенту. Клиент освобождает память полученных данных. Параметр [out] – это всегда указатель. |
[in, out] |
Посылка от клиента к серверу. Клиент отводит и освобождает память, но сервер тоже может управлять памятью при вызове метода. |
coclass |
Объявление кокласса в библиотеке, uuid – обязателен. |
default |
Определение интерфейса по умолчанию. Каждый кокласс должен иметь ровно один интерфейс по умолчанию. Используется для работы с С\С++. |
Пример определения интерфейсов сервера CarServer средствами IDL.
import “oaidl.idl”
...
// ICreateCar
[object, uuid (...), // IID
helpstring (“Create a car”)]
interface ICreateCar: IUnknown
{
HRESULT SetPetName ([in] BSTR petName);
HRESULT SetMaxSpeed ([in] int maxSp);
};
[uuid (...), //LIBID
helpstring (“CoCar Server with TypeLib”)]
library CarServer
{
importlib(“stdole32.tlb”); //должна быть //первой строкой в библиотеке
[uuid (...)] //CLSID
coclass CoCar
{
[default] interface ICreateCar;
interface IStats;
interface IEngine;
};
};
Файлы, генерируемые компилятором MIDL
Имена большинства генерируемых MIDL-файлов основаны на первоначальном имени IDL-файла. Для файла CarServer.idl, например, строятся следующие файлы.
CarServer.h – содержит определение интерфейса с использованием интерфейсных макросов СОМ.
CarServer_i.c – содержит все определения GUID. Для каждого атрибута [uuid] формируется соответствующая константа с префиксом IID_ , CLSID_ или LIBID_.
CarServer.tlb – двоичный эквивалент текста IDL, который называется библиотекой типов. Программы на других языках, кроме С\С++, могут использовать содержащиеся в них коклассы, интерфейсы и пользовательские типы.
CarServer_p.c и dlldata.c – используются для формирования dll, содержащего заглушки/прокси для удалённого доступа к СОМ-интерфейсам.
Для того чтобы другие языки могли получить информацию о типе сервера, надо ввести информацию о файле *.tlb в системный реестр под ключом HKEY_CLASSES_ROOT/TypeLib.
Организация обмена между клиентом и сервером (маршалинг).
В модели СОМ одна и та же программа пользователя используется для доступа к СОМ-объекту независимо от его физического расположения. СОМ создает иллюзию, что все вызовы методов интерфейсов происходят внутри процесса. Для этого во время выполнения используется DLL, содержащая объекты "заглушка" и "прокси".
Процедура обмена между клиентом и локальным или удаленным сервером посредством использования заглушек и прокси называется маршалингом. Коды заглушек и прокси генерируются для всех определенных на IDL интерфейсов. Маршалинг осуществляется с помощью механизма удаленного вызова процедур (RPC).
Прокси-объект – это СОМ-объект, загруженный в процесс клиента, который отправляет запросы клиента к методам интерфейсов.
Заглушка-объект – это СОМ-объект, загруженный в процесс сервера, который получает запросы клиента и передает «реальному» объекту.
Прокси – значит представитель. Это СОМ-объект, поддерживающий стандартные СОМ-интерфейсы. Прокси-объекты располагаются внутри процесса клиента и предоставляют клиенту локальные или удаленные интерфейсы.
Следующий посредник на пути между клиентом и сервером – объект канала ORPC (Object RPC). Объект канала поддерживает стандартный интерфейс IRpcChannelBuffer. Этот интерфейс разбивает на пакеты запросы клиента и посылает их заглушке, используя вызовы RPC низкого уровня.
Прокси-объект поддерживает стандартный интерфейс IRpcProxyBuffer. Этот интерфейс используется для установления или разрыва связи между прокси-объектом и объектом канала. IRpcProxyBuffer содержит два метода: Connect() и Disconnect(). Метод Connect() в качестве параметра принимает указатель интерфейса IRpcChannelBuffer.
Связь между клиентом, прокси и объектом канала иллюстрирует рис. 1.7. Прокси-объекты поддерживают IRpcProxyBuffer, позволяющий им связываться с объектом канала. Объект канала поддерживает IRpcChannelBuffer, который пакетирует запросы на предоставление метода.
Если клиент имеет интерфейсные указатели к десяти интерфейсам локального или удаленного объекта, то создается десять прокси-объектов, представляющих эти интерфейсы, и создается десять соответствующих объектов канала. Управляет всеми прокси – объектами, загруженными в процесс клиента, менеджер прокси-объектов. Он создает иллюзию, что удаленный объект находится внутри процесса клиента.
На серверной стороне объект-заглушка реализует
Рис.1.7. Связь между клиентом, прокси и объектом канала
На серверной стороне объект-заглушка реализует стандартный СОМ-интерфейс IRpcStubBuffer, который аналогично интерфейсу IRpcProxyBuffer тоже поддерживает методы Connect() и Disconnect(). Запрос, полученный от объекта канала ORPC, направляется заглушкой серверу (см. рис. 1.8) .
Схема вызова метода удаленного интерфейса показана на рис. 1.9.
Объекты прокси и заглушки – это обычные объекты СОМ, которые создаются одной фабрикой класса с интерфейсом IPSFactoryBuffer. Как только клиент запрашивает доступ к удаленному интерфейсу, соответствующие менеджеры вызывают метод CreateProxy() или CreateStub() интерфейса IPSFactoryBuffer. Далее прокси и заглушка загружаются в процессы клиента и сервера.
В процессе инсталляции всему СОМ-серверу, то есть всем коклассам, присутствующим в исполняемом файле, присваивается идентификатор приложения AppID (GUID).
Все зарегистрированные СОМ-приложения перечисляются в системном реестре под ключом HKCR/AppID. Там же могут содержаться именованные значения, касающиеся параметров защиты конкретного приложения, например, список пользователей, которым разрешен доступ к серверу, уровень аутентификации и т.п.
Чтобы избежать недостатков внутрипроцессного СОМ-объекта, то есть запустить внутрипроцессный СОМ-сервер в режиме локального сервера, применяется утилита dllhost.exe, строящая суррогатный процесс. В этом случае DLL на основе СОМ загружается с характеристиками отдельного процесса. Чтобы внутрипроцессный сервер загрузился в оболочку dllhost.exe, а не в процесс клиента, требуется внести необходимые записи в реестр системы.
Кроме того, надо перекомпилировать клиент с флагом CLSCTX_LOCAL_SERVER в методе CoGetClassObject().
Рис.1.8. Заглушки реализуют IrpcStubBuffer для распаковки запроса и передают его реальному СОМ-объекту
Рис.1.9. Схема вызова метода удалённого интерфейса