использовать атрибут [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);

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

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:

[uuid(E02E5345-l473-11d1-8C85-0080C73925BA),object ]

interface IDogManager : IUnknown {

typedef struct tagHUMAN {

long nHumanID;

} HUMAN;

typedef struct tagDOG {

long nDogID;

[unique] HUMAN *pOwner;

} DOG;

HRESULT GetFromPound([out] DOG *pDog);

HRESULT TakeToGroomer([in] const DOG *pDog);

HRESULT SendToVet([in, out] DOG *pDog);

}

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

DOG fido;

// argument is a DOG *, so caller needs a DOG

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату