class PugCat : public IPug, public ICat

{

LONG mcRef;

protected:

virtual ~PugCat(void);

public: PugCat(void);

// IUnknown methods

// методы IUnknown

STDMETHODIMP QueryInterface(REFIID riid, void **ppv);

STDMETHODIMP(ULONG) AddRef(void);

STDMETHODIMP(ULONG) Release(void);

// IAnimal methods

// методы IAnimal

STDMETHODIMP Eat(void);

// IDog methods

// методы IDog

STDMETHODIMP Bark(void);

// IPug methods

// методы IPug

STDMETHODIMP Snore(void);

// ICat methods

// методы ICat

STDMETHODIMP IgnoreMaster(void);

};

Отметим, что в классе должен быть реализован каждый метод, определенный в любом интерфейсе, от которого он наследует, так же, как и каждый метод, определенный в любых производных (implied) базовых интерфейсах (например, IDog, IAnimal ). Для создания стековых фреймов, совместимых с СОМ, необходимо использовать макросы STDMETHODIMP и STDMETHODIMP. При ориентации на платформы Win32, использующие компилятор Microsoft C++, заголовки SDK определяют эти два макроса следующим образом:

#define STDMETHODIMP HRESULT stdcall

#define STDMETHODIMP(type) type stdcall

Заголовочные файлы SDK также определяют макросы STDMETHOD и STDMETHOD , которые можно использовать при определении интерфейсов без IDL-компилятора. В серийно выпускаемом программировании на СОМ эти два макроса не нужны.

Реализация AddRef и Release чрезвычайно прозрачна. Элемент данных mcRef отслеживает, сколько неосвобожденных интерфейсных указателей удерживают объект. Конструктор класса приводит счетчик ссылок в нулевое состояние:

PugCat::PugCat(void) : mcRef(0)

// initialize reference count to zero

// устанавливаем счетчик ссылок в нуль

{ } 

Реализация AddRef в классе фиксирует путем увеличения счетчика ссылок, что вызывающий объект продублировал указатель интерфейса. Измененное значение счетчика ссылок возвращается для целей диагностики:

STDMETHODIMP(ULONG) AddRef(void)

{ return ++mcRef; }

Реализация Release фиксирует уничтожение указателя интерфейса простым уменьшением счетчика ссылок, а также производит соответствующее действие, когда счетчик ссылок достигает нуля. Для объектов, находящихся в динамически распределяемой области памяти, это означает вызов оператора delete для уничтожения объекта:

STDMETHODIMP(ULONG) Release(void)

{

LONG res = -mcRef;

if (res == 0) delete this;

return res;

}

Для кэширования обновленного счетчика ссылок необходимо использовать временную переменную, так как нельзя обращаться к элементам данных объекта после того, как объект уже уничтожен.

Заметим, что показанные реализации Addref и Release используют собственные операторы инкремента и декремента (увеличения и уменьшения на единицу). Для простой реализации это весьма разумно, так как СОМ не допускает более одного потока для обращения к объекту до тех пор, пока конструктор не обеспечит явный многопоточный доступ (почему и как конструктор сделает это, подробно описано в главе 5). В случае объектов, доступных в многопоточной среде, для автоматического подсчета ссылок следует использовать подпрограммы Win32 InterlockedIncrement/InterlockedDecrement:

STDMETHODIMP(ULONG) AddRef(void)

{

return InterlockedIncrement(&mcRef);

}

STDMETHODIMP(ULONG) Release(void)

{

LONG res = InterlockedDecrement(&mcRef);

if (res == 0) delete this; return res;

}

Этот код несколько менее эффективен, чем версии, использующие собственные операторы C++. Но, вообще говоря, разумнее использовать менее эффективные варианты InterlockedIncrement / InterlockedDecrement, так как известно, что они надежны во всех ситуациях и освобождают разработчика от необходимости сохранять две версии практически одинакового кода.

Показанные выше реализации AddRef и Release предполагают, что объект может размещаться только в динамически распределяемой области памяти (в «куче») с использованием С++-оператора new. В определении класса деструктор сделан защищенной операцией для обеспечения того, чтобы ни один экземпляр класса не был определен никаким другим способом. Однако иногда желательно иметь объекты, не размещенные в «куче». Для этих объектов вызов delete в последнем вызове Release был бы гибельным. Так как единственной причиной для того, чтобы объект в первую очередь поддерживал счетчик ссылок, была необходимость вызова delete this, допустимо оптимизировать счетчик ссылок для объектов, не содержащихся в динамически распределяемой области памяти:

STDMETHODIMP(ULONG) GlobalVar::AddRef(void)

{

return 2;

// any non-zero value is legal

// допустима любая ненулевая величина

}

STDMETHODIMP(ULONG) GlobalVar::Release (void)

{

return 1;

// any non-zero value is legal

// допустима любая ненулевая величина

}

Эта реализация использует тот факт, что результаты AddRef и Release служат только для сведения и не обязаны быть точными.

При наличии реализации AddRef и Release единственным еще не реализованным методом из IUnknown остается QueryInterface. Его реализации должны отслеживать иерархию типов объекта и использовать статические приведения типов для возврата правильного типа указателя для всех поддерживаемых интерфейсов. Для определения класса PugCat, рассмотренного ранее, следующий код является корректной реализацией QueryInterface : STDMETHODIMP

PugCat::QueryInterface(REFIID riid, void **ppv)

{

assert(ppv != 0);

// or return EPOINTER in production

// или возвращаем EPOINTER в реальный продукт

if (riid == IIDIPug) *ppv = staticcast(this);

else if (riid == IIDIDog) *ppv = staticcast(this);

else if (riid == IIDIAnimal)

// cat or pug?

// кот или мопс?

*ppv == staticcast(this);

else if (riid == IIDIUnknown)

// cat or pug?

// кот или мопс?

*ppv = staticcast(this);

else if (riid == IIDICat) *ppv = staticcast(this);

else

{

// unsupported interface

// неподдерживаемый интерфейс

*ppv = 0;

return ENOINTERFACE;

}

// if we reach this point, *ppv is non-null

// and must be AddRef'ed (guideline A2)

// если мы дошли до этого места, то *ppv ненулевой

// и должен быть обработан AddRef'ом ( принцип A2)

reinterpretcast(*ppv)->AddRef();

return SOK;

}

Использование staticcast более предпочтительно, чем традиционные приведения типа в стиле С:


Перейти на страницу:
Изменить размер шрифта: