3590
.pdfсоединении к существующему каркасу новых деталей. interface Idraw2: public Idraw interface Idraw3: public Idraw2
Здесь Idraw2 строится на основе Idraw, то есть Idraw2 является расширением Idraw. Idraw3, в свою очередь, является расширением Idraw2.
Каждый СОМ – интерфейс должен быть произведен от основного интерфейса IUnknown.
При этом используется ключевое слово interface. Оно определено в заголовочном файле
<objbase.h>:
#define interface struct
При использовании слов interface или struct по умолчанию доступ считается открытым, при использовании class – доступ к элементам структуры – закрытый.
Интерфейс IUnknown является основой любой иерархии интерфейсов, а, следовательно, и реализующих их классов. Он определяет три метода:
QueryInterface(), AddRef() и Release().
Идентификаторы интерфейсов
Интерфейсы должны быть именованы.
Каждый СОМ-интерфейс помечается универсальным уникальным идентификатором GUID. Это 128 – битное число, которое генерируется обычно на основе уникального сетевого адреса и точного времени запроса.
Виды GUID:
IID – идентификаторы интерфейса СОМ;
CLSID – уникальный идентификатор СОМкласса;
11
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.
В программе используется макрос DEFINE_GUID, в
12
который подставляется полученное число GUID, например:
<initguid.h> //Обязательно использовать.
...
// (A533DA30-D372-11D2-B8CF-0020781238D4) DEFINE_GUID (IID_IEngine,
//префикс ___
//имя интерфейса___
0xa533da30,0xda30,0x11d2,0xb8,0xcf,0x0,0x2
0,0x78,0x12,0x38,0xd4);
Для создания GUID можно использовать библиотечную функцию СОМ
CoCreateGuid (GUID* pguid);
Ей передается указатель на GUID, и она заполняет требуемые поля.
1.1.4. Интерфейс IUnknown
Класс СОМ – это тип, определенный пользователем, как минимум содержит IUnknown. Класс СОМ также имеет название кокласс (coclass).
Объект Сом – это экземпляр кокласса. Кокласс реализует интерфейсы.
IUnknown – это стандартный интерфейс СОМ, от которого производятся все остальные интерфейсы. Во всех случаях первые три метода интерфейса СОМ – это методы, унаследованные от IUnknown. Сокращенное определение из <unknown.h>:
13
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( ) это чисто виртуальные функции, то программист должен определить реализацию этих методов. В них реализуется счетчик вызовов.
14
Итак, интерфейс Iunknown отвечает за получение ссылки на интерфейс и за цикл жизни объекта.
COM-интерфейс создается на основе IUnknown: interface IDraw : public IUnknown
{
virtual void Draw() = 0;
}
При попытке вызова Draw в данном случае будет выдано сообщение об ошибке, так как отсутствует его реализация. Поэтому должна быть определена реализация метода Draw (с параметрами, если необходимо):
void Draw( . . . )
{
. . .
}
Правила вызова функций AddRef( ) и Release( )
1. Объект СОМ самостоятельно вызывает AddRef( ), когда он передает указатель интерфейса клиенту (обычно при вызове QueryInterface).
2. После получения указателя интерфейса клиент считает, что AddRef была вызвана объектом, и клиент по окончании работы с полученным указателем должен вы-
звать Release( ).
IDraw* pDraw = NULL; if (GetIDraw(&pDraw))
//Метод возвращает указатель интерфейса. { // Успешно.
pDraw -> Draw( );
//Если нет других вызовов, то объект удаляется pDraw -> Release( );
} |
. |
15
3. Если клиент (или сервер) делает копию указателя интерфейса, клиент должен явно вызвать AddRef() для этой копии.
pIFace2 = pIFace1; pIFace2 -> AddRef( );
4. Функции, принимающие интерфейсный указатель в качестве параметров, должны выполнять 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 ? |
|
16
*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 проверяют старший
17
бит HRESULT и возвращают true или false. Тексты сообщений об ошибках содержатся в <winerror.h>. Сообщение об ошибке выводит функция FormatMessage().
Пример:
IDraw* pDraw = NULL; HRESULT hr;
// pUnk – ранее полученный указатель IUnknown
hr = pUnk -> QueryInterface(IID_IDraw,
|
(void**) |
if (SUCCEEDED(hr)) |
&pDraw); |
// У объекта есть IDraw ? |
|
pDraw -> Draw(); |
// Есть интерфейс IDraw |
18
1.2. Основы разработки программ, использующих СОМ
1.2.1. Макросы СОМ
Для определения интерфейса используются следующие макросы СОМ.
Имеются два основных набора интерфейсных макросов СОМ (один - для определения интерфейса, другой – для кокласса, реализующего интерфейс). Они определены в <objbase.h>.
Для определения интерфейса используются следующие макросы СОМ.
DECLARE_INTERFACE_ (имя_интерфейса,
имя_базового_интерфейса)
STDMETHOD (метод) (параметры) –
используется при описании метода, возвращающего HRESULT, в скобках перечисляются параметры, если они есть.
STDMETHOD_ (тип, метод) (параметры) –
используется при описании метода, возвращающего не HRESULT, а другой тип, который указывается в качестве первого элемента в скобках.
PURE – заменяет текст ' = 0 ' в определении чисто виртуальной функции.
Макросам STDMETHOD и STDMETHOD_, опреде-
ляющим интерфейс, соответствуют макросы STDMETHODIMP и STDMETHODIMP_, которые реализуют интерфейс в коклассе (IMP – от слова implementation – реализация).
Пример определения интерфейса:
19
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; returnS_OK;
}
20