Изменить стиль страницы

QueryInterface рефлективна

Спецификация СОМ требует, чтобы запрос QueryInterface через интерфейсный указатель всегда достигал цели, если запрошенный тип соответствует типу указателя, с помощью которого произведен запрос. Это означает, что QI(A)->A всегда должен быть верным.

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

Это требование проиллюстрировано рис. 4.4 и в следующем фрагменте кода:

void AssertReflexive(ICar *pCar)

{

if (pCar)

{

ICar *pCar2 = 0;

// request same type of interface

// запрос интерфейса того же типа

HRESULT hr = pCar->QueryInterface(IID_ICar, (void**)&pCar2);

// if the following assertion fails, pCar

// did not point to a valid СОМ object

// если следующее утверждение неверно, то pCar

// не указывает на корректный объект СОМ

assert(SUCCEEDED(hr));

pCar2->Release();

}

}

Из этого кода следует, что все реализации ICar должны быть способны удовлетворить дополнительные запросы QueryInterface для ICar через интерфейсный указатель ICar. Если бы это не соблюдалось, то было бы невозможно передавать жестко типизированные интерфейсы через параметры базового типа без невосполнимой потери исходного типа:

extern void GetCar(ICar **ppcar);

extern void UseVehicle(IVehicle *pv);

ICar *pCar;

GetCar(&pCar);

UseVehicle(pCar);

// ICar-ness is syntactically lost

// ICar-ность синтаксически потеряна

void UseVehicle(IVehicle *pv)

{

ICar *pCar = 0;

// try to regain syntactic ICar-ness

// пытаемся восстановить синтаксическую ICar-ность

HRESULT hr = pv->QueryInterface(IID_ICar, (void**)&pCar);

}

Поскольку указатель, использованный в функции UseVehicle , имеет то же самое значение, что и указатель ICar , переданный вызывающим объектом, то выглядело бы неестественным (counterintuitive), если бы этот тип не мог быть восстановлен внутри функции.

Из того, что QueryInterface является симметричным, рефлексивным и транзитивным, следует, что любой интерфейсный указатель на объект должен выдавать тот же самый ответ «да/нет» на данный запрос QueryInterface. Это позволяет клиентам рассматривать иерархию типов объекта как простой граф, все вершины которого непосредственно соединены друг с другом (и с самими собой) с помощью открытых (explicit) ребер. На рис. 4.5 изображен такой граф. Отметим, что в любую вершину графа можно попасть из любой другой вершины, пройдя вдоль только одного ребра.

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

Объекты имеют статический тип

Один из выводов, который можно сделать из трех требований QueryInterfасе , состоит в том, что множество интерфейсов, поддерживаемых объектом, не может изменяться во времени. Спецификация СОМ четко требует, чтобы этот вывод был верен для всех объектов. Из этого требования следует, что иерархия типов объекта является статичной, несмотря на тот факт, что для определения множества поддерживаемых типов данных клиенты должны опрашивать объекты динамически. Если объект отвечает «да» на запрос интерфейса типа А, то он должен отвечать «да», начиная с этой точки. Если объект отвечает «нет» на запрос интерфейса типа А , то он должен отвечать «нет», начиная с этой точки. Фраза «начиная с этой точки» (from that point on) буквально переводится как «до тех пор, пока есть хотя бы один внешний указатель интерфейса на объект». Обычно это соответствует жизненному циклу базового объекта C++, но язык Спецификации СОМ обладает достаточной свободой, чтобы предоставить разработчикам определенную гибкость (например, иерархия типов глобальной переменной может изменяться, когда все указатели освобождены).

Из того, что все объекты СОМ имеют статическую иерархию типов, следует, что утверждение, записанное в следующем коде, никогда не должно быть ложным, несмотря на то, что идентификатор интерфейса используется в качестве второго параметра:

void AssertStaticType(IUnknown *pUnk, REFIID riid)

{

IUnknown *pUnk1 = 0,

*pUnk2 = 0;

HRESULT hr1 = pUnk->QueryInterface(riid, (void**)&pUnk1);

HRESULT hr2 = pUnk->QueryInterface(riid, (void**)&pUnk2);

// both requests for the same interface should

// yield the same yes/no answer

// оба запроса того же самого интерфейса

// должны получить тот же самый ответ да/нет

assert(SUCCEEDED(hr1) == SUCCEEDED(hr2));

if (SUCCEEDED(hr1)) pUnk1->Release();

if (SUCCEEDED(hr2)) pUnk2->Release();

}

Это требование означает, что в СОМ запрещены следующие программные технологии:

Использование временной информации при решении вопроса о том, удовлетворять или нет запрос QueryInterface (например, выдавать интерфейс IMorning (утро) только до 12:00).

Использование переменной информации о состоянии при решении вопроса о том, удовлетворять или нет запрос QueryInterface (например, выдавать интерфейс INotBusy (не занят), только если количество внешних интерфейсных указателей меньше десяти).

Использование маркера доступа (security token) вызывающего объекта для решения, удовлетворять или нет запрос QueryInterface . Как будет объяснено в главе 6, на самом деле это не обеспечивает никакой реальной безопасности из-за протокола передачи (wire protocol ), используемого СОМ.

Использование успешного захвата динамических ресурсов для решения вопроса о том, удовлетворять или нет запрос QueryInterface (например, выдавать интерфейс IHaveTonsOfMemory (у меня тонны памяти) только при успешном выполнении malloc(4096*4096)).

Эта последняя методика может быть до некоторой степени смягчена, если разработчик объекта желает поупражняться с выражением спецификации СОМ «barring catastrophic failure» (за исключением катастрофического сбоя).

Эти ограничения не означают, что два объекта одного и того же класса реализации не могут давать различные ответы «да/нет» при запросе одного и того же интерфейса. Например, класс может реализовать показанные ранее интерфейсы ICar, IBoat и IPlane , но может разрешить только одному интерфейсу быть использованным в каком-то определенном объекте. Эти ограничения также не означают, что объект не может использовать постоянную или временную информацию для решения вопроса о том, дать ли исходное «да» или «нет» для данного интерфейса. В примере для класса, который разрешает только один из трех интерфейсов, следующая идиома была бы вполне допустимой:

class СВР : public ICar, public IPlane, public IBoat

{

enum TYPE { CAR, BOAT, PLANE, NONE };

TYPE m_type;

CBP(void) : m_type(NONE) { }

STDMETHODIMP QueryInterface(REFIID riid, void **ppv)

{

if (md == IID_ICar)

{

// 1st QI Initializes type of object

// первая QI инициализирует тип объекта

if (m_type == NONE) m_type = CAR;

// only satisfy request if this object is a car

// удовлетворяем запрос, только если данный объект

// является car (автомобилем)

if (m_type == CAR) *ppv = static_cast(this);

else return (*ppv = 0), E_NOINTERFACE;

}

else if (md == IID_IBoat)