Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Учебное пособие 1877

.pdf
Скачиваний:
5
Добавлен:
30.04.2022
Размер:
2.61 Mб
Скачать

DECIMAL – 96-битный (12 байтов) тип, предназначенный для хранения чисел со знаком и переменной плавающей точкой (от 0 до 28 разрядов).

DATE – 64-битное число с плавающей точкой, используемое для хранения даты.

IDispatch * – Указатель на dispили dual-интерфейс.

IUnknown * – Указатель на IUnknown. Используется для передачи обыкновенных интерфейсов (не dispили dualинтерфейсов).

BSTR – basic-строка или бинарная срока, на Win32платформах указатель на строку Unicode-символов. Переменная этого типа хранит не только саму строку, но и ее длину. Это позволяет помещать в тело строки любые символы,

втом числе неограниченное количество NULL-символов.

VARIANT – тип, позволяющий создать переменную, тип которой определяется во время выполнения программы и может изменяться с течением времени. Поддерживаются все типы, доступные для Automation, за исключением самого VARIANT’а. По существу, VARIANT – это структура, содержащая поле vt, определяющее тип, и поле, созданное на базе объединения, содержащего доступные типы данных. VARIANT позволяет хранить в себе даже структуру

(VT_RECORD), массив (VT_ARRAY) или указатель на интерфейс. Использование массивов VARIANT’ов и массивов структур позволяет создавать модели данных любой сложности. Единственная проблема типа VARIANT – размер. VARIANT занимает 16 байт. Впрочем, по сравнению с типом данных Any (из CORBA), он просто малютка.

SAFEARRAY – так называемый безопасный массив. С его помощью можно создавать динамические много- и одномерные массивы. Как и в случае с типом VARIANT, в качестве типа ячейки SAFEARRAY’я можно использовать любой Automation-тип, включая сам SAFEARRAY. В отличие от типа VARIANT, SAFEARRAY не приводит к перерасходу оперативной памяти, даже когда хранит в себе структуры (тип

121

VT_RECORD). При объявлении массивов типа SAFEARRAY в MIDL используется синтаксис – SAFEARRAY(elementtype) *arrayname. Здесь elementtype – это тип элемента массива. SAFEARRAY может быть многомерным, но все его размерности должны быть одного типа.

Типа «структура» как такового не существует. Структуры являются чисто сконструированным типом данных, и, похожи на структуры в языке С, за тем исключением, что здесь в структуры нельзя включать поля, являющиеся указателями на функции. Структуры могут содержать любые перечисленные типы данных, перечисления и другие структуры, определенные в этой же или импортированной (оператором importlib) библиотеке. Не обязательно, но очень рекомендуется добавлять к определенным пользователям типам (структурам и перечислениям) уникальный идентификатор (в атрибуте uuid). Эти идентификаторы сделают описания типов уникальными (по крайней мере, в пределах нашей планеты ;-)) и позволят получать их описание с помощью таких API-функций, как GetRecordInfoFromGuids.

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

С хранением структур в VARIANT или SAFEARRAY есть некоторая тонкость. Хотя в библиотеке типов можно указать, что в поле структуры или параметре функции может содержаться массив (SAFEARRAY) конкретных структур, во время работы приложения все равно приходится иметь дело с VARIANT’ом и SAFEARRAY’ем. Даже то, что у них можно узнать, что в них лежит структура (VT_RECORD), не дает никакого представления о том, что это за структура. А раз неизвестно, какая структура лежит, то и взаимодействовать с ней затруднительно. Как уже говорилось ранее, COM придерживается стратегии идентификации объектов, с которыми он взаимодействует, при помощи GUID. И если с

122

некоторым объектом, в данном случае со структурой, не ассоциирован GUID, то COM не может поместить ее в VARIANT или SAFEARRAY. Поэтому при описании структуры, которую необходимо поместить в SAFEARRAY, надо задавать GUID. Зато после выполнения этого нехитрого требования вам становится доступна вся информация о структуре, хранящейся в ячейке массива SAFEARRAY или переменной типа VARIANT. Вы можете проверить, какого типа структура лежит в массиве, динамически получить информацию о полях структуры, считать или установить значение отдельного поля (по имени) и многое другое. Вы также можете просто узнать количество записей в массиве, получить указатель на данные, привести его к указателю на нужную структуру и заняться обработкой данных.

Листинг 3. cpp-файл примера «работа со структурами и массивами»

// O.cpp : Implementation of CO #include "stdafx.h"

#include "RemTest1.h" #include "O.h"

/////////////////////////////////////////////////////////////////////////////

// CO

STDMETHODIMP CO::InterfaceSupportsErrorInfo(REFIID riid)

{

static const IID* arr[] =

{

&IID_IO

};

for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)

{

if (InlineIsEqualGUID(*arr[i],riid)) return S_OK;

}

return S_FALSE;

123

}

STDMETHODIMP CO::M1(SomeStruct *ss)

{

if(!ss)

return E_INVALIDARG; ss->i = 111;

ss->se = Some3; return S_OK;

}

STDMETHODIMP CO::M2(SAFEARRAY ** ssbs)

{

HRESULT hr = S_OK;

//Проверяем, что в массиве лежат структуры...

if(FADF_RECORD != (*ssbs)->fFeatures) return E_INVALIDARG;

//С помощью IRecordInfo можно получать различную информацию о структуре

CComPtr<IRecordInfo> spIRecordInfo;

hr = SafeArrayGetRecordInfo(*ssbs, &spIRecordInfo); if(FAILED(hr))

return hr;

//Проверяем, та ли структура лежит в массиве...

GUID RecordGuid; spIRecordInfo->GetGuid(&RecordGuid); GUID SomeStructGuid;

//Переводим взятый из IDL-файла строковый GUID в бинарный GUID

CLSIDFromString(OLESTR("{080C357D-EE8D-40b0- AD0A-5667995BB186}"),

&SomeStructGuid);

//Если GUID-ы не совпали, значит, нам подсунули массив не тех структур

if(RecordGuid != SomeStructGuid) return E_INVALIDARG;

124

//Если вы уверены, что получаете правильный массив, то предыдущий код

//не нужен. Дальше начинается код, меняющий первый элемент в массиве

SomeStruct * pss;

//Блокируем массив if(SUCCEEDED(SafeArrayLock(*ssbs)))

{// Теперь можно производить доступ к его элементам

//Получаем доступ к элементу 1 LONG iIndex = 1;

hr = SafeArrayPtrOfIndex(*ssbs, &iIndex, (void**)&pss); if(FAILED(hr))

return hr;

//Изменяем значения полей...

pss->i = 987654321; SysFreeString(pss->str);

pss->str = SysAllocString(OLESTR("Изменено на C++")); pss->se = Some3;

// Отпускаем массив

return SafeArrayUnlock(*ssbs);

}

return S_OK;

}

STDMETHODIMP CO::M3(long len, SomeStruct * ssa)

{

if(len < 2)

return S_FALSE; ssa[1].i = 555; ssa[1].se = Some2; return S_OK;

}

125

В Листингах 1, 2 и 3 приведена реализация COMобъекта, демонстрирующего работу с отдельной структурой (метод M1), с массивом структур через SAFEARRAY (метод M2), и работа с массивом, описанным средствами MIDL (метод M3). Под Windows 2000 этот пример не требует создания и регистрации заглушки, но так как метод M3 содержит прямое обращение к не-Automation типу, на других платформах этот пример может потребовать компиляции и регистрации заглушки. Чтобы избежать этого, можно изменить описания метода M3 так, чтобы использовались только типы

VARIANT и SAFEARRAY.

Код метода M2 перегружен. Но, во-первых, основную его часть занимают проверки, которые на практике не нужны. Во-вторых, можно сделать шаблонный класс, который принимал бы в качестве параметра тип структуры и эмулировал простой массив. Сделать это можно, скрыв блокировку и ее снятие в конструкторе и деструкторе, а эмулировать массив – перегрузив оператор.

Клиентское приложение, взаимодействующее с объектом из примера, можно создать на C++ (например, выбрав проект MFC EXE) или на VB. В Листинге 4 приведен пример на VB.

Листинг 4. VB-клиент примера «работа со структурами и массивами»

'Не забудьте подключить ссылку на эту библиотеку

Dim rem1 As New REMTEST1Lib.O

Private Sub Command1_Click()

Dim SS As SomeStruct SS.i = 12345

SS.se = Some1

SS.Str = "Изменено на VB"

MsgBox "i = " & SS.i & " SS.se = " & SS.se & " str = " & SS.Str

rem1.M1 SS

MsgBox "i = " & SS.i & " SS.se = " & SS.se & " str = " &

126

SS.Str

End Sub

Private Sub Command2_Click() Dim ssa(10) As SomeStruct Dim ssa1() As SomeStruct For i = 0 To 10

ssa(i).i = i ssa(i).se = Some1

ssa(i).Str = "Str " & 1 Next

ssa1 = ssa

MsgBox " ssa1(1).se = " & ssa1(1).se & " ssa1(1).Str = " & ssa1(1).Str

rem1.M2 ssa1

MsgBox " ssa1(1).se = " & ssa1(1).se & " ssa1(1).Str = " & ssa1(1).Str

End Sub

2.4.5. Disp-интерфейсы и IDispatch

Как уже говорилось, IDispatch применяется в тех случаях, где нужна интерпретация, то есть при применении OLE Automation или скриптовых языков, таких, как VBScript и JScript. Хотя один объект и может поддерживать более одного disp-интерфейса, скриптовые языки могут воспользоваться только одним из них.

Вызовы методов disp-интерфейса производятся по номеру называемому DISPID. Отображением имен методов в DISPID

занимается GetIDsOfNames.

Вот полный перечень методов, входящих в IDispatch:

GetTypeInfoCount – Возвращает число поддерживаемых TypeInfo (всегда равен 0 или 1, практического интереса не представляет).

127

GetTypeInfo – возвращает информацию о типах для текущего интерфейса (см. «Метаданные объектов»).

GetIDsOfNames – Позволяет получить список DISPID по заданному списку имен.

Invoke – это главный метод в IDispatch. С его помощью и производится вызов. Ему передаются DISPID (номер) метода или свойства и список параметров. Параметры помещаются в массив вариантов. После окончания вызова в этот же массив помещаются значения [out]-параметров. Если вызываемый метод является функцией или производится считывание значения свойства, то значение возвращается через параметр pVarResult.

Производительность Invoke ниже, чем прямого вызова интерфейса с теми же параметрами. Главным образом разница

вскорости объясняется необходимостью запаковки и последующей распаковки параметров в VARIANT. При сетевом вызове разница в скорости вызова метода dispинтерфейса и метода простого интерфейса становится менее значимой. Однако большинство высокоуровневых средств (типа VB и Delphi) при вызове удаленного метода кроме Invoke вызывают GetIdsOfNames, причем норовят сделать это при каждом вызове, без какой бы ни было оптимизации. Так как GetIdsOfNames выливается в отдельный сетевой вызов, на методах с небольшим числом маленьких по размеру параметров производительность вызовов методов через IDispatch может быть медленнее статического вызова в 4 раза.

Для вызова методов disp-интерфейсов в VB, Delphi и Java используется стандартный синтаксис этих языков. На C++ для этого применяются dispatch-драйверы, специальные классы-обертки.

На C++ более удобно и производительно использовать VTBLчасть dual-интерфейсов.

Dual-интерфейсы - это интерфейсы, поддерживающие как IDispatch, так и прямой вызов через VTBL.

128

dual-интерфейсы совмещают скорость обыкновенных интерфейсов с гибкостью disp-интерфейсов. Именно поэтому Microsoft рекомендует использовать их везде, где это возможно.

Практически все среды разработки позволяют легко создавать dual-интерфейсы. VB позволяет создавать только dualинтерфейсы, хотя использовать может и обыкновенные, и dispинтерфейсы. Если нормальный интерфейс описан вне VB (например, в библиотеке типов, созданной с помощью MIDL), то на VB можно создать реализацию этого интерфейса, воспользовавшись ключевым словом Implements.

Вызов Idispatch-части при вызове методов dual-интерфейсов еще больше снижает производительность, так как на стороне сервера этот вызов превращается в нормальный VTBL-вызов, что приводит к ненужным расходам на работу с библиотекой типов. Dual-интерфейсы защищены гораздо лучше, чем чистые disp-интерфейсы, поскольку при конвертации, о которой говорилось выше, производится контроль и приведение типов параметров вызванного метода.

2.4.6. HRESULT

По спецификации COM каждый метод COM-объекта должен возвращать специальное 32-х битное число, называемое HRESULT. В нем вызывающей стороне возвращается код ошибки (отрицательное значение), информация об успехе (константа S_OK, равная нулю) или предупреждение (значение, большее нуля).

Обычно COM-объект имеет определенный набор кодов возврата, но так как объекты могут транслировать сообщения об ошибке, полученные от вызываемых ими других методов, то не всегда можно гарантировать, что список значений неизменен. Чтобы определить, был ли вызов успешен или произошла ошибка, можно воспользоваться макросами: SUCCEEDED() – успех и FAILED() – неудача.

129

Для передачи дополнительной информации об ошибках в COM используется специальный объект, ассоциируемый с потоком, в котором произошла ошибка.

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

Чтобы записать в этот объект собственную информацию, необходимо выполнить следующие шаги:

1.Для объекта, в котором инициируется ошибка, реализовать интерфейс ISupportErrorInfo.

2.Для создания объекта, описывающего ошибку,

вызвать функцию CreateErrorInfo.

3.Для задания атрибутов этого объекта воспользоваться методами ICreateErrorInfo.

4.Для ассоциации объекта, сообщающего об ошибке,

стекущим логическим потоком, вызвать функцию

SetErrorInfo.

Чтобы получить информацию об ошибках, необходимо выполнить следующие шаги:

1.Следует проверить, может ли объект обработать ошибку, соответствующую возвращенному значению.

2.Для этого через IUnknown::QueryInterface

запрашивается указатель на интерфейс ISupportErrorInfo, а

затем – InterfaceSupportsErrorInfo, для проверки того, что ошибка случилась именно с тем объектом, который возвратил ее, и что объект, сообщающий об ошибке, относится именно к этой ошибке, а не к предыдущему вызову.

3.Затем нужно вызвать функцию GetErrorInfo, чтобы получить указатель на объект, описывающий ошибку. В

130