Чтение онлайн

ЖАНРЫ

Сущность технологии СОМ. Библиотека программиста
Шрифт:

HRESULT hr = p->g(0);

// danger: passing null [ref] ptr.

// опасность: передается нулевой указатель с атрибутом [ref]

является ошибочным. И если p указывает на интерфейсный заместитель, то данный интерфейсный заместитель обнаружит нулевой указатель и возвратит вызывающей программе ошибку маршалинга, даже не передав метод текущему объекту. А чтобы сделать нулевой указатель допустимым значением параметра, в IDL-определении следует использовать атрибут [unique]:

HRESULT h([in, unique] short *ps);

// ps can be a null ptr.

// ps может быть нулевым указателем

Указатели, использующие атрибут [unique], называются уникальными указателями (unique pointers). При IDL-определении, приведенном выше, следующий код со стороны клиента:

HRESULT hr = p->h(0);

// relax: passing null [unique] ptr.

// расслабьтесь:

передается нулевой указатель с атрибутом [unique]

является допустимым. Это означает, что интерфейсный заместитель должен подробно исследовать указатель перед тем, как разыменовать его. И что более важно: это означает, что интерфейсному заместителю необходимо записывать в ответ на ORPC-запрос не только разыменованную величину. Кроме нее, он должен записать тег, указывающий, был или не был передан нулевой указатель. Это добавляет к размеру ORPC-сообщения четыре байта на каждый указатель. Для большинства приложений эти добавочные четыре байта и то процессорное время, которое необходимо для выявления нулевого указателя [1] , пренебрежимо малы по сравнению с преимуществами использования нулевых указателей в качестве параметров.

1 Сгенерированные MIDL интерфейсные заместители и заглушки не проверяют указатели с атрибутом [ref] на нуль. Вместо этого они вслепую разыменовывают указатель, что может привести к нарушению доступа. Поскольку маршалеры, сгенерированные MIDL, всегда выполняются внутри обработчика исключительных ситуаций, это нарушение доступа обнаруживается внутри маршалера и преобразуется в ошибку маршалинга, которая и возвращается в качестве HRESULT метода.

Вообще говоря, схемы [ref] и [unique] мало отличаются по эффективности. Однако до сих пор не обсуждалась еще одна проблема, связанная с указателями. Рассмотрим следующий фрагмент на IDL:

HRESULT j([in] short *ps1, [in] short *ps2);

Имея такое IDL-определение, рассмотрим теперь следующий фрагмент кода со стороны клиента:

short x = 100;

HRESULT hr = p->j(&x, &х);

// note: same ptr. passed twice

// заметим: тот же самый указатель передан дважды

Естественный вопрос: что должен делать интерфейсный заместитель при наличии одинаковых указателей? Если интерфейсный заместитель не делает ничего, тогда значение 100 будет передано в ORPC-запросе дважды: один раз для *ps1 и один раз для *ps2. Это означает, что заместитель посылает одну и ту же информацию дважды, впустую занимая сеть и тем самым уменьшая ее пропускную способность. Конечно, число байтов, занятых величиной 100, невелико, но если бы ps1 и ps2 указывали на очень большие структуры данных, то повторная передача существенно повлияла бы на производительность. Другой побочный эффект от невыявления дублирующего указателя состоит в том, что интерфейсная заглушка будет демаршалировать эти значения в два различных места памяти. Если бы семантика метода изменилась из-за тождественности двух таких указателей:

STDMETHODIMP MyClass::j(short *ps1, short *ps2)

{

if (ps1 == ps2)

return this->OneKindOfBehavior(ps1);

else

return this->AnotherKindOfBehavior(ps1, ps2);

}

то интерфейсный маршалер нарушил бы семантический контракт (semantic contract) интерфейса, что нарушило бы прозрачность экспорта в СОМ.

Наличие атрибутов указателя [ref] и [unique] означает, что память, на которую ссылается указатель, не является ссылкой для какого-либо другого указателя в вызове метода и что интерфейсный маршалер не должен осуществлять проверку на дублирование указателей. Для того чтобы показать, что указатель может ссылаться на память, на которую ссылается другой указатель, разработчику IDL следует использовать атрибут [ptr]:

HRESULT k([in, ptr] short *ps1, [in, ptr] short *ps2);

Указатели, использующие атрибут [ptr], называются полными указателями (full pointers), потому что они наиболее близки к полному соответствию с семантикой языка программирования С. Имея такое IDL-определение, следующий код со стороны клиента:

short x = 100;

HRESULT hr = p->k(&x, &x);

// note: same ptr. passed twice

// заметим: тот же самый указатель передан дважды

передаст значение 100 ровно один раз, поскольку

атрибут [ptr] при параметре ps1 сообщает интерфейсному маршалеру, что следует выполнить проверку на дублирование для всех остальных указателей с атрибутом [ptr]. Поскольку параметр ps2 также использует атрибут [ptr], интерфейсный маршалер определит значение дублирующего указателя [2] , а разыменует и передает значение только одного из указателей. Интерфейсная заглушка отметит, что это значение должно быть передано с обоими параметрами, ps1 и ps2, вследствие чего метод получит один и тот же указатель в обоих параметрах.

2 Интерфейсный маршалер выявляет значения дублирующих указателей (ps1 == ps2), а не одинаковые разыменованные значения (*ps1 == *ps2); однако второе вытекает из первого.

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

Указатели и память

Интерфейсы, показанные в данной главе до настоящего момента, были довольно просты и использовали только примитивные типы данных. При применении сложных типов данных одной из наиболее серьезных проблем является управление памятью для параметров метода. Рассмотрим следующий прототип функции IDL:

HRESULT f([out] short *ps);

При наличии такого прототипа нижеследующий код вполне допустим с точки зрения С:

short s;

HRESULT hr = p->f(&s);

// s now contains whatever f wrote

// s теперь содержит все, что написал f

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

short *ps;

// the function says it takes a short *, so ...

// функция говорит, что она берет * типа short, следовательно ...

HRESULT hr = p->f(ps);

При рассмотрении следующей допустимой реализации функции:

STDMETHODIMP MyClass::f(short *ps)

{

static short n = 0;

*ps = n++;

return S_OK;

}

очевидно, что выделение памяти для короткого целого числа и передача ссылки на память в качестве аргумента функции является обязанностью вызывающей программы. О только что приведенной реализации заметим, что для функции неважно, откуда взялась эта память (например, динамически выделена из «кучи», объявлена как переменная auto в стеке), до тех пор, пока текущий аргумент ссылается на допустимую область памяти. Для подкрепления этого положения СОМ требует, чтобы все параметры с атрибутами [out], являющиеся указателями, были ссылочными указателями.

Ситуация становится менее очевидной, когда вместо простых целых типов используются типы, определенные пользователем. Рассмотрим следующее IDL-определение:

typedef struct tagPoint {

short x;

short у;

} Point;

HRESULT g([out] Point *pPoint);

Как и в предыдущем примере, правильной является такая схема: вызывающая программа выделяет память для значений и передает ссылку на память, выделенную вызывающей программой:

Point pt;

HRESULT hr = p->g(&pt);

Если вызывающая программа передала неверный указатель:

Point *ppt;

// random unitialized pointer

// случайный неинициализированный указатель

HRESULT hr = p->g(ppt);

// where should proxy copy x & у to?

// куда заместитель должен копировать x и у ?

то не найдется легальной памяти, куда метод (или интерфейсный заместитель) мог бы записать значения x и y.

Чем более сложные типы определяются пользователем, тем интереснее становится сценарий. Рассмотрим следующий код IDL:

Поделиться с друзьями: