VARIANT var;
VariantInit(&var);
// initialize VARIANT
// инициализируем VARIANT
V_VT(&var) = VT_I4;
// set discriminator
// устанавливаем дискриминатор
V_I4(&var) = 100;
// set union
// устанавливаем объединение
HRESULT hr = pItf->UseIt(var);
// use VARIANT
// используем VARIANT
VariantClear(&var);
// free any resources in VARIANT
// освобождаем все ресурсы VARIANT
Отметим, что этот фрагмент кода использует макросы стандартного аксессора (accessor) для доступа к элементам данных VARIANT. Две следующие строки
V_VT(&var) = VT_I4;
V_I4(&var) = 100;
эквивалентны коду, который обращается к самим элементам данных:
var.vt = VT_I4;
var.lVal = 100;
Первый вариант предпочтительнее, так как он может компилироваться на С-трансляторах, которые не поддерживают неименованных объединений.
Ниже приведен пример того, как с помощью приведенной выше технологии реализация метода использует параметр VARIANT в качестве строки:
STDMETHODIMP MyClass::UseIt( /*[in] */ VARIANT var)
{
// declare and init a second VARIANT
// объявляем и инициализируем второй VARIANT
VARIANT var2;
VariantInit(&var2);
// convert var to a BSTR and store it in var2
// преобразуем переменную в BSTR и заносим ее в var2
HRESULT hr = VariantChangeType(&var2, &var, 0, VT_BSTR);
// use the string
// используем строку
if (SUCCEEDED(hr))
{
ustrcpy(m_szSomeDataMember, SAFEBSTR(V_BSTR(&var2)));
// free any resources held by var2
// освобождаем все ресурсы, поддерживаемые var2
VariantClear(&var2);
}
return hr;
}
Отметим, что API-процедура VariantChangeType способна осуществлять сложное преобразование любого переданного клиентом типа из VARIANT в нужный тип (в данном случае BSTR).
Один из последних типов данных, который вызывает дискуссию, – это интерфейс СОМ. Интерфейсы СОМ могут быть переданы в качестве параметров метода одним из двух способов. Если тип интерфейсного указателя известен на этапе проектирования, то тип интерфейса может быть объявлен статически:
HRESULT GetObject([out] IDog **ppDog);
Если же тип на этапе проектирования неизвестен, то разработчик интерфейса может дать пользователю возможность задать тип на этапе выполнения. Для поддержки динамически типизируемых интерфейсов в IDL имеется атрибут [iid_is]:
HRESULT GetObject([in] REFIID riid, [out, iid_is(riid)] IUnknown **ppUnk);
Хотя эта форма будет работать вполне хорошо, следующий вариант предпочтительнее из-за его подобия с QueryInterface:
HRESULT GetObject([in] REFIID riid, [out, iid_is(riid)] void **ppv);
Атрибут [iid_is] можно использовать как с параметрами [in], так и [out] для типов IUnknown * или void *. Для того чтобы использовать параметр интерфейса с динамически типизируемым типом, необходимо просто установить IID указателя требуемого типа:
IDog *pDog = 0; HRESULT hr = pItf->GetObject(IID_IDog, (void**)&pDog);
Соответствующая реализация для инициализации этого параметра просто использовала бы метод QueryInterface для нужного объекта:
STDMETHODIMP Class::GetObject(REFIID riid, void **ppv)
{
extern IUnknown * g_pUnkTheDog;
return g_pUnkTheDog->QueryInterface(riid, ppv);
}
Для уменьшения количества дополнительных вызовов методов между клиентом и объектом указатели интерфейса с динамически типизируемым типом должны всегда использоваться вместо указателей интерфейса со статически типизируемым типом IUnknown.
Атрибуты и свойства
Иногда бывает полезно показать, что объект имеет некие открытые свойства, которые могут быть доступны и/или которые можно модифицировать через СОМ-интерфейс. СОМ IDL позволяет аннотировать методы интерфейса с тем, чтобы данный метод либо модифицировал, либо читал именованный атрибут объекта. Рассмотрим такое определение интерфейса:
[ object, uuid(0BB3DAE1-11F4-11d1-8C84-0080C73925BA) ]
interface ICollie : IDog
{
// Age is a read-only property
// Age (возраст) – это свойство только для чтения
[propget] HRESULT Age([out, retval] long *pVal);
// HairCount is a read/write property
// HairCount (счетчик волос) – свойство для чтения/записи
[propget] HRESULT HairCount([out, retval] long *pVal);
[propput] HRESULT HairCount([in] long val);
// CurrentThought is a write-only property
// CurrentThought (текущая мысль) – свойство только для записи
[propput] HRESULT CurrentThought([in] BSTR val);
}
Использование атрибутов [propget] и [propput] информирует компилятор IDL, что методы, которые ему соответствуют, должны быть отображены в преобразователи свойств (property mutators) или в аксессоры на языках, явно поддерживающих свойства. Применительно к Visual Basic это означает, что элементами Age, HairCount и CurrentThought можно манипулировать, используя тот же синтаксис, как при обращении к элементам структуры:
Sub UseCollie(fido as ICollie)
fido.HairCount = fido.HairCount – (fido.Age * 1000)
fido.CurrentThought = «I wish I had a bone»
End Sub
С++-отображение этого интерфейса просто прибавляет к именам методов конструкции put или get, чтобы подсказать программисту, что обращение относится к свойству:
void UseCollie(ICollie *pFido)
{
long n1, n2;
HRESULT hr = pFido->getHairCount(&n1);
assert(SUCCEEDED(hr));
hr = pFido->getAge(&n2);
assert(SUCCEEDED(hr));
hr = pFido->putHairCount(n1 – (n2 * 1000)): assert(SUCCEEDED(hr));
BSTR bstr = SysAllocString(OLESTR(«I wish I had a bone»));
hr = pFido->putCurrentThought(bstr);
assert(SUCCEEDED(hr));
SysFreeString(bstr);
}
Хотя свойства напрямую не обеспечивают развития, они полезны для выполнения точных преобразований на те языки, которые их поддерживают[1].
Исключения
СОМ имеет специфическую поддержку выбрасывания (throwing) исключительных ситуаций из реализации методов. Поскольку в языке C++ не существует двоичного стандарта для исключений, СОМ предлагает явные API-функции для выбрасывания и перехвата объектов СОМ-исключений:
// throw an exception
// возбуждаем исключения
HRESULT SetErrorInfo([in] ULONG reserved, //
m.b.z. [in] IErrorlnfo *pei);
// catch an exception
// перехватываем исключение
HRESULT GetErrorInfo([in] ULONG reserved, // m.b.z.
[out] IErrorInfo **ppei);
Процедура SetErrorInfo вызывается из реализации метода, чтобы связать объект исключения с текущим логическим потоком (logical thread)[1]. GetErrorInfo выбирает объект исключения из текущего логического потока и сбрасывает исключение, так что следующие вызовы GetErrorInfo возвратят SFALSE, показывая тем самым, что необработанных исключений нет. Как следует из приведенных ниже подпрограмм, объекты исключений должны поддерживать по крайней мере интерфейс IErrorInfo:
[ object, uuid(1CF2B120-547D-101B-8E65-08002B2BD119) ]
interface IErrorInfo: IUnknown
{
// get IID of interface that threw exception
// получаем IID того интерфейса, который возбудил исключение
HRESULT GetGUID([out] GUID * pGUID);
// get class name of object that threw exception
// получаем имя класса того объекта, который возбудил исключение
HRESULT GetSource([out] BSTR * pBstrSource);
// get human-readable description of exception
// получаем читабельное описание исключения
HRESULT GetDescription([out] BSTR * pBstrDescription);
// get WinHelp filename of documentation of error
// получаем имя файла WinHelp, содержащего документацию об ошибке
HRESULT GetHelpFile([out] BSTR * pBstrHelpFile);
// get WinHelp context ID for documentation of error
// получаем контекстный идентификатор WinHelp для документации ошибки
HRESULT GetHelpContext([out] DWORD * pdwHelpContext);
1 Пакет Direct-to-COM фирмы Microsoft позволяет клиентам использовать свойства как открытые элементы данных интерфейса с помощью некоего очень хитрого механизма.