Сущность технологии СОМ. Библиотека программиста - Дональд Бокс
- Дата:20.06.2024
- Категория: Компьютеры и Интернет / Программирование
- Название: Сущность технологии СОМ. Библиотека программиста
- Автор: Дональд Бокс
- Просмотров:3
- Комментариев:0
Шрифт:
Интервал:
Закладка:
else if (riid == IID_ICar)
// forward request…
// переадресовываем запрос…
return m_pUnkCar->QueryInterface(riid, ppv);
else return (*ppv = 0), E_NOINTERFACE; ((IUnknown*)*ppv)->AddRef();
return S_OK;
}
Поскольку объект Car не имеет понятия о том, что он является частью идентификационной единицы (identity) другого объекта, то он будет причиной неуспеха любых запросов QueryInterface для IBoat. Это означает, что
QI(IBoat)->ICar
пройдет успешно, а запрос
QI(QI(IBoat)->ICar)->IBoat
потерпит неудачу, так как полученная QueryInterface будет несимметричной. Вдобавок запросы QueryInterface о IUnknown через интерфейсные указатели ICar и IBoat вернут различные значения, а это означает, что будет идентифицировано два различных объекта. Из подобных нарушений протокола IUnknown следует, что объекты CarBoat попросту не являются действительными объектами СОМ.
Идея составления объекта из двоичных композитов звучит красиво. Действительно, Спецификация СОМ четко и подробно указывает, как реализовать эту идею в стандартной и предсказуемой манере. Технология выставления клиенту двоичного подкомпонента непосредственно через QueryInterface называется СОМ-агрегированием. СОМ-агрегирование является лишь набором правил, определяющих отношения между внешним объектом (агрегирующим) и внутренним (агрегируемым). СОМ-агрегирование – это просто набор правил IUnknown, позволяющих более чем одному двоичному компоненту фигурировать в качестве идентификационной единицы (identity) СОМ.
Агрегирование СОМ несомненно является главной движущей силой для повторного использования в СОМ. Намного проще приписывать объекту значения и использовать его методы в реализации методов других объектов. Только в редких случаях кто-то захочет выставлять интерфейсы другого объекта непосредственно клиенту как часть той же самой идентификационной единицы. Рассмотрим следующий сценарий:
class Handlebar : public IHandlebar { … };
class Wheel : public IWheel {};
class Bicycle : public IBicycle
{
IHandlebar * m_pHandlebar;
IWheel *m_pFrontWheel;
IWheel *m_pBackWheel;
}
Было бы опрометчиво для класса Вicycle объявлять интерфейсы IHandlebar (велосипедный руль) или IWheel (колесо) в собственном методе QueryInterface. QueryInterface зарезервирован для выражения отношений «является» (is-a), а велосипед (bicycle) очевидно не является колесом (wheel) или рулем (handlebar). Если разработчик Bicycle хотел бы обеспечить прямой доступ к этим сторонам объекта, то интерфейс IBicycle должен был бы иметь для этой цели аксессоры определенных свойств:
[object, uuid(753A8A60-A7FF-11d0-8C30-0080C73925BA)] interface IBicycle : IVehicle
{
HRESULT GetHandlebar([out,retval] IHandlebar **pph);
HRESULT GetWheels([out] IWheel **ppwFront, [out] IWheel **ppwBack);
}
Реализация Bicycle могла бы тогда просто возвращать указатели на свои подобъекты:
STDMETHODIMP Bicycle::GetHandlebar(IHandlebar **pph)
{
if (*pph = m_pHandlebar) (*pph)->AddRef();
return S_OK;
}
STDMETHODIMP Bicycle::GetWheels(IWheel **ppwFront, IWheel **ppwBack)
{
if (*ppwFront = m_pFrontWheel) (*ppwFront)->AddRef();
if (*ppwBack = m_pBackWheel) (*ppwBack)->AddRef();
return S_OK;
}
При использовании данной технологии клиент по-прежнему получает прямой доступ к подобъектам. Однако поскольку указатели получены через явные методы, а не через QueryInterface, то между различными компонентами не существует никаких идентификационных отношений.
Несмотря на этот пример, все же остаются сценарии, где желательно обеспечить реализацию интерфейса, которая могла бы быть внедрена в идентификационную единицу другого объекта. Чтобы осуществить это, в СОМ-агрегировании требуется, чтобы внутренний объект (агрегируемый) уведомлялся во время его создания, что он создается как часть идентификационной единицы другого объекта. Это означает, что создающая функция (creation function), обычно используемая для создания объекта, требует один дополнительный параметр: указатель IUnknown на идентификационную единицу, которой агрегирующий объект должен передать функции в ее методы QueryInterface, AddRef и Release. Покажем определение метода CreateInstance интерфейса IClassFactory:
HRESULT CreateInstance([in] Unknown *pUnkOuter, [in] REFIID riid, [out, iid_is(riid)] void **ppv);
Этот метод (и соответствующие API-функции CoCreateInstanceEx и CoCreateInstance) перегружен с целью поддержки создания автономных (stand-alone ) объектов и агрегатов. Если вызывающий объект передает нулевой указатель и качестве первого параметра CreateInstance (pUnkOuter ), то результирующий объект будет автономной идентификационной единицей самого себя. Если же вызывающий объект передает в качестве первого параметра ненулевой указатель, то результирующий объект будет агрегатом с идентификационной единицей, ссылка на которую содержится в pUnkOuter. В случае агрегации агрегат должен переадресовывать все запросы QueryInterface, AddRef и Release непосредственно и безусловно на pUnkOuter. Это необходимо для обеспечения идентификации объекта.
Имея прототип функции, приведенный выше, класс CarBoat после небольшой модификации будет удовлетворять правилам агрегации:
CarBoat::CarBoat(void) : m_cRef(0)
{
// need to pass identity of self to Create routine
// to notify car object it 1s an aggregate
// нужно передать свою идентификацию подпрограмме
// Create для уведомления объекта car, что он – агрегат
HRESULT hr = CoCreateInstance(CLSID_Car, this, CLSCTX_ALL, IID_IUnknown, (void**)&m_pUnkCar);
assert(SUCCEEDED(hr));
}
Реализация CarBoat QueryInterface просто переадресовывает запрос ICar внутреннему агрегату:
STDMETHODIMP CarBoat::QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown) *ppv = static_cast<IUnknown*>(this);
else if (riid == IID_ICar)
// forward request…
// переадресовываем запрос…
return m_pUnkCar->QueryInterface(riid, ppv);
else if (riid == IID_IBoat)
:
:
:
Теоретически это должно работать, так как агрегат будет всегда переадресовывать любые последующие запросы QueryInterface обратно главному объекту, проводя таким образом идентификацию объекта.
В предыдущем сценарии метод CreateInstance класса Car возвращает внешнему объекту указатель интерфейса, наследующего IUnknown. Если бы этот интерфейсный указатель должен был просто делегировать вызовы функций интерфейсу IUnknown внешнего объекта, то невозможно было бы: 1) уведомить агрегат, что он больше не нужен; 2) запросить интерфейсные указатели при выделении их клиентам главного объекта. На деле результатом приведенной выше реализации QueryInterface будет бесконечный цикл, поскольку внешний объект делегирует функции внутреннему, который делегирует их обратно внешнему.
Для решения этой проблемы необходимо сделать так, чтобы начальный интерфейсный указатель, который возвращается внешнему объекту, не делегировал вызовы реализации IUnknown внешнего объекта. Это означает, что объекты, поддерживающие СОМ– агрегирование, должны иметь две реализации IUnknown. Делегирующая, то есть передающая функции, реализация переадресовывает все запросы QueryInterface, AddRef и Release внешней реализации. Это и есть реализация по умолчанию, на которую ссылаются таблицы vtbl всех объектов, и это именно та версия, которую видят внешние клиенты. Объект должен также иметь неделегирующую реализацию IUnknown, которая выставляется только агрегирующему внешнему объекту.
Имеется несколько возможностей обеспечить две различные реализации IUnknown от одного объекта. Самый прямой путь[1] – это использование композиции и элемента данных для реализации неделегирующих методов IUnknown. Ниже показана реализация Car, поддающаяся агрегации:
class Car : public ICar
{
LONG m_cRef;
IUnknown *m_pUnk0uter;
public: Car(IUnknown *pUnk0uter);
// non-delegating IUnknown methods
// неделегирующие методы
IUnknown STDMETHODIMP InternalQueryInterface(REFIID, void **);
STDMETHODIMP (ULONG) InternalAddRef(void);
STDMETHODIMP_(ULONG) InternalRelease(void);
// delegating IUnknown methods
// делегирующие методы IUnknown
STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP GetMaxSpeed(*long *pn);
STDMETHODIMP Brake(void);
// composite to map distinguished IUnknown vptr to
// non-delegating InternalXXX routines in main object
// композит для преобразования определенного vptr IUnknown
// в неделегирующие подпрограммы InternalXXX в главном
// объекте
class XNDUnknown : public IUnknown
{ Car* This()
{
return (Car*)((BYTE*)this – offsetof(Car, m_innerUnknown));
}
STDMETHODIMP QueryInterface(REFIID r, void**p)
{
return This()->InternalQueryInterface(r,p);
}
STDMETHODIMP_(ULONG) AddRef(void)
{
return This()->InternalAddRef();
}
STDMETHODIMP_(ULONG) Release(void)
{
return This()->InternalRelease();
}
};
XNDUnknown m_innerUnknown;
// composite instance
// экземпляр композита };
Двоичное размещение этого объекта показано на рис. 4.8. Методы делегирования класса чрезвычайно просты:
- Аквариум. (Новое издание, исправленное и переработанное) - Виктор Суворов (Резун) - Шпионский детектив
- Права на результаты интеллектуальной деятельности и средства индивидуализации: Комментарий к части четвертой Гражданского кодекса Российской Федерации - Вадим Погуляев - Юриспруденция
- У меня есть идея! Что дальше? - Михаил Соболев - О бизнесе популярно
- Ориентирование - К. М. Станич - Современные любовные романы
- Проект 365 - Константин Рочев - Поэзия