«Машины должны работать. Люди должны думать»
48. Пример реализации и использования 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 и т.д.
Описание (базового) класса 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.
Вызов этой функции целесообразно выполнить следующим образом:
if (m_pAccount)
{
m_pAccount->Release();
m_pAccount = NULL;
}
// создание экземпляра СОМ класса
if (!CreateAccount(&m_pAccount))
{
MessageBox("CreateAccount failed");
return;
}
После получения ссылки на интерфейс ею можно пользоваться для вызова методов класса, например, так:
int balance;
HRESULT hr = m_pAccount->GetBalance(&balance);
if (FAILED(hr))
{
MessageBox("GetBalance failed");
return;
}
Добавление других интерфейсов. Для реализации дополнительных интерфейсов необходимо проделать следующие шаги:
Ø объявить новый (абстрактный) класс как производный от IUnknown;
Ø сгенерировать для него идентификатор IID с помощью GuidGen.exe;
Ø объявить конкретный класс как производный от всех поддерживаемых объектом СОМ интерфейсов.
Например:
class IDisplay : public IUnknown
{
public:
// IDisplay methods
STDMETHOD(Show)() = 0;
};
Теперь используем множественное наследование и описываем класс конкретный:
class NewCAccount : public IAccount, IDisplay
{
…
}
Так как появился дополнительный интерфейс, необходимо изменить реализацию QueryInterface так, чтобы он позволял получать любой из интерфейсов IAccount и IDisplay. Пример реализации этого метода:
STDMETHODIMP
CAccount::QueryInterface(REFIID iid, void** ppv)
{
if (iid == IID_IUnknown)
*ppv = (IAccount*) this;
else if (iid == IID_IAccount)
*ppv = (IAccount*) this;
else if (iid == IID_IDisplay)
*ppv = (IDisplay*) this;
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
AddRef();
return NOERROR;
}
«47. Способы реализации СОМ серверов»
49. Расширения COM