Вообще говоря, схемы [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, вследствие чего метод получит один и тот же указатель в обоих параметрах.
Хотя полные указатели могут решать различные проблемы и в определенных случаях полезны, они не являются предпочтительными указателями в семантике СОМ. Дело в том, что в большинстве случаев разработчик знает заранее, что дублирующие указатели передаваться не будут. Кроме того, поскольку полные указатели обеспечивают более короткие 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);
При рассмотрении следующей допустимой реализации функции:
Читать дальше