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

187

// Ошибка при вызове Invoke if (hr == DISP_E_EXCEPTION)

{

//Метод сгенерировал исключение.

//Сервер может отложить заполнение EXCEPTINFO.

if (excepinfo.pfnDefferedFillIn != NULL)

{

// Заполнить структуру EXCEPTINFO (*(excepinfo.pfnDefferedFillIn)(&excepinfo);

}

strstream sout;

sout << “Информация об исключительной ситуации в компоненте:”

<<endl

<<“ Источник: ” << excepinfo.bstrSource << endl

<<“ Описание: ” << excepinfo.bstrDescription

<<ends;

trace(sout.str());

}

}

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

Если возвращаемое значение IDispatch::Invoke равно либо DISP_E_PARAMNOTFOUND, либо DISP_E_TYPEMISMATCH, то индекс аргумента, вызвавшего ошибку, возвращается в последнем параметре — puArgErr.

Теперь, познакомившись со всеми параметрами Invoke, давайте рассмотрим еще один пример вызова функции disp-интерфейса. Затем более подробно поговорим о VARIANT, а также рассмотрим два типа, которые могут содержаться в VARIANT: BSTR и SAFEARRAY.

Примеры

Код примера этой главы содержит компонент, который реализует дуальный интерфейс IX. Весь код можно найти на прилагающемся к книге диске. Для компиляции при помощи Microsoft Visual C++ воспользуйтесь командой:

nmake –f makefile

По этой команде будут построены версии компонента внутри и вне процесса. Интерфейс IX описан в SERVER.IDL так:

// Interface IX

[

object,

uuid(32BB8326-B41B-11CF-A6BB-0080C7B2D682), helpstring("Интерфейс IX"),

pointer_default(unique), dual,

oleautomation

]

interface IX : IDispatch

{

import "oaidl.idl";

HRESULT Fx();

HRESULT FxStringIn([in] BSTR bstrIn);

HRESULT FxStringOut([out, retval] BSTR* pbstrOut); HRESULT FxFakeError();

};

С этим компонентом могут работать два клиента. Клиент, содержащийся в файле CLIENT.CPP, подключается к компоненту с помощью vtbl, как мы делали в предыдущих главах. Клиент же из файла DCLIENT.CPP работает через disp-интерфейс. Ранее в этой главе мы уже видели, как он вызывает функцию Fx. Теперь посмотрим на вызов функции FxStringIn. Для большей ясности я убрал из кода обработку ошибок:

trace("Получить DispID метода \"FxStringIn\"."); name = L"FxStringIn";

hr = pIDispatch->GetIDsOfNames(IID_NULL, &name, 1,

GetUserDefaultLCID(),

188

&dispid);

//Передать компоненту следующую строку wchar_t wszIn[] = L"Это тестовая строка";

//Преобразовать строку Unicode в BSTR BSTR bstrIn;

bstrIn = ::SysAllocString(wszIn);

//Подготовить параметры и осуществить вызов

//Выделить и инициализировать аргумент VARIANT

VARIANTARG varg;

::VariantInit(&varg);// Инициализировать VARIANT.

varg.vt = VT_BSTR;

// Тип данных VARIANT

varg.bstrVal = bstrIn;

// Данные для VARIANT

// Заполнить структуру DISPPARAMS

DISPPARAMS param;

// Один аргумент

param.cArgs = 1;

param.rgvarg = &varg;

// Указатель на аргумент

param.cNamedArgs = 0;

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

param.rgdispidNamedArgs = NULL;

trace("Вызвать метод \"FxStringIn\"."); hr = pIDispatch->Invoke(dispid,

IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, &param,

NULL,

NULL,

NULL);

// Очистка

::SysFreeString(bstrIn);

На заполнение структур VARIANTARG и DISPPARAMS может уйти много строк. К счастью, Вы можете написать вспомогательные функции, которые значительно упростят вызов Invoke. Некоторые подобные функции можно найти внутри MFC. Кроме того, ClassWizard генерирует для disp-интерфейсов класс-оболочку C++. Подобные классы содержат удобные для работы на С++ функции, которые преобразуют свои параметры в формат, необходимый для вызова Invoke.

Давайте воспользуемся случаем более подробно рассмотреть тип VARIANT. При рассмотрении типов мы также кратко познакомимся с типами BSTR и SAFEARRAY.

Тип VARIANT

Мы уже видели, как выглядит структура VARIANT (или VARIANTARG). Теперь давайте несколько подробнее рассмотрим, как она используется. Как видно из предыдущего фрагмента кода, структура VARIANT инициализируется при помощи VariantInit. Эта функция устанавливает поле vt в VT_EMPTY. После вызова VariantInit поле vt используется для указания типа данных, хранящихся в объединении VARIANT. В предыдущем примере мы сохраняли BSTR и поэтому использовали поле bstrVal.

Позднее связывание

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

Возможно, Вы не обратили внимания, но мы не предоставляли Visual Basic ничего, эквивалентного заголовочному файлу C++. Для того, чтобы разрешить программе вызвать метод диспетчерского интерфейса, Visual Basic не требуется знание аргументов этого метода. Достигается это при помощи структуры VARIANT. Пусть в программе на Visual Basic имеется следующий фрагмент:

Dim Bullwinkle As Object

Set Bullwinkle = CreateObject(“TalkingMoose”)

189

Bullwinkle.PullFromHat 1, “Topolino”

Visual Basic не требуется знать о Bullwinkle ничего, кроме того, что тот поддерживает IDispatch. Поскольку же Bullwinkle поддерживает IDispatch, Visual Basic может получить DISPID для PullFromHat с помощью вызова IDispatch::GetIDsOfNames. Но у него нет никакой информации об аргументах PullFromHat. Здесь можно прибегнуть к помощи библиотеки типа, независимого от языка программирования эквивалента заголовочного файла С++. Мы будем рассматривать библиотеки типа далее в этой главе.

Но на самом деле Visual Basic не требует, чтобы ему сообщили допустимые типы параметров (через заголовочный файл или некий его эквивалент). Он может взять аргументы, введенные пользователем, и «засунуть» их в VARIANT. В предыдущем примере Visual Basic может предположить, что тип первого параметра

long, а второго — BSTR. Затем созданные таким образом «варианты» передаются функции Invoke. Если типы параметров не совпадают, сервер Автоматизации возвратит ошибки, возможно, вместе с индексом неправильного параметра. Конечно, программисту в любом случае необходима некая документация функций, чтобы знать, как их вызывать, но самой программе никакая информация о типе не требуется. Использование VARIANT позволяет практически полностью отказаться от статической проверки типов — за счет того, что компонент будет проверять их во время выполнения. Это более похоже на Smalltalk, где нет проверки типов, чем на строгую типизацию С++.

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

Преобразование типов

Если хорошие disp-интерфейсы возвращают код ошибки при получении параметров неадекватного типа, то очень хорошие выполняют преобразование типа полученные аргументов за программиста. Возьмем функцию PullFromHat из предыдущего фрагмента. Visual Basic мог предположить, что функция принимает long и BSTR. Но может оказаться, что на самом деле функция принимает не long, а double. Disp-интерфейс должен уметь выполнять такое преобразование автоматически. Кроме того, disp-интерфейсы должны выполнять преобразование в BSTR и из BSTR. Например, если функция установки значения свойства, описанная в IDL так:

[propput] HRESULT Title([in] BSTR bstrTitle);

вызывается следующим кодом на Visual Basic:

component.Title = 100

то эта функция должна преобразовать число 100 в BSTR и использовать результат преобразования в качестве заголовка (title). И, наоборот, функция установки значения свойства:

[propput] HRESULT Age([in] short sAge);

должна быть способна корректно выполнить следующий вызов из Visual Basic:

component.Age = “16”

Я не сомневаюсь, что у Вас нет никакого желания писать код этих преобразований. Даже если у Вас оно есть, то у других его нет. Хуже того, если преобразования будут писать все, все преобразования будут разными. В результате какие-то методы и свойства будут выполнять преобразования одним способом, а какие-то — другим. Поэтому Автоматизация предоставляет функцию VariantChangeType, которая выполняет преобразование за Вас:

HRESULT VariantChangeType(

 

// Преобразованное значение

VARIANTARG*

pVarDest,

VARIANTARG*

pVarSrc,

// Исходное значение

unsigned short

wFlags,

// Целевой тип преобразования

VARTYPE

vtNew

}

 

 

Пользоваться этой функцией очень легко. Например, приведенная ниже процедура преобразует VARIANT в double с помощью VariantChangeType:

BOOL VariantToDouble(VARIANTARG* pvarSrc, double dp)

{

VARIANTARG varDest; VariantInit(&varDest);

HRESULT hr = VariantChangeType(&varDest, pvarSrc,

0, VT_R8);

190

if (FAILED(hr))

{

return FALSE;

}

*pd = varDest.dblVal;

return TRUE;

}

Необязательные аргументы

Метод disp-интерфейса может иметь необязательные аргументы. Если Вы не хотите задавать значение такого аргумента, просто передайте вместо него VARIANT с полем vt, установленным в VT_ERROR, и полем scode, равным DISP_E_PARAMNOTFOUND. В этом случае вызываемый метод должен использовать собственное значени по умолчанию.

Теперь давайте рассмотрим BSTR.

Тип данных BSTR

BSTR, сокращение от Basic STRing или Binary STRing (в зависимости от того, кого Вы спросите) — это указатель на строку символов Unicode. У BSTR есть три интересных особенности. Во-первых, в BSTR хранится число символов строки. Вторая важная особенность — то, что число хранится перед самим массивом символов (рис. 11- 4). Следовательно, нельзя объявить переменную типа BSTR и инициализировать ее массивом символов:

BSTR bstr = L“Где же счетчик?”;

// Неправильно

Поскольку при этом не будет инициализирован счетчик. Вместо этого следует использовать функцию API Win32

SysAllocString:

wchar_t wsz[] = L“Вот где счетчик”; BSTR bstr;

bstr = SysAllocString(wsz);

BSTR

Массив символов Unicode

 

Счетчик символов 'a' 'b' 'c' '\0' 'd' 'd' 'f' '\0' 'A' 'B' 'X' 'Y' 'Z' '\0'

Может содержать несколько нулевых символов

Рис. 11-4 Счетчик символов хранится перед тем участком памяти, на который указывает BSTR

По окончании использования BSTR следует освободить с помощью SysFreeString. Преобразовать BSTR обратно в строку wchar_t легко; в конце концов, BSTR указывает на начало массива wchar_t. Но у BSTR есть и третья интересная черта — в строке может содержаться несколько символов ‘\0’. Следовательно, Вы должны писать код, готовый к обработке нескольких символов ‘\0’, если для Вашей функции это имеет смысл.

Тип данных SAFEARRAY

Другой специальный тип данных, который можно передавать disp-интерфейсу, — SAFEARRAY. Как следует из названия*, это массив, содержащий информацию о своих границах. Ниже приведено объявление из OAIDL.IDL:

typedef struct tagSAFEARRAY {

// Число измерений

unsigned short cDims;

unsigned short fFeatures;

// Размер каждого элемента

unsigned long cbElements;

unsigned long clocks;

// Счетчик блокировок

BYTE* pvData;

// Указатель на данные

[size_is(cDims) SAFEARRAYBOUND rgsabound[];

} SAFEARRAY;

 

typedef struct tagSAFEARRAYBOUND {

// Число элементов в данном измерении

ULONG cElements;

LONG lLBound;

// Нижняя граница по данному измерению

} SAFEARRAYBOUND;

 

Поле fFeatures описывает, какого типа данные хранятся в SAFEARRAY. Возможны следующие значения:

* «Безопасный массив». — Прим.перев.

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