Выбрать главу

// this method is how it goes out on the wire

// данный метод, как он выходит на передачу

[call_as(Next)]

HRESULT RemoteNext([in] ULONG cElems, [out, size_is(cElems), length_is(*pcFetched)] double *prg, [out] ULONG *pcFetched);

HRESULT Skip([in] ULONG cElems);

HRESULT Reset(void); HRESULT Clone([out] IEnumDouble **ppe);

}

Результирующий заголовочный файл C/C++ будет содержать определение интерфейса, включающее в себя метод Next, но не определение метода RemoteNext. Что касается клиента и объекта, то у них нет метода RemoteNext. Он существует только для того, чтобы интерфейсный маршалер мог правильно отправить метод. Хотя у методов Next и RemoteNext списки параметров идентичны, при использовании данной технологии этого не требуется. На самом деле иногда бывает полезно включить в отправляемую форму метода добавочные параметры, чтобы дать исчерпывающее определение тому, как эта операция будет отправлена.

С добавлением в метод пары атрибутов [local]/[call_as] исходный код, сгенерированный интерфейсным маршалером, более не сможет успешно компоноваться из-за непреобразованных внешних символов. Дело в том, что в этом случае разработчик интерфейса должен предусмотреть две дополнительных подпрограммы. Одна из них будет использоваться интерфейсным заместителем для преобразования формы метода с атрибутом [local] в форму с атрибутом [call_as]. B случае приведенного выше определения интерфейса компилятор IDL будет ожидать, что разработчик интерфейса обеспечит его следующей функцией:

HRESULT STDMETHODCALLTYPE IEnumDouble_Next_Proxy(IEnumDouble *This, ULONG cElems, double *prg, ULONG *pcFetched);

Вторая необходимая подпрограмма используется интерфейсной заглушкой для преобразования формы метода с атрибутом [call_as] в форму с атрибутом [local]. В случае приведенного выше определения интерфейса компилятор IDL будет ожидать от разработчика интерфейса следующей функции:

HRESULT STDMETHODCALLTYPE IEnumDouble_Next_Stub(IEnumDouble *This, ULONG cElems, double *prg, ULONG *pcFetched);

Для удобства прототипы для этих двух подпрограмм будут приведены в сгенерированном заголовочном файле C/C++.

Как показано на рис. 7.10, определяемая пользователем подпрограмма [local]-to-[call_as] используется для заполнения таблицы vtbl интерфейсного заместителя и вызывается клиентом. Данная подпрограмма предназначена для преобразования вызова в удаленный вызов процедуры посредством вызова отправляемой версии, которая генерируется компилятором IDL. Для подпрограммы нумератора Next необходимо только убедиться, что в качестве третьего параметра передается ненулевой указатель:

HRESULT STDMETHODCALLTYPE IEnumDouble_Next_Proxy( IEnumDouble *This, ULONG cElems, double *prg, ULONG *pcFetched) {

// enforce semantics on client-side

// осуществляем семантику на стороне клиента

if (pcFetched == 0 && cElems != 1) return E_INVALIDARG;

// provide a location for last [out] param

// обеспечиваем место для последнего [out]-параметра

ULONG cFetched;

if (pcFetched == 0) pcFetched = &cFetched;

// call remote method with non-null pointer as last param

// вызываем удаленный метод с ненулевым указателем

// в качестве последнего параметра

return IEnumDouble_RemoteNext_Proxy(This, cElems, prg, pcFetched);

}

Отметим, что во всех случаях отправляемая версия метода получает в качестве последнего параметра ненулевой указатель.

Определяемая пользователем подпрограмма [local]-to-[call_as] будет вызываться интерфейсной заглушкой после демаршалинга отправляемой формы метода. Эта подпрограмма предназначена для преобразования отправляемой формы вызова в локальный вызов процедуры на текущий объект. Поскольку реализации объекта иногда проявляют небрежность и не считают нужным показывать, сколько элементов возвращается при возвращении S_OK, правильность установки этого параметра обеспечивает подпрограмма преобразования со стороны объекта:

HRESULT STDMETHODCALLTYPE IEnumDouble_Next_Stub( IEnumDouble *This, ULONG cElems, double *prg, ULONG *pcFetched) {

// call method on actual object

// вызываем метод на текущий объект

HRESULT hr = This->Next(cElems, prg, pcFetched);

// enforce semantics on object-side

// проводим в жизнь семантику на стороне объекта

if (hr == S_OK)

// S_OK implies all elements sent

// S_OK означает, что все элементы посланы

*pcFetched = cElems;

// [length_is] must be explicit

// атрибут [length_is] должен быть явным

return hr;

}

Интерфейсная заглушка всегда будет вызывать данную подпрограмму с ненулевым последним параметром.

Технология с атрибутом [call_as] является полезной при организации преобразований из вызываемой формы в отправляемую по схеме «метод-за-методом». В СОМ также предусмотрена возможность специфицировать определяемые пользователем преобразования для отдельных типов данных при помощью атрибутов определения типов [transmit_as] и [wire_marshal]. Эти три технологии не следует считать основными при разработке интерфейсов; они существуют в основном для поддержки традиционных идиом и типов данных. Еще одним приемом, которым владеет компилятор IDL, является cpp_quote. Ключевое слово cpp_quote разрешает появление в IDL-файле любых операторов C/C++, даже если этот оператор не является допустимым в IDL. Рассмотрим следующее простейшее применение cpp_quote для внедрения определения встраиваемой функции в сгенерированный IDL заголовочный файл:

// surfboard.idl

cpp_quote(«static void Exit(void) { ExitProcess(1); }»)

Имея данный IDL-код, сгенерированный C/C++ заголовочный файл будет просто содержать следующий фрагмент:

// surfboard.h static void Exit(void) { ExitProcess(1); }

Ключевое слово cpp_quote может быть использовано для осуществления различных трюков в компиляторе IDL. Примером этого может служить тип данных REFIID . Фактическим определением IDL для этого типа является typedef IID *REFIID;

В то же время тип C++ определен как typedef const IID& REFIID;

Однако ссылки в стиле C++ не допускаются в IDL. Для решения данной проблемы системный IDL-файл использует следующий прием:

// from wtypes.idl (approx.)

// из файла wtypes.idl (приблизительно)

cpp_quote(«#if 0») typedef IID "REFIID;

// this is the pure IDL definition

// это чисто IDL-определение

cpp_quote(«#endif») cpp_quote(«#ifdef _cplusplus») cpp_quote(«#define REFIID const IID&»)

// C++ definition

// определение C++

cpp_quote(«#else») cpp_quote(«#define REFIID const IID * const»)

// С definition

// определение С

cpp_quote(«#endif»)

Результирующий заголовочный файл C++ выглядит так:

// from wtypes.h (approx.)

// из файла wtypes.h (приблизительно)

#if 0 typedef IID *REFIID;

#endif

#ifdef _cplusplus

#define REFIID const IID&

#else

#define REFIID const IID * const

#endif

Этот несколько гротескный прием необходим, поскольку многие базовые интерфейсы СОМ были определены без учета возможного применения IDL.

Асинхронные методы

Вызовы методов в СОМ являются по умолчанию синхронными. Это означает, что клиентский поток заблокирован до тех пор, пока ответное ORPC-сообщение не получено и не демаршалировано. Такая схема в полной мере демонстрирует, как работает обычный вызов метода в одном потоке (same-thread), и это с полным основанием принято по умолчанию. До появления Windows NT 5.0 не было способа осуществить вызов метода и продолжать обработку одновременно с выполнением метода без явного порождения дополнительных потоков. В версии СОМ Windows NT 5.0 вводится поддержка асинхронного вызова метода. Асинхронность является свойством метода и должна быть выражена в IDL посредством применения атрибута [async_uuid].