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

11 глава

Диспетчерские интерфейсы и автоматизация

Как гласит пословица, «есть много способов содрать шкуру с кошки». Поскольку я никогда не пытался обдирать кошек и не нахожу в этом особого смысла, я предпочитаю говорить: «Есть много способов причесать кошку». Полагаю, что большинству кошек моя версия понравится больше. Один мой друг из Ла Гранде, штат Джорджия, использует другую версию этой фразы: «Есть много способов пахнуть как скунс». Если верить его матери, большинство этих способов ему известно. Все это говорит о том, как много можно придумать способов перефразировать поговорку.

В этой главе Вы увидите, что есть и много способов коммуникации между клиентом и компонентом. В предыдущих главах клиент использовал интерфейс СОМ для работы с компонентом напрямую. В этой главе мы рассмотрим Автоматизацию (в прошлом OLE Автоматизацию) — другой способ управления компонентом. Этот способ использует такие приложения, как Microsoft Word и Microsoft Excel, а также интерпретируемые языки типа Visual Basic и Java.

Автоматизация облегчает интерпретируемым языкам и макроязыкам доступ к компонентам СОМ, а также облегчает написание самих компонентов на этих языках. В Автоматизации делается упор на проверку типов во время выполнения за счет снижения скорости выполнения и проверки типов во время компиляции. Но если Автоматизация проста для программиста на макроязыке, то от разработчика на С++ она требует гораздо больше труда. Во многих случаях Автоматизация заменяет код, генерируемый компилятором, кодом, который написан разработчиком.

Автоматизация — не пристройка к СОМ, а надстройка над нею. Сервер Автоматизации (Automation server) — это компонент СОМ, который реализует интерфейс IDispatch. Контролер Автоматизации (Automation Controller)

— это клиент СОМ, взаимодействующий с сервером Автоматизации через интерфейс IDispatch. Контролер Автоматизации не вызывает функции сервера Автоматизации напрямую. Вместо этого он использует методы интерфейса IDispatch для неявного вызова функций сервера Автоматизации.

Интерфейс IDispatch, как и вся Автоматизация, разрабатывался для Visual Basic — чтобы его можно было использовать для автоматизации таких приложений, как Microsoft Word и Microsoft Excel. В конце концов из

Visual Basic вырос Visual Basic for Applications — язык для Microsoft Office. Подмножество Visual Basic for Applications — Visual Basic Scripting Edition (VBScript) — можно использовать для автоматизации элементов управления на страницах Web. Версия 5.0 Microsoft Developer Studio использует VBScript в качестве своего макроязыка.

Практически любой сервис, который можно представить через интерфейсы СОМ, можно предоставить и при помощи IDispatch. Из этого следует, что IDispatch и Автоматизация — это не менее (а может быть, и более) широкая тема, чем СОМ. Поскольку эта книга посвящена все-таки СОМ, мы рассмотрим Автоматизацию только частично. Но и этого все еще большая область: IDispatch, disp-интерфейсы, дуальные интерфейсы, библиотеки типа, IDL, VARIANT, BSTR и многое другое. По счастью, именно эти вопросы наиболее важны при программировании Автоматизации на С++.

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

Новый способ общения

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

180

Прежде чем подробно рассматривать IDispatch, давайте разберемся, как он может поддерживать столь много функций; для этого мы сравним его со специализированными интерфейсами СОМ (которые он может заменить).

Старый способ общения

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

pIX->FxStringOut(msg);

как

(*(pIX->pvtbl[IndexOfFxStringOut]))(pIX, msg);

где pvtbl — это указатель на ytbl данного класса, а IndexOfFxStringOut — индекс указателя на функцию FxStringOut в таблице указателей на функции. Все это происходит автоматически — Вы этого не знаете или, в большинстве случаев, Вас это не беспокоит.

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

Когда макроязык вызывает функцию компонента СОМ, у него есть три элемента информации: ProgID компонента, реализующего функцию, имя функции и ее аргументы. Нам нужен простой способ, чтобы интерпретатор макроязыка мог вызывать функцию по ее имени. Именно для этого и служит IDispatch.

IDispatch, или «Я диспетчер, ты диспетчер…»*

Говоря попросту, IDipatch принимает имя функции и выполняет ее. Описание IDipatch на IDL, взятое из файла OAIDL.IDL, приведено ниже:

interface IDispatch : IUnknown

{

HRESULT GetTypeInfoCount([out] UINT * pctinfo);

HRESULT GetTypeInfo([in] UINT iTInfo, [in] LCID lcid,

[out] ItypeInfo ** ppTInfo);

HRESULT GetIDsOfNames( [in] REFIID riid,

[in, size_is(cNames)] LPOLESTR * rgszNames, [in] UINT cNames,

[in] LCID lcid,

[out, size_is(cNames)] DISPID * rgDispId); HRESULT Invoke([in] DISPID dispIdMember,

[in] REFIID riid, [in] LCID lcid, [in] WORD wFlags,

[in, out] DISPPARAMS * pDispParams, [out] VARIANT * pVarResult,

[out] EXCEPINFO * pExcepInfo, [out] UINT * puArgErr);

};

Наиболее интересны в этом интерфейсе функции GetIDsOfNames и Invoke. Первая принимает имя функции и возвращает ее диспетчерский идентификатор, или DISPID. DISPID — это не GUID, а просто длинное целое (LONG), идентифицирующее функцию. DISPID не уникальны (за исключением данной реализации IDipatch). У каждой реализации IDipatch имеется собственный IID (некоторые называют его DIID).

* В оригинале «IDispatch, You Dispatch, We Dispatch». — Прим. перев.

181

Для вызова функции контроллер автоматизации передает ее DISPID функции-члену Invoke. Последняя использует DISPID как индекс в массиве указателей на функции, что очень похоже на обычные интерфейсы СОМ. Однако сервер Автоматизации не обязан реализовывать Invoke именно так. Простой сервер Автоматизации может использовать оператор switch, который выполняет разный код в зависимости от значения DISPID. Именно так реализовывали оконные процедуры, прежде чем стала популярна MFC.

У оконных процедур и IDispatch::Invoke есть другие общие черты. Как окно ассоциируется с оконной процедурой, так и сервер Автоматизации ассоциируется с функцией IDispatch::Invoke. Microsoft Windows посылает оконной процедуре сообщения; контроллер автоматизации посылает IDispatch::Invoke разные DISPID. Поведение оконной процедуры определяется получаемыми сообщениями; поведение Invoke — получаемыми

DISPID.

Способ действий IDispatch::Invoke напоминает и vtbl. Invoke реализует набор функций, доступ к которым осуществляется по индексу. Таблица vtbl — массив указателей на функции, обращение к которым также идет по индексу. Но если vtbl работает автоматически за счет магии компилятора С++, то Invoke работает благодаря тяжкому труду программиста. Однако в С++ vtbl статические, и компилятор работает только во время компиляции. Если программисту С++ необходимо порождать vtbl во время выполнения, он предоставлен самому себе. С другой стороны, легко создать универсальную реализацию Invoke, которая сможет «на лету» адаптироваться для реализации самых разных сервисов.

Disp-интерфейсы

У реализации IDispatch::Invoke есть еще одно сходство с vtbl. Обе они определяют интерфейс. Набор функций,

реализованных с помощью IDispatch::Invoke, называется диспетчерским интерфейсом (dispatch interface) или,

короче, disp-интерфейсом (dispinterface). По определению, интерфейс СОМ — это указатель на массив указателей на функции, первыми тремя из которых являются QueryInterface, AddRef и Release. В соответствии с более общим определением, интерфейс — это набор функций и переменных, посредством которых взаимодействуют две части программы. Реализация IDispatch::Invoke определяет набор функций, посредством которых взаимодействуют сервер и контроллер Автоматизации. Как нетрудно видеть, функции, реализованные Invoke, образуют интерфейс, но не интерфейс СОМ.

На рис. 11-1 диспетчерский интерфейс представлен графически. Слева изображен традиционный интерфейс СОМ

IDispatch реализованный при помощи vtbl. Справа показан disp-интерфейс. Центральную роль в dispинтерфейсе играют DISPID, распознаваемые IDispatch::Invoke. На рисунке показана одна из возможных реализаций Invoke и GetIDsOfNames: массив имен функций и массив указателей на функции, индексируемые DISPID. Это только один способ. Для больших disp-интерфейсов GetIDsOfNames работает быстрее, если передаваемое ей имя используется в качестве ключа хеш-таблицы.

 

Интерфейс IDispatch

Disp-интерфейс

IDispatch*

pVtbl

&QueryInterface

DISPID

Имя

pIDispatch

 

1

"Foo"

 

 

 

 

 

&AddRef

 

 

 

 

2

"Bar"

 

 

&Release

функция

 

 

3

"FooBar"

 

 

&GetTypeInfoCount

GetDsOfNames

 

 

 

 

Указатель

 

 

&GetTypeInfo

 

 

 

 

DISPID

на

 

 

&GetDsOfNames

функцию

 

 

&Invoke

функция

1

&Foo

 

 

2

&Bar

 

 

 

Invoke

 

 

 

 

3

&FooBar

Рис. 11-1. Disp-интерфейсы реализуются с помощью IDispatch и не являются интерфейсами СОМ. На этом рисунке представлена только одна из возможных реализаций IDispatch::Invoke.

Конечно, для реализации IDispatch::Invoke можно использовать и интерфейс СОМ (рис. 11-2).

Дуальные интерфейсы

На рис. 11-2 представлен не единственный способ реализации disp-интерфейса при помощи интерфейса СОМ. Другой метод, показанный на рис. 11-3, состоит в том, чтобы интерфейс СОМ, реализующий IDispatch::Invoke, наследовал не IUnknown, а IDispatch. Так реализуют интерфейсы, называемые дуальными интерфейсами (dual interface). Дуальный интерфейс — это disp-интерфейс, все члены которого, доступные через Invoke, доступны и напрямую через vtbl.

182

 

Интерфейс IDispatch

Disp-интерфейс

IDispatch*

pVtbl

&QueryInterface

DISPID

Имя

pIDispatch

 

1

"Foo"

 

 

 

 

 

&AddRef

 

 

 

 

2

"Bar"

 

 

&Release

функция

 

 

3

"FooBar"

 

 

&GetTypeInfoCount

GetDsOfNames

 

 

 

 

 

 

 

&GetTypeInfo

Интерфейс FooBar

 

 

&GetDsOfNames

функция

pVtbr

&Foo

 

 

&Invoke

 

 

Invoke

 

&Bar

 

 

 

 

 

 

 

 

 

 

&FooBar

Рис. 11-2 Реализация IDispatch::Invoke с помощью интерфейса СОМ.

 

Интерфейс FooBar наследует

Disp-интерфейс

 

интерфейсу IDispatch

 

 

 

 

IDispatch*

pVtbl

&QueryInterface

DISPID

Имя

pIDispatch

 

1

"Foo"

 

 

 

 

 

&AddRef

 

 

 

 

2

"Bar"

 

 

&Release

функция

 

 

3

"FooBar"

 

 

&GetTypeInfoCount

GetDsOfNames

 

 

 

 

 

 

 

&GetTypeInfo

 

 

 

 

 

&GetDsOfNames

функция

 

 

 

 

&Invoke

 

 

 

 

Invoke

 

 

 

 

&Foo

 

 

 

 

 

&Bar

 

 

 

 

 

&FooBar

 

 

 

Рис. 11-3. Дуальный интерфейс — это интерфейс СОМ, который наследует IDispatch. Доступ к членам такого интерфейса возможен и через Invoke, и через vtbl.

Дуальные интерфейсы предпочтительны для реализации disp-интерфейсов. Они позволяют программистам на С++ работать через vtbl; такие вызовы не только легче реализовать на С++, но они и быстрее выполняются. Макро- и интерпретируемые языки также могут использовать сервисы компонентов, реализующих дуальные интерфейсы, применяя Invoke вместо вызова через vtbl. Программа на Visual Basic может работать с дуальным интерфейсом как с disp-интерфейсом, так и через vtbl. Если Вы объявили тип переменной Visual Basic как Object, то работа идет через disp-интерфейс:

Dim doc As Object

Set doc = Application.ActiveDocument doc.Activate

Если переменная имеет тип конкретного объекта, то Visual Basic выполняет вызов через vtbl:

Dim doc As Document

Set doc = Application.ActiveDocument

Doc.Activate

Однако если что-то выглядит слишком хорошо, чтобы быть правдой, — вероятно, так оно и есть. Наверное, Вы удивитесь, узнав, что у дуальных интерфейсов есть недостатки. С точки зрения Visual Basic, их и нет. Но с точки зрения контроллера Автоматизации, написанного на С++, их несколько. Основной из них — ограничения на типы параметров.

Прежде чем обсудить ограниченность набора типов, допустимых для параметров disp-интерфейсов и дуальных интерфейсов, рассмотрим, как вызывается disp-интерфейс на С++.

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