Совмещение имен в IDL

We use cookies. Read the Privacy and Cookie Policy

Совмещение имен в IDL

Часто бывает необходимо объединить традиционные (старые) типы данных и идиомы программирования в одну систему на основе СОМ. В идеале существует простое и очевидное преобразование традиционного кода в его аналог, совместимый с IDL. Если у нас именно такой случай, то тогда переход к СОМ будет достаточно простым. Существуют, однако, ситуации, когда традиционные типы данных или идиомы приложения просто не могут разумным образом преобразовываться в IDL. Для решения этой проблемы в IDL предусмотрено несколько технологий замещения (aliasing techniques ), которые позволяют разработчику интерфейса составлять подпрограммы преобразования, способные переводить традиционные типы данных и идиомы в легальные, доступные для вызова представления на IDL.

Прекрасным примером ситуации, в которой данная технология полезна, является идиома IEnum . Идиома нумератора СОМ была разработана раньше, чем компилятор IDL, поддерживаемый СОМ. Это означает, что первый разработчик интерфейса IEnum не мог проверить свою разработку на соответствие известным правилам преобразования в IDL. Метод перечислителя Next не может быть чисто преобразован в IDL[1]. Рассмотрим идеальный IDL-прототип метода Next:

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

К сожалению, исходное «до-IDL-овское» определение метода Next устанавливало, что вызывающие программы могут передавать в качестве третьего параметра нулевой указатель, при условии, что первый параметр показывал, что запрашивается только один элемент. Это предоставляло вызывающим программам удобную возможность извлекать по одному элементу за раз:

double dblElem;

hr = p->Next(1, &dblElem, 0);

Данное допустимое использование интерфейса противоречит приведенному выше IDL-определению, так как [out] -параметры самого верхнего уровня не имеют права быть нулевыми (нет места, куда интерфейсный заместитель мог бы сохранять результат). Для разрешения этого противоречия каждое определение метода Next должно использовать атрибут [call_as] для замены вызываемой формы (callable form) метода его отправляемой формой (remotable form).

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

interface IEnumDoubIe : IUnknown {

// this method is what the caller and object see

// данный метод, как его видят вызывающая программа и объект

[local] HRESULT Next([in] ULONG cElems,

[out] double *prgElems, [out] ULONG *pcFetched);

// 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.