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

183

Использование IDispatch

Рассмотрим следующую программу на Visual Basic:

Dim Cmpnt As Object

Set Cmpnt = CreateObject(“InsideCOM.Chap11.Cmpnt11”)

Cmpnt.Fx

Эта маленькая программа создает компонент СОМ и вызывает функцию Fx через интерфейс IDispatch, реализованный компонентом. Взглянем теперь на аналогичную программу на С++. Во-первых, необходимо создать компонент по его ProgID. (Эта процедура обсуждалась в гл. 6.) Приведенный ниже код, взятый из файла DCLIENT.CPP примера этой главы (который находится на прилагающемся к книге диске), создает компонент, используя ProgID. (Для ясности я убрал проверки ошибок.)

//Инициализировать библиотеку OLE. HRESULT hr = OleInitialize(NULL);

//Получить CLSID приложения.

wchar_t progid[] = L”InsideCOM.Chap11”; CLSID clsid;

hr = ::CLSIDFromProgID(progid, &clsid);

// Создать компонент. IDispatch* pIDispatch = NULL;

hr = ::CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (void**)&pIDispatch);

Чтобы не делать лишнего вызова QueryInterface, я запросил у CoCreateInstance указатель на IDispatch. Теперь, имея этот указатель, мы можем получить DISPID функции Fx. Функция IDispatch::GetIDsOfNames принимает имя функции и в виде строки возвращает соответствующий DISPID:

DISPID dispid;

 

OLECHAR* name = L”Fx”;

 

pIDispatch->GetIDsOfNames(

// Должно быть IID_NULL

IID_NULL,

&name,

// Имя функции

1,

// Число имен

GetUserDefaultLCID(),

// Информация локализации

&dispid);

// Диспетчерский идентификатор

С точки зрения клиента DISPID — просто средство оптимизации, позволяющее избежать передачи строк. Для сервера же DISPID — идентификатор функции, которую хочет вызвать клиент.

Имея DISPID для Fx, мы можем вызвать эту функцию, передав DISPID IDispatch::Invoke, которая представляет собой сложную функцию. Ниже приведен один из простейших вариантов вызова Invoke. Здесь Fx вызывается без параметров:

// Подготовить аргументы для Fx DISPPARAMS dispparamsNoArgs = {

NULL,

 

 

NULL,

// Ноль аргументов

 

0,

 

0,

// Ноль именованных аргументов

};

 

 

// Простейший вызов Invoke

 

pIDispatch->Invoke(dispid,

// DISPID

 

IID_NULL,

// Должно быть IID_NULL

 

GetUserDefultLCID(),

// Информация локализации

 

DISPATCH_METHOD,

// Метод

 

&dispparamsNoArgs,

// Аргументы метода

 

NULL,

// Результаты

 

NULL,

// Исключение

 

NULL);

// Ошибка в аргументе

Контроллер Автоматизации не обязан что-либо знать о сервере Автоматизации. Контроллеру не нужен заголовочный файл с определением функции Fx. Информация об этой функции не зашита в программу. Сравните это с самим интерфейсом IDispatch, который является интерфейсом СОМ. IDispatch определен в OAIDL.IDL. код вызова членов IDispatch генерируется во время компиляции и остается неизменным. Однако вызываемая функция определяется параметрами Invoke. Эти параметры, как и параметры всех функций могут меняться во время выполнения.

184

Преобразовать приведенный выше фрагмент кода в программу, которая будет вызывать любую функцию без параметров, легко. Просто запросите у пользователя две строки — ProgID и имя функции — и передайте их

CLSIDFromProgID и GetIDsOfNames. Код вызова Invoke останется неизменным.

Сила Invoke в том, что она может использоваться в полиморфно. Любой реализующий ее компонент можно вызывать при помощи одного и того же кода. Однако у этого есть своя цена. Одна из задач IDispatch::Invoke — передача параметров вызываемой функции. Число типов параметров, которые Invoke может передавать, ограничено. Более подробно об этом мы поговорим ниже, в разделе, где будет обсуждаться VARIANT. Но прежде чем поговорить о параметрах функций disp-интерфейсов, давайте рассмотрим параметры самой IDispatch::Invoke.

Параметры Invoke

Рассмотрим параметры функции Invoke более подробно. Первые три параметра объяснить нетрудно. Первый — это DISPID функции, которую хочет вызвать контроллер. Второй параметр зарезервирован и должен быть равен IID_NULL. Третий параметр содержит информацию локализации. Рассмотрим более детально оставшиеся параметры, начиная с четвертого.

Методы и свойства

Все члены интерфейса СОМ — функции. Интерфейсы СОМ, многие классы С++ и даже Win32 API моделируют доступ к переменной с помощью функций «Get» и «Set». Пусть, например, SetVisible делает окно видимым, а GetVisible возвращает текущее состояние видимости окна:

if (pIWindow->GetVisible() == FALSE)

{

pIWindow->SetVsible(TRUE);

}

Но для Visual Basic функций «Get» и «Set» недостаточно. Основная задача Visual Basic — сделать все максимально простым для разработчика. Visual Basic поддерживает понятие свойств (properties). Свойства — это функции «Get/Set», с которыми программист на Visual Basic работает как с переменными. Вместо синтаксиса вызова функции программист использует синтаксис обращения к переменной:

’ Код VB

If Window.Visible = False Then

Window.Visible = True

End If

Атрибуты IDL propget и propput указывают, что данная функция СОМ должна рассматриваться как свойство. Например:

[

object, uuid(D15B6E20-0978-11D0-A6BB-0080C7B2D682), pointer_default(unique),

dual

]

interface IWindow : IDispatch

{

...

[propput]

HRESULT Visible([in] VARIANT_BOOL bVisible); [propget]

HRESULT Visible([out, retval] VARIANT_BOOL* pbVisible);

...

}

Здесь определяется интерфейс со свойством Visible. Функция, помеченная как propput, принимает значение свойства в качестве параметра. Функция помеченная propget, возвращает значение свойства как выходной параметр. Имена свойства и функции совпадают. Когда MIDL генерирует для функций propget и propput заголовочный файл, он присоединяет к имени функции префикс get_ или put_. Следовательно, на С++ эти функции должны вызываться так:

VARIANT_BOOL vb; get_Visible(&vb);

{

put_Visible(VARIANT_TRUE);

}

185

Возможно, Вы уже начали понимать, почему я, программист на С++, не люблю disp-интерфейсы. Что хорошо на Visual Basic, плохо на С++. Я рекомендую Вам предоставлять интерфейс СОМ низкого уровня для пользователей Visual Basic и Java. Такой дуальный интерфейс можно реализовать с помощью интерфейсов СОМ низкого уровня. Писать хорошие интерфейсы достаточно сложно, даже если не пытаться удовлетворить два разных класса разработчиков. Кстати, VARIANT_TRUE — 0xFFFF.

Возможно, Вы удивлены, какое все это имеет отношение к четвертому параметру IDispatch::Invoke. Все просто — одно имя, например Visible, может быть связано с четырьмя разными функциями: нормальной функцией, функцией установки значения свойства, функцией установки значения свойства по ссылке и функцией, которая возвращает значение свойства. У всех этих одноименных функций будет один и тот же DISPID, но реализованы они могут быть совершенно по-разному. Таким образом, Invoke необходимо знать, какую функцию вызывать. Необходимая информация задается одним из следующих значений четвертого параметра:

DISPATCH_METHOD

DISPATCH_PROPERTGET

DISPATCH_PROPERTYPUT

DISPATCH_PROPERTYPUTREF

Параметры функций disp-интерфейсов

Довольно запутанно, не так ли? Пятый параметр IDispatch::Invoke содержит параметры вызываемой функции. Понятнее это будет на примере. Пусть мы вызываем Invoke для установки свойств Visible в True. Аргументы функции, к которой мы обращаемся, передаются в пятом параметре Invoke. Таким образом, нам нужно передать в этом пятом параметре True — новое значение свойства Visible.

Пятый параметр — это структура DISPPARAMS, определение которой таково:

typedef struct tagDISPPARAMS {

// Массив аргументов

VARIANTARG* rgvarg;

DISPID* rgdispidNamedArgs;

// DISPID для именованных аргументов

unsigned int cArgs;

// Число аргументов

unsigned int cNamedArgs;

// Число именованных аргументов

} DISPPARAMS;

 

Visual Basic и disp-интерфейсы поддерживают концепцию именованных аргументов. Именованные аргументы позволяют программисту задавать параметры функции в любом порядке, передавая вместе со значениями параметра его имя. Эта концепция малополезна для программиста на С++, и у нас есть гораздо более важные темы для обсуждения, поэтому я не собираюсь ее здесь рассматривать. В этой книге rgdispidNamedArgs всегда будет равен NULL, а cNamedArgs — 0.

Первый элемент (rgvarg) структуры DISPPARAMS — это массив аргументов. Поле cArgs задает число аргументов в данном массиве. Каждый аргумент имеет тип VARIANTARG, и именно поэтому число типов параметров, которые могут передаваться между контроллером и сервером Автоматизации, ограничено. Функциям disp-интерфейса или дуального интерфейса можно передавать только такие параметры, которые можно поместить в структуру VARIANTARG (поскольку функции в vtbl должны соответствовать функциям, доступным через Invoke).

VARIANTARG — это то же самое, что и VARIANT. Знакомый нам файл IDL Автоматизации OAIDL.IDL дает следующее определение VARIANT:

typedef struct tagVARIANT { VARTYPE vt;

unsigned short wReserved1; unsigned short wReserved2; unsigned short wReserved3;

union {

 

 

Byte

bVal;

// VT_UI1.

Short

iVal;

// VT_I2.

long

lVal;

// VT_I4.

float

fltVal;

// VT_R4.

double

dblVal;

// VT_R8.

VARIANT_BOOL

boolVal;

// VT_BOOL.

SCODE

scode;

// VT_ERROR.

CY

cyVal;

// VT_CY.

DATE

date;

// VT_DATE.

BSTR

bstrVal;

// VT_BSTR.

DECIMAL

FAR* pdecVal

// VT_BYREF|VT_DECIMAL.

IUnknown

FAR* punkVal;

// VT_UNKNOWN.

IDispatch

FAR* pdispVal;

// VT_DISPATCH.

SAFEARRAY

FAR* parray;

// VT_ARRAY|*.

186

Byte

FAR* pbVal;

// VT_BYREF|VT_UI1.

short

FAR* piVal;

// VT_BYREF|VT_I2.

long

FAR* plVal;

// VT_BYREF|VT_I4.

float

FAR* pfltVal;

// VT_BYREF|VT_R4.

double

FAR* pdblVal;

// VT_BYREF|VT_R8.

VARIANT_BOOL

FAR* pboolVal;

// VT_BYREF|VT_BOOL.

SCODE

FAR* pscode;

// VT_BYREF|VT_ERROR.

CY

FAR* pcyVal;

// VT_BYREF|VT_CY.

DATE

FAR* pdate;

// VT_BYREF|VT_DATE.

BSTR

FAR* pbstrVal;

// VT_BYREF|VT_BSTR.

IUnknown

FAR* FAR* ppunkVal;

// VT_BYREF|VT_UNKNOWN.

IDispatch

FAR* FAR* ppdispVal;

// VT_BYREF|VT_DISPATCH.

SAFEARRAY

FAR* FAR* pparray;

// VT_ARRAY|*.

VARIANT

FAR* pvarVal;

// VT_BYREF|VT_VARIANT.

void

FAR* byref;

// Generic ByRef.

char

cVal;

// VT_I1.

unsigned short

uiVal;

// VT_UI2.

unsigned long

ulVal;

// VT_UI4.

int

intVal;

// VT_INT.

unsigned int

uintVal;

// VT_UINT.

char FAR *

pcVal;

// VT_BYREF|VT_I1.

unsigned short FAR *

puiVal;

// VT_BYREF|VT_UI2.

unsigned long FAR *

pulVal;

// VT_BYREF|VT_UI4.

int FAR *

pintVal;

// VT_BYREF|VT_INT.

unsigned int FAR *

puintVal;

//VT_BYREF|VT_UINT.

};

};

Как видите, VARIANT — это просто большое объединение (union) разных типов. VARIANT всегда использовался в Visual Basic для унифицированного хранения переменных разных типов. Идея оказалась настолько хороша, что разработчики Visual Basic решили выпустить ее в свет. Скоро мы рассмотрим, как ее использовать. Для нас, однако, важно то, что disp-интерфейсы и дуальные интерфейсы могут передавать только те типы, которые можно выразить при помощи VARIANT. Теперь продолжим рассмотрение Invoke.

Возврат результатов

Шестой параметр, pVarResult — это указатель на VARIANT, который будет содержать результат выполнения метода (или propget), исполняемого Invoke. Этот параметр может быть равен NULL для методов, не возвращающих значение, а также для propput и propputref.

Исключения

Следующий параметр IDispatch::Invoke — указатель на структуру EXCEPINFO. Если в процессе работы метода или свойства, вызванного с помощью Invoke, возникнет исключение (исключительная ситуация), структура будет заполнена информацией об этой ситуации. Структуры EXCEPINFO используются в тех же случаях, что и исключения в C++.

Ниже приведено определение EXCEPINFO. BSTR — это строка специального формата, о которой мы поговорим далее в этой главе.

typedef struct tagEXCEPINFO {

// Код ошибки

WORD wCode;

WORD wReserved;

// Источник исключительной ситуации

BSTR bstrSource;

BSTR bstrDescription;

// Описание ошибки

BSTR bstrHelpFile;

// Полное имя файла справки

DWORD dwHelpContext;

// Контекст внутри файла справки

ULONG pvReserved;

// Функция для заполнения этой структуры

ULONG pfnDefferedFillIn;

SCODE scode;

// Код возврата

} EXCEPINFO;

 

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

EXCEPINFO:

EXCEPINFO excepinfo;

HRESULT hr = pIDispatch->Invoke(..., &excepinfo); if (FAILED(hr))

{