Категории
Самые читаемые
ChitatKnigi.com » 🟢Компьютеры и Интернет » Программирование » Сущность технологии СОМ. Библиотека программиста - Дональд Бокс

Сущность технологии СОМ. Библиотека программиста - Дональд Бокс

Читать онлайн Сущность технологии СОМ. Библиотека программиста - Дональд Бокс
1 ... 35 36 37 38 39 40 41 42 43 ... 95
Перейти на страницу:

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать

QI(A)->A

Поскольку из множественного наследования вытекает единственная реализация QueryInterface для всех интерфейсов объекта, в действительности очень трудно нарушить требования симметричности, транзитивности и рефлективности.

Реализация также корректно выполняет правило СОМ об идентификации, возвращая только одно значение указателя при запросе IUnknown:

if (riid == IID_IUnknown) *ppv = static_cast<ICar*>(this);

Если бы реализация QueryInterface возвращала различные указатели vptr для каждого запроса:

if (riid == IID_IUnknown)

{

int n = rand() % 3;

if (n == 0) *ppv = static_cast<ICar*>(this);

else if (n == 1) *ppv = static_cast<IBoat*>(this);

else if (n == 2) *ppv = static_cast<IPlane*>(this);

}

то реализация была бы корректной только в терминах чисто С++-отношений типа (то есть все три интерфейса были бы совместимы по типу с запрошенным типом IUnknown). Эта реализация, однако, не является допустимой с точки зрения СОМ, поскольку правило идентификации для QueryInterface было нарушено.

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

Множественное наследование является очень эффективной и простой технологией для реализации интерфейсов СОМ в классе C++. Это требует написания очень короткого явного кода, так как большая часть работы компилятора и компоновшика заключается в построении соответствующих СОМ указателей vptr и таблиц vtbl. Если имя метода появляется более чем в одном базовом классе с идентичными типами параметров, то компилятор и компоновщик заполняют каждый элемент vtbl таким образом, чтобы он указывал на одну реализацию метода в классе. Этот режим применяется к таким методам, как QueryInterface, AddRef и Release, так как все интерфейсы СОМ начинаются с этих методов, и все же разработчику класса требуется написать каждый метод только один раз (и это хорошо). Этот же режим применяется и к методам любых интерфейсов, где происходит повтор имени и сигнатуры. Здесь есть одна возможная ловушка множественного наследования.

Иерархия транспортных интерфейсов из этой главы содержит конфликт имен. В интерфейсе ICar (автомобиль) имеется метод, названный GetMaxSpeed (развить максимальную скорость). В интерфейсах IBoat (лодка) и IPlane (самолет) также имеются методы, именуемые GetMaxSpeed с идентичной сигнатурой. Это означает, что при использовании множественного наследования разработчик класса пишет метод GetMaxSpeed один раз, а компилятор и компоновщик инициализируют таблицы vtbl , совместимые с ICar, IBoat и IPlane так, чтобы они указывали только на эту реализацию.

Возможно, это вполне разумное поведение для большого числа реализации. Но что если объекту нужно было вернуть другую максимальную скорость, зависящую от интерфейса, на который был сделан запрос? Поскольку имя и сигнатуры одинаковы, то необходимо принимать неординарные меры для разрешения множественных реализации конфликтного метода. Один из возможных способов состоит в создании промежуточного класса C++, производного от интерфейса и реализующего конфликтный метод путем создания чисто виртуального вызова неконфликтного имени:

struct IXCar : public ICar {

// add new non-clashing method as pure virtual

// добавляем новый неконфликтный метод как чисто виртуальный

virtual HRESULT STDMETHODCALLTYPE GetMaxCarSpeed(long *pval) = 0;

// implement clashing method by upcalling

// non-clashing implementation in derived class

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

// неконфликтной реализации в производном классе

STDMETHODIMP GetMaxSpeed(long *pval)

{ return GetMaxCarSpeed(pval); }

};

Допуская, что интерфейсы IBoat и IPlane подвергнуты подобной операции, можно реализовывать различные версии GetMaxSpeed простым наследованием от расширенных версий интерфейсов и переопределением неконфликтных версий каждого метода GetMaxSpeed:

class CarBoatPlane: public IXCar, public IXBoat, public IXPlane

{

public:

// Unknown methods – методы IUnknown

STDMETHODIMP QueryInterface(REFIID, void**);

STDMETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

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

// 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 должна возвратить указатель на тот элемент данных, который реализует запрашиваемый интерфейс:

1 ... 35 36 37 38 39 40 41 42 43 ... 95
Перейти на страницу:
Открыть боковую панель
Комментарии
Настя
Настя 08.12.2024 - 03:18
Прочла с удовольствием. Необычный сюжет с замечательной концовкой
Марина
Марина 08.12.2024 - 02:13
Не могу понять, где продолжение... Очень интересная история, хочется прочесть далее
Мприна
Мприна 08.12.2024 - 01:05
Эх, а где же продолжение?
Анна
Анна 07.12.2024 - 00:27
Какая прелестная история! Кратко, ярко, захватывающе.
Любава
Любава 25.11.2024 - 01:44
Редко встретишь большое количество эротических сцен в одной истории. Здесь достаточно 🔥 Прочла с огромным удовольствием 😈