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

46

клиента, переставляя две функции местами, — и все перестает работать. Написать клиент для такого компонента было бы практически невозможно.

Общая задача всех приведенных правил — сделать использование QueryInterface простым, логичным, последовательным и определенным. По счастью, при реализации QueryInterface для компонента следовать правилам нетрудно. Если компоненты реализуют QueryInterface корректно, то клиенту не нужды беспокоиться об этих правилах. Пожалуйста, учтите, что простота реализации и использования QueryInterface не снижают значения этой функции. В СОМ нет ничего важнее, чем QueryInterface.

QueryInterface определяет компонент

QueryInterface — это самая важная часть СОМ, поскольку она определяет компонент. Интерфейсы, поддерживаемые компонентом, — это те интерфейсы, указатели на которые возвращает QueryInterface. Их определяет реализация QueryInterface, а не заголовочный файл для класса С++, реализующего компонент. Компонент не определяется и иерархией наследования этого класса С++. Его определяет исключительно

реализация QueryInterface.

Поскольку реализация QueryInterface для клиента невидима, то он не знает, какие интерфейсы поддерживаются. Единственный способ, которым клиент может об этом узнать, — запросить компонент. Такой порядок совершенно отличен от обычного в С++, где клиент имеет заголовочный файл класса и знает обо всех членах последнего. В некотором смысле концепция СОМ больше напоминает знакомство с человеком на вечеринке, чем при приеме на работу. Придя наниматься на работу, человек предоставляет Вам резюме, его характеризующее. Такое резюме похоже на объявление класса С++. На вечеринке же новый знакомый не вручает Вам никакой «сводки данных» о себе; чтобы узнать о нем, Вы задаете вопросы. Это больше похоже на практику СОМ (и на игру Animal).

Вы не можете воспользоваться всеми знаниями сразу

Первым вопросом, который я задал при изучении СОМ, был: «Почему я не могу сразу запросить у компонента все его интерфейсы?». Ответ в духе Дзен гласит: «Что станешь ты делать со списком интерфейсов, поддерживаемых компонентом?» Оказывается, это очень хороший ответ (хотя он и сформулирован как вопрос).

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

СОМ все же предоставляет средство, библиотеки типа (type libraries), для определения интерфейсов, которые предоставляет компонент, во время выполнения. Хотя клиент может использовать библиотеку типа для определения параметров функций некоторого интерфейса, он по-прежнему не знает, как писать программы, использующие эти функции. Эта работа остается программисту. Библиотеки типа будут рассматриваться в гл. 11.

Во многих случаях клиенты могут использовать только компоненты, реализующие определенный набор интерфейсов. Создавать компонент, запрашивать у него интерфейсы по одному и в конце концов выяснить, что он не поддерживает один из нужных, — это пустая трата времени. Чтобы ее избежать, можно определить объединенный набор интерфейсов как категорию компонентов (component category). Затем компоненты могут опубликовать сведения о том, принадлежат ли они к некоторой категории. Эту информацию клиенты могут получить, не создавая компонентов; подробнее об этом будет рассказано в гл. 6.

Теперь позвольте перейти к одному из самых неожиданных применений QueryInterface — работе с новыми версиями компонентов.

Работа с новыми версиями компонентов

Как Вы уже знаете, интерфейсы СОМ неизменны. После того, как интерфейс опубликован и используется какимлибо клиентом, он никогда не меняется. Но что именно я имею в виду, когда говорю, что интерфейсы остаются теми же? У каждого интерфейса имеется уникальный идентификатор интерфейса (IID). Вместо того, чтобы изменять интерфейс фактически нужно создать новый, с новым IID. Если QueryInterface получает запрос со старым IID, она возвращает старый интерфейс. Если же QueryInterface получает запрос с новым IID, то возвращает новый интерфейс. С точки зрения QueryInterface IID и есть интерфейс.

47

QueryMultipleInterfaces

В распределенной СОМ (DCOM) определен новый интерфейс ImultiQI. Он имеет единственную функциючлен — QueryMultipleInterfaces. Эта функция позволяет клиенту запросить у компонента несколько интерфейсов за один вызов. Один вызов QueryMultipleInterfaces заменяет несколько циклов «запрос-ответ» по сети, что повышает производительность.

Итак, интерфейс, соответствующие данному IID, неизменен. Новый интерфейс может наследовать старый или быть совершенно другим. На существующие клиенты это не влияет, так как старый интерфейс не меняется. Новые же клиенты могут использовать как старые, так и новые компоненты, поскольку могут запрашивать как старый, так и новый интерфейс.

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

В качестве примера предположим, что у нас есть программа моделирования полета, названная Pilot, которая использует средства разных поставщиков для моделирования летательных аппаратов. Для того, чтобы работать с Pilot, компонент-«летательный аппарат» должен реализовывать интерфейс IFly. Пусть у одного из поставщиков имеется такой компонент, называемый Bronco и поддерживающий интерфейс IFly. Мы решаем модернизировать Pilot и выпускаем новую версию, FastPilot. FastPilot расширяет набор «поведений» самолета при помощи интерфейса IFastFly, в дополнение к IFly. Компания, продающая Bronco, добавляет интерфейс IFastFly и создает

FastBronco.

FastPilot по-прежнему поддерживает IFly, поэтому, если у пользователя есть копия Bronco, то FastPilot попрежнему может ее использовать. FastPilot будет сначала запрашивать у компонента IFlyFast, а если компонент его не поддерживает, — IFly. FastBronco по-прежнему поддерживает IFly, так что если у кого-то есть старый Pilot, то FastBronco будет работать и с ним. На рис. 3-4 возможные взаимосвязи представлены графически.

Pilot

Bronco

 

pIFly

IFly

FastPilot

FastBronco

 

pIFly

IFly

pIFlyFast

IFlyFast

Рис. 3-4 Различные комбинации старых и новых версий клиентов и компонентов

Врезультате клиент и компонент смогут работать в любом сочетании.

Внекоторых случаях новый компонент или клиент не могут поддерживать обратную совместимость, поскольку та сложна или слишком медленно работает. Однако и тогда обработка версий в СОМ не теряет своей силы. Как и раньше, IID интерфейса определяет его версию. Всякий раз, когда клиент получает интерфейс, он получает корректную версию, поскольку другая версия — это другой интерфейс с другим IID.

Когда нужно создавать новую версию

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

!" число функций в интерфейсе; !" порядок следования функций в интерфейсе; !" число параметров функции; !" типы параметров функции;

!" возможные возвращаемые функцией значения; !" типы возвращаемых значений; !" смысл параметров функции;

48

!" смысл функций интерфейса.

Вообще говоря, любое изменение, которое может нарушить работу любого из существующих клиентов, требует нового интерфейса. (Конечно, если Вы контролируете и клиент, и компонент, степеней свободы больше.)

Имена версий интерфейсов

Если Вы создаете новую версию интерфейса, следует изменить и его имя. Стандартное соглашение СОМ на этот счет заключается в добавлении номера к концу имени. Согласно ему, IFly становится IFly2, а не IFastFly. Конечно, свои собственные интерфейсы Вы можете называть как угодно. Если же интерфейсы принадлежат кому-либо другому, то следует запросить у него разрешение, прежде чем создавать новую версию или присваивать новое имя.

Неявные соглашения

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

Это можно представить себе при помощи аналогии. Юридические договоры должны ясно и четко указывать обязанности сторон. Однако, позже, сколь бы коротким и простым ни был договор, в нем всегда найдется набранное мелким шрифтом. И это обязательно будет что-то, что Вы не считали важным, когда подписывали бумаги, — но что теперь может обойтись в тысячи долларов. Размер шрифта не имеет значения, юридическая сила зависит не от него.

Интерфейс — своего рода форма договора между клиентом и компонентом. Как и во всех договорах, здесь есть кое-что «мелким шрифтом». В случае интерфейсов это способ их использования. Способ, которым клиент использует функции интерфейса, составляет предмет договора с компонентом, реализующим интерфейс. Если компонент изменяет реализацию интерфейса, он должен гарантировать, что клиент сможет пользоваться функциями прежним способом. В противном случае клиент не будет работать, и его придется перекомпилировать. Пусть, например, клиент вызывает функции Foo1, Foo2 и Foo3 в этом порядке. Если компонент изменится так, что первой нужно будет вызывать Foo3, он нарушит неявное соглашение, определяющее способ и последовательность использования функций интерфейса.

Все интерфейсы «заключают» неявные соглашения. Это становится проблемой, только если мы хотим реализовать интерфейс способом, не совместимым с принятым порядком. Чтобы избежать нарушения неявного соглашения, у Вас есть два варианта. Первый заключается в том, чтобы сделать интерфейс работоспособным независимо от последовательности и способа вызова его функций-членов. Второй вариант — заставить всех клиентов использовать интерфейс одинаково и документировать этот порядок. Теперь, если компонент изменяется и нарушает работу клиента, он разрывает явный договор, а не неявный. Оба решения требуют огромной предусмотрительности и тщательного планирования.

«У Вас две ноги?»

Теперь Вы знаете, «что за животное» СОМ. QueryInterface — это единственная особенность, которая в действительности отличает создание компонентов СОМ от написания классов С++. QueryInterface дает СОМ большую часть ее гибкости и способности к инкапсуляции. QueryInterface определяет «поведения», поддерживаемые компонентом во время выполнения, и максимально использует силу динамической компоновки. Полностью скрывая детали компонента о клиента, QueryInterface максимально защищает последний от влияния возможных изменений компонента. QueryInterface также является «становым хребтом» элегантного и прозрачного механизма работы с версиями. Этот механизм позволяет старым и новым компонентам взаимодействовать и работать вместе.

В этой главе Вы также познакомились с IUnknown — корневым интерфейсом, поддерживаемым всеми другими. QueryInterface — это лишь одна из трех функций, составляющих IUnknown. В следующей главе мы увидим, как совместное использование двух его функций-членов, AddRef и Release, заменяет оператор delete (который мы использовали в предыдущих примерах). Но, может, перед этим немного поиграем в Animal?

Имеете ли Вы отношение к удалению компонентов из памяти? > да Имеете ли Вы отношение к подсчету ссылок?

> да

Вы AddRef?

> нет Кто же Вы?

> Release

49

Чем Вы отличаетесь от AddRef? > уменьшаю счетчик ссылок Спасибо

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