Библиотека программиста

«Любой дурак может написать программу, которую поймет компилятор. Хорошие программисты пишут программы, которые смогут понять другие программисты»

Мартин Фаулер

Главная страница > Технология COM > 48. Пример реализации и использования COM класса в С++

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