Выбрать главу

// do not override GetMaxSpeed!

// не подменяем GetMaxSpeed!

// ICar methods – методы ICar

STDMETHODIMP Brake(void);

// IBoat methods – методы IBoat

STDMETHODIMP Sink(void);

// IXPlane methods – методы IXPlane

STDMETHODIMP TakeOff(void);

// upcalled from IXCar::GetMaxSpeed

// вызвано из IXCar::GetMaxSpeed

STDMETHODIMP GetMaxCarSpeed(long *pval);

// upcalled from IXBoat::GetMaxSpeed

// вызвано из IXBoat::GetMaxSpeed

STDMETHODIMP GetMaxBoatSpeed(long *pval);

// called from IXPlane::GetMaxSpeed

// вызвано из IXPlane::GetMaxSpeed

STDMETHODIMP GetMaxPlaneSpeed(long *pval);

}

Рисунок 4.6 иллюстрирует представление этого класса и форматы таблиц vtbl. Отметим, что конфликтный метод GetMaxSpeed не реализован в этом классе. Поскольку каждый из базовых классов CarBoatPlane подменяет этот чисто виртуальный метод, то CarBoatPlane не нуждается в создании своей собственной реализации. Действительно, если бы в CarBoatPlane нужно было подменить GetMaxSpeed, то одна его реализация этого метода подменила бы версии, вызываемые из каждого базового класса, аннулировав результат использования IXCar, IXBoat и IXPlane. В силу этой проблемы данная технология годится только в тех ситуациях, когда можно быть уверенным, что класс реализации (или любые возможные производные классы) никогда не станет подменять конфликтный метод.

Другой способ обеспечения множественных реализации конфликтных методов состоит в том, чтобы усилить правила IUnknown . Спецификация СОМ не требует, чтобы объект был реализован как класс C++. Хотя существует весьма естественное соответствие между объектами СОМ и классами C++, базирующимися на множественном наследовании, это всего лишь одна из возможных технологий реализации. Для создания объекта СОМ может быть использована любая программная технология, производящая таблицы vtbl в нужном формате и удовлетворяющая правилам СОМ для QueryInterface. Один стандартный метод разрешения конфликтов имен состоит в реализации интерфейсов с конфликтующими именами как отдельных классов C++ и последующей компоновке целевого класса C++ из экземпляров этих отдельных классов. Для гарантии того, что каждый из этих составных элементов данных появится во внешнем мире как единый объект СОМ, часто назначается одна главная реализация QueryInterface, которой каждый составной элемент данных будет передавать функции. Следующий код демонстрирует эту технологию:

class CarPlane

{

LONG m_cRef;

CarPlane(void) : m_cRef(0) {}

public:

// Main IUnknown methods

// Главные методы IUnknown

STDMETHODIMP QueryInterface(REFIID, void**);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

private:

// define nested class that implements ICar

// определяем вложенный класс, реализующий

ICar struct XCar : public ICar

{

// get back pointer to main object

// получаем обратный указатель на главный объект

inline CarPlane* This();

STDMETHODIMP QueryInterface(REFIID, void**);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

STDMETHODIMP GetMaxSpeed(long *pval);

STDMETHODIMP Brake(void);

};

// define nested class that implements IPlane

// определяем вложенный класс, реализующий IPlane

struct XPlane : public IPlane {

// Get back pointer to main object

// получаем обратный указатель на главный объект

inline CarPlane* This();

STDMETHODIMP QueryInterface(REFIID, void**);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

STDMETHODIMP GetMaxSpeed(long *pval);

STDMETHODIMP TakeOff(void);

};

// declare instances of nested classes

// объявляем экземпляры вложенных классов

XCar m_xCar;

XPlane m_xPlane;

};

Использование вложенных классов не является обязательным, но оно подчеркивает, что эти подчиненные классы не имеют смысла вне контекста класса CarPlane. Рисунок 4.7 показывает двоичное размещение этого класса и размещения соответствующих vtbl .

Отметим, что имеется два определения вложенного класса, по одному для каждого реализованного им интерфейса. Это позволяет разработчику объекта обеспечить две различных реализации GetMaxSpeed:

STDMETHODIMP CarPlane::XCar::GetMaxSpeed(long *pn) {

// set *pn to max speed for cars

// устанавливаем *pn для максимальной скорости автомобилей

}

STDMETHODIMP CarPlane::XPlane::GetMaxSpeed(long *pn) {

// set *pn to max speed for planes

// устанавливаем *pn для максимальной скорости самолетов

}

Тот факт, что две реализации GetMaxSpeed встречаются в различных определениях вложенных классов, позволяет определить метод дважды и к тому же гарантирует то, что таблицы vtbl, соответствующие ICar и IPlane, будут иметь различные элементы для GetMaxSpeed.

Необходимо также отметить, что хотя класс CarPlane, находящийся на верхнем уровне, реализует методы IUnknown, он не наследует никакому производному от IUnknown классу. Вместо этого объекты CarPlane имеют элементы данных, которые наследуют интерфейсам СОМ. Это значит, что вместо того, чтобы использовать static_cast для вхождения в объект и нахождения определенного указателя vptr, реализация QueryInterface в CarPlane должна возвратить указатель на тот элемент данных, который реализует запрашиваемый интерфейс:

STDMETHODIMP CarPlane::QueryInterface(REFIID riid, void **ppv)

{

if (riid == IID_IUnknown) *ppv = static_cast<IUnknown*>(&m_xCar);

else if (riid == IID_IVehicle) *ppv = static_cast<IVehicle*> (&m_xCar);

else if (riid == IID_ICar) *ppv = static_cast<ICar*>(&m_xCar);

else if (riid == IID_IPlane) *ppv = static_cast<IPlane*>(&m_xPlane);

else return (*ppv = 0), E_NOINTERFACE;

((IUnknown*)(*ppv))->AddRef();

return S_OK;

}

Для обеспечения идентификации объекта каждый из элементов данных CarPlane должен или воспроизвести этот код в своей собственной реализации QueryInterface, или просто передать управление главной функции QueryInterface в CarPlane. Чтобы осуществить это, необходим механизм перехода к главному объекту со стороны функции-члена составного элемента данных. Определение класса CarPlane::XCar содержит встроенную подпрограмму, которая использует фиксированные смещения для вычисления указателя this главного объекта от указателя this составного элемента данных.

inline CarPlane CarPlane::XCar::This(void)

{

return (CarPlane*)((char*)this

// ptr to composite – указатель на композит – offsetof (CarPlane, m_xCar)); }

inline CarPlane CarPlane::XPlane::This(void)

{

return (CarPlane*)((char*)this

// ptr to composite – указатель на композит

– offsetof(CarPlane, m_xPlane));

}

Такая технология вычисления обратного указателя (back-pointer) компактна и чрезвычайно эффективна, так как не требует явных элементов данных для нахождения главного объекта внутри реализации метода элемента данных. При наличии таких алгоритмов вычисления обратного указателя реализация композитного QueryInterface становится тривиальной:

STDMETHODIMP CarPlane::XCar::QueryInterface(REFIID r, void**p)

{

return This()->QueryInterface(r, p);

}

STDMETHODIMP CarPlane::XPlane::QueryInterface(REFIID r, void**p)

{

return This()->QueryInterface(r, p);

}

Такая же передача this потребуется для AddRef и Release для получения обобщенного представления о времени жизни объекта в случае составных (композитных) элементов данных.