Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Роджерсон Д. - Основы COM - 2000.pdf
Скачиваний:
411
Добавлен:
13.08.2013
Размер:
2.4 Mб
Скачать

43

}

}

// Удалить компонент delete pIUnknown;

return 0;

}

Листинг 3-1 Использование QueryInterface

Эта программа выдает на экран следующее:

Клиент: Получить указатель на IUnknown Клиент: Получить интерфейс IX QueryInterface: Вернуть указатель на IX Клиент: IX получен успешно

Fx

Клиент: Получить интерфейс IY QueryInterface: Вернуть указатель на IY Клиент: IY получен успешно

Fy

Клиент: Запросить неподдерживаемый интерфейс QueryInterface: Интерфейс не поддерживается Клиент: Не могу получить интерфейс IZ Клиент: Получить интерфейс IY через IX QueryInterface: Вернуть указатель на IY Клиент: IY получен успешно

Fy

Клиент: Получить интерфейс IUnknown через IY QueryInterface: Вернуть указатель на IUnknown

Совпадают ли указатели на IUnknown? Да, pIUnknownFromIY == pIUnknown

Клиент начинает с создания компонента при помощи CreateInstance. CreateInstance возвращает указатель на интерфейс IUnknown компонента. Клиент при помощи QueryInterface запрашивает через интерфейс IUnknown указатель на интерфейс IX компонента. Для проверки успешного окончания используется макрос SUCCEEDED. Если указатель на IX получен успешно, то клиент с его помощью вызывает функцию этого интерфейса Fx.

Затем клиент использует указатель на IUnknown, чтобы получить указатель на интерфейс IY. В случае успеха клиент пользуется этим указателем. Поскольку класс CA реализует как IX, так и IY, QueryInterface успешно обрабатывает запросы на эти интерфейсы. Однако CA не реализует интерфейс IZ. Поэтому — когда клиент запрашивает этот интерфейс, QueryInterface возвращается код ошибки E_NOINTERFACE. Макрос SUCCEEDED возвращает FALSE, и pIZ не используется (для доступа к функциям-членам IZ).

Теперь мы дошли до по-настоящему интересных вещей. Клиент запрашивает указатель на интерфейс IY через указатель на интерфейс IX, pIX. Поскольку компонент поддерживает IY, этот запрос будет успешным, и клиент сможет использовать возвращенный указатель на интерфейс IY так же, как он использовал первый указатель.

Наконец, клиент запрашивает интерфейс IUnknown через указатель на IY. Поскольку все интерфейсы СОМ наследуют IUnknown, этот запрос должен быть успешным. Однако самое интересное, что возвращенный указатель на IUnknown, pIUnknownFromIY, совпадает с первым указателем на IUnknown, pIUnknown. Как мы увидим далее, это одно из требований СОМ: QueryInterface должна возвращать один и тот же указатель на все запросы к IUnknown.

Пример показывает, что при помощи QueryInterface можно получить любой из интерфейсов CA через любой другой. Это одно из важных правил реализации QueryInterface. Давайте более подробно рассмотрим его и другие правила.

Правила и соглашения QueryInterface

В этом разделе представлены некоторые правила, которым должны следовать все реализации QueryInterface. Если их выполнять, клиент сможет узнать о компоненте достаточно, чтобы (надеяться) управлять им и использовать его в своих целях. Без этих правил поведение QueryInterface было бы неопределенным, и писать программы было бы невозможно.

!" Вы всегда получаете один и тот же IUnknown.

!" Вы можете получить интерфейс снова, если смогли получить его раньше. !" Вы можете снова получить интерфейс, который у Вас уже есть.

44

!" Вы всегда можете вернуться туда, откуда начали.

!" Если Вы смогли попасть куда-то хоть откуда-нибудь, Вы можете попасть туда откуда угодно. Теперь рассмотрим эти правила подробно.

Вы всегда получаете один и тот же IUnknown

У данного экземпляра компонента есть только один интерфейс IUnknown. Всегда, когда Вы запрашиваете у компонента IUnknown (не важно, через какой интерфейс), в ответ вы получите одно и то же значение указателя. Вы можете определить, указывают ли два интерфейса на один компонент, запросив у каждого из них IUnknown и сравнив результаты. Приведенная ниже функция SameComponents определяет, указывают ли pIX и pIY на интерфейсы одного компонента:

BOOL SameComponents(IX* pIX, IY* pIY)

{

IUnknown* pI1 = NULL;

IUnknown* pI2 = NULL;

// Получить указатель на IUnknown через pIX pIX->QueryInterface(IID_IUnknown, (void**)&pI1);

// Получить указатель на IUnknown через pIY pIY->QueryInterface(IID_IUnknown, (void**)&pI2);

// Сравнить полученные указатели return pI1 == pI2;

}

Это важное правило. Без него нельзя было бы определить, указывают ли два интерфейса на один и тот же компонент.

Вы можете получить интерфейс снова, если смогли получить его раньше

Если QueryInterface однажды успешно обработала запрос на некоторый интерфейс, то все последующие запросы для того же компонента будут успешными. Если же запрос был неудачным, то для этого интерфейса QueryInterface всегда будет возвращать ошибку. Это правило относится только к конкретному экземпляру компонента. Ко вновь созданному экземпляру оно неприменимо.

Представьте себе, что произошло бы, если бы набор поддерживаемых интерфейсов мог изменяться со временем. Писать код клиента было бы крайне сложно. Когда клиент должен запрашивать интерфейсы у компонента? Как часто это делать? Что произойдет, если клиент не сможет получить интерфейс, который только что использовал? Без фиксированного набора интерфейсов клиент не мог бы сколько-нибудь уверенно определить возможности компонента.

Вы можете снова получить интерфейс, который у Вас уже есть

Если у Вас есть интерфейс IX, то Вы можете запросить через него интерфейс IX и получите в ответ указатель на IX. Код выглядит так:

void f(IX* pIX)

{

IX* pIX2 = NULL;

// Запросить IX через IX

HRESULT hr = pIX->QueryInterface(IID_IX, (void**)&pIX2); assert(SUCCEEDED(hr)); // Запрос должен быть успешным

}

Это правило звучит несколько странно. Зачем Вам интерфейс, который у Вас уже есть? Вспомните, однако, что все интерфейсы полиморфны относительно IUnknown и многим функциям передается указатель на IUnknown. У этих функций должна быть возможность использовать любой указатель на IUnknown и получить по нему любой другой интерфейс. Это иллюстрирует приведенный ниже пример:

void f(IUnknown* pI)

{

HRESULT hr; IX* pIX = NULL;

// Запросить IX через pI

45

hr = pI->QueryInterface(IID_IX, (void**)&pIX);

// Что-нибудь содержательное

}

void main()

{

//Получаем откуда-то указатель на IX IX* pIX = GetIX();

//Передаем его в функцию

f(pIX);

}

Функция f сможет получить указатель на IX по переданному ей указателю, хотя последний и так указывает на IX.

Вы всегда можете вернуться туда, откуда начали

Если у Вас есть указатель на интерфейс IX и с его помощью Вы успешно получаете интерфейс IY, то можно получить «обратно» интерфейс IX через указатель на IY. Иными словами, независимо от того, какой интерфейс у Вас есть сейчас, можно снова получить интерфейс, с которого Вы начали. Это иллюстрирует следующий код:

void f(IX* pIX)

{

HRESULT hr;

IX* pIX2 = NULL; IY* pIY = NULL;

// Получить IY через IX

hr = pIX->QueryInterface(IID_IY, (void**)&pIY); if (SUCCEEDED(hr))

{

// Получить IX через IY

hr = pIY->QueryInterface(IID_IX, (void**)&pIX2); // QueryInterface должна отработать успешно

assert(SUCCEEDED(hr));

}

}

Если Вы смогли попасть куда-то хоть откуда-нибудь, Вы можете попасть туда откуда угодно

Если Вы можете получить у компонента некоторый интерфейс, то его можно получить с помощью любого из интерфейсов, поддерживаемых компонентом. Если можно получить интерфейс IY через IX, а IZ — через IY, то IZ можно получить и через IX. В программе это выглядит так:

void f(IX* pIX)

{

HRESULT hr;

IY* pIY = NULL;

// Запросить IY у IX

hr = pIX->QueryInterface(IID_IY, (void**)&pIY); if (SUCCEEDED(hr))

{

IZ* pIZ = NULL;

// Запросить IZ и IY

hr = pIY->QueryInterface(IID_IZ, (void**)&pIZ); if (SUCCEEDED(hr))

{

// Запросить IZ у IX

hr = pIX->QueryInterface(IID_IZ, (void**)&pIZ);

// Это должно работать assert(SUCCEEDED(hr));

}

}

}

Это правило делает QueryInterface пригодной для использования. Вообразите, что стало бы, если бы получение указателя на интерфейс зависело от того, через какой интерфейс Вы делаете запрос. Вы редактируете код своего

Соседние файлы в предмете Программирование на C++