Рис. 10. Программист компонента может использовать один
счетчик ссылок для всего компонента либо отдельные счетчики ссылок для
интерфейсов.
Когда перекрывание времен жизни указателей на
интерфейсы. Здесь надо подсчитывать ссылки на оба интерфейса.
Для реализации функций AddRef ( ) и Release ( )
унаследованных от класса IUnknown введем
переменную m _ cRef, которая отвечает за подсчет текущих обращений к
объекту или ожидающих обработку указателей интерфейса. Хотя функции AddRef ( ) и Release ( )
могут изменять значение счетчика обращений к СОМ-интерфейсу, сам интерфейс не
является экземпляром объекта. Объект в каждый момент времени может иметь любое
количество пользователей своих интерфейсов и должен отслеживать значение
внутреннего счетчика активных интерфейсов.
ULONG _ stdcall AddRef ( )
{
return ++m _ cRef;
}
ULONG _ stdcall Release ( )
{
if (--m _ cRef = = 0)
{
delete this;
return 0;
}
return m _ cRef;
}
AddRef увеличивает значение переменной m _ cRef, счетчика
ссылок. Release уменьшает m _ cRef и удаляет компонент, если значение переменной
становится равным нюлю. Во многих случаях можно встретить реализацию AddRef и Release при помощи функций Win32 Interlocked Increment и Interlocked Decrement.
Эти функции гарантируют, что значение переменной изменяет в каждый момент
времени только один поток управления и это обеспечивает в свою очередь,
возможность синхронизировать доступа к внутренним счетчикам активных
интерфейсов.
ULONG
_ stdcall AddRef ( )
{
return
Interlocked Increment (& m _ cRef)
}
ULONG _ stdcall Release ( )
{
if (Interlocked Decrement (& m _ cRef) = = 0)
{
delete this;
return 0;
}
}
§ 10.3. Множественные интерфейсы.
Одним из наиболее
мощных свойств СОМ является то, что каждый компонент предоставляет несколько
интерфейсов для одного объекта. При разработке С++ - класса создается всего
один интерфейс. Обычно при построении СОМ-компонента необходимо предоставить
несколько интерфейсов для одного экземпляра класса С++.
Объявление нескольких для одного класса
С++ на самом деле является непростым делом. Во-первых, поскольку СОМ-интерфейсы
фактически являются указателями на виртуальную таблицу функций С++, класс с
множеством интерфейсов требует создания множества виртуальных таблиц. Другой
причиной взаимосвязи компонентных объектов и их классов реализации интерфейса
является необходимость подсчета количества обращений. Все интерфейсы СОМ –
объекта, должны взаимодействовать между собой в обеспечение подсчета обращений.
Для каждого объекта существует только один счетчик обращений. Для каждого
объекта существует только один счетчик обращений, и интерфейсы должны
использовать его совместно. Эта взаимосвязь осуществляется с помощью множества
интерфейсов IUnknow. Каждый СОМ-компонент
должен обладать собственной реализацией интерфейса IUnknown.
В
языке программирования С++ существует
три основных способа обеспечения множества интерфейсов для СОМ-компонента:
1) множественное наследование;
2) реализации интерфейсов;
3) вложенность классов С++.
Мы будем использовать множественное наследование
классов С++, т.к. с его помощью в активной библиотеке шаблонов (ATL) реализованы СОМ-интерфейсы. Вложенность классов С++
применяется и в библиотеках базовых Microsoft
(MFC).
Допустим компонент Math предоставляет нам два интерфейса IMath и IExpression.
Используя множественное наследование, снабдим класс Math обоими интерфейсами:
class Math: public IMath, public IExpression
{
// Реализация компонента включает и реализацию каждого
наследуемого //интерфейса
…
};
Единственная проблема этого подхода
состоит в том, что в таком случае может возникнуть конфликт базовых интерфейсов
IUnknown (так как оба класса, и IMath, и IExpression, наследуется IUnknown), однако в данном случае конфликта не происходит,
поскольку унаследованный класс должен «совместно использовать» реализацию
интерфейса IUnknown.
Важным моментом является то, что
СОМ-компоненты должны предоставлять несколько интерфейсов или, другими словами,
несколько указателей на виртуальные таблицы. Если обеспечивать это с помощью
множественного наследования, то необходимо, чтобы тип указателя данного
экземпляра правильно приводился к типу указателя на виртуальную таблицу. Таким
образом, метод Query Interface для класса Math
будет иметь вид:
HRESVLT MATH : : Query Interface (REFIID riid,
void ** ppv)
{
switch (*iid)
{
case IID_ IUnknown:
case IID_ IMath:
// Множественное
наследование требует явного приведения типов
*ppv = (IMath*) this;
break;
case IID_ IExpression:
// Множественное
наследование требует явного приведения типов
*ppv = (IExpression*) this;
break;
default:
return (E_NOINTERFASE);
}
// Возвращаем
указатель нового интерфейса и таким образом
// вызывается
AddRef для нового
указателя
AddRef ( );
return (S_OK);
}
Указатель на интерфейс IUnknown может быть возвращен посредством обращения к IMath или IExpression,
поскольку оба эти класса содержат такой метод. Допустим это будет обращение к IMath. Для реализации Query Interface был
использован оператор switch, хотя на
сомом деле нельзя, поскольку идентификатор интерфейса - структура (IDD), а не константа.
Другой вариант реализации Query Interface:
HRESULT _ stdcall CA::Query Interface
(const IDD&iid, void **ppv )
{
if (iid = = IID _ IUnknown)
{
// Клиент запрашивает интерфейс IUnknown
*ppv = static _ cast <IX*>(this); //(= (IX*)this)
}
else if (iid = = IID _ IX)
{
//Клиент
запрашивает интерфейс IX
*ppv = static _
cast <IX*> (this);
}
else if (iid = = IID _ IY)
{
//Клиент запрашивает интерфейс IY
*ppv = static _ cast <IY*> (this);
}
else
{
// Мы не
поддерживаем запрашиваемый компонентом интерфейс
//Установить
возвращаемый указатель в NULL
*ppv = NULL; //обязательно
return E _ NOINTERFASE;
}
static _ cast <IUncnown*> → AddRef ( );
return S _ OK;
}
Перед присваиванием указателю,
описанному как void, надо всегда явно приводить this к нужному типу. При возвращении указателя на IUncnown возникает неоднозначность:
*ppv = static _ cast < IUncnown*> (this); // неодназначность
Поскольку IUncnown
наследует два интерфейса, IX и IY. Таким образом, следует выбрать, какой из
указателей static _ cast <IUncnown*> (static _ cast <IX*> (this)) или
static _ cast <IUncnown> (static _ cast
<IY*> (this)) – возвращать. В данном случае выбор несущественен, поскольку реализации указателей
идентичны. Необходимо, чтобы для IUncnown возвращался один и тот же указатель.