Современная электронная библиотека ModernLib.Net

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

ModernLib.Net / Программирование / Бокс Дональд / Сущность технологии СОМ. Библиотека программиста - Чтение (стр. 26)
Автор: Бокс Дональд
Жанр: Программирование

 

 


// передаем нулевой указатель

Если вызывающий поток выполняется в апартаменте объекта, то заместителя нет и нулевой указатель будет передан прямо объекту. Но что если объект расположен в другом апартаменте и заместитель используется? Что в точности должен передать интерфейсный заместитель, чтобы показать, что был послан нулевой указатель? Кроме того, означает ли это, что интерфейсные заместители и заглушки должны проверять каждый указатель, не является ли он нулевым? Оказывается, бывают ситуации, в которых указатель никогда не должен быть нулевым, и другие ситуации, когда нулевые указатели, наоборот, чрезвычайно полезны как начальные значения. В последнем случае факт передачи нулевого указателя интерфейсному заместителю должен быть продублирован интерфейсной заглушкой в апартаменте объекта.

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

HRESULT g([in, ref] short *ps);

// ps cannot be a null ptr.

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

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

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], пренебрежимо малы по сравнению с преимуществами использования нулевых указателей в качестве параметров.

Вообще говоря, схемы [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);

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

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

// аргументом является DOG *, поэтому вызывающей программе нужен DOG

HUMAN dummy;

// the DOG refers to an owner, so alloc space?

// DOG ссылается на владельца, поэтому выделяем память?

fido.pOwner = &dummy;

HRESULT hr = p->GetFromPound(&fido);

// is this correct?

// правильно ли это?

В данном коде предполагается, что вызывающая программа ответственна за выделение памяти для DOG, который передается по ссылке. В этом смысле код правилен. Однако в этом коде также предполагается, что он отвечает за управление любой памятью более низкого уровня, на которую могут сослаться обновленные значения объекта DOG. Именно здесь данный код отступает от правил СОМ.

СОМ разделяет указатели, участвующие в вызове метода, на две категории. Любые именованные параметры метода, являющиеся указателями, относятся к указателям высшего уровня (top-level). Любой подчиненный указатель, который получен путем разыменования указателя высшего уровня, является вложенным (embedded) указателем. В методе GetFromPound параметр pDog считается указателем высшего уровня. Подчиненный указатель pDog->pOwner рассматривается как вложенный указатель. Отметим, что определение структуры DOG использует атрибут [unique] для явной квалификации семантики указателя для элемента структуры pOwner. Если бы семантика указателя не была квалифицирована явно, разработчик интерфейса мог бы применить принятый по умолчанию во всех интерфейсах для всех вложенных указателей атрибут [pointer_default]:

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

pointer_default(ref)

// default embedded ptrs to [ref]

// по умолчанию вложенные указатели [ref]

]

interface IUseStructs : IUnknown {

typedef struct tagNODE {

long val;

[unique] struct tagNODE *pNode;

// explicitly [unique]

// явно [unique]

} NODE;

typedef struct tagFOO {

long val;

long *pVal;

// implicitly [ref]

// неявно [ref]

} FOO;

HRESULT Method([in] FOO *pFoo, [in, unique] NODE *pHead);

}

Атрибут [pointer_default] применяется только к тем вложенным указателям, семантика которых не квалифицирована явно. В приведенном выше определении интерфейса единственный указатель, к которому это относится, – это элемент данных pVal структуры FOO. Элемент pNode структуры NODE явно квалифицирован как уникальный указатель, поэтому установка [pointer_default] на него не влияет. На параметры метода pFoo и pHead атрибут [pointer_default] также не влияет, поскольку они являются указателями высшего уровня и по умолчанию [ref], если только они не квалифицированы явно иным образом (как в случае с pHead).

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

HUMAN bob = { 2231 };

DOG fido = { 12288, &bob };

// fido is owned by bob

// fido принадлежит bob'y

HRESULT hr = p->TakeToGroomer(&fido);

// this is correct!

// это правильно!

В то же время разграничение между указателями высшего уровня и вложенными является существенным, когда оно касается организации памяти для параметров [out] и [in,out]. Для обоих параметров, [out] и [in,out], память, на которую ссылаются указатели высшего уровня, управляется вызывающим оператором, как и в случае параметров [in]. Для вложенных же указателей, которые появляются в параметрах [out] и [in, out], память управляется вызываемым оператором (самим методом). Причина появления этого правила заключается в том, что глубина вложения типов данных может быть сколь угодно большой. Например, в таком определении типа:

typedef struct tagNODE {

short value;

[unique] struct tagNODE *pNext;

} NODE:

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

При наличии правила, по которому разработчики метода должны выделять память при инициализации любых вложенных указателей, возникает естественный вопрос, откуда методы должны получить эту память, чтобы вызывающие операторы знали, как освободить ее после прочтения всех возвращенных значений? Ответом является распределитель памяти (task allocator) СОМ-задачи. Распределителем памяти задачи СОМ называется распределитель памяти, индивидуальный для каждого процесса, используемый исключительно для выделения памяти вложенным указателям с атрибутами [out] и [in,out]. Проще всего использовать этот распределитель памяти СОМ-задачи посредством применения трех API-функций СОМ:

void *CoTaskMemAlloc(DWORD cb);

// allocate cb bytes

// размещаем cb байтов

void CoTaskMemFree(void *pv);

// deallocate memory at *pv

// освобождаем память в *pv

void *CoTaskMemRealloc(void *pv,DWORD cb);

// grow/shrink *pv

// расширяем/сжимаем *pv

Семантика этих трех функций такая же, как у их эквивалентов из динамической библиотеки С: malloc, free и realloc. Разница состоит в том, что они предназначены исключительно для выделения памяти параметрам типа вложенных указателей с атрибутами [out] и [in,out]. Другое важное отличие состоит в том, что подпрограммы из динамической библиотеки С нельзя использовать для выделения памяти в одном модуле и освобождения ее в другом. Дело в том, что детали реализации каждой динамической библиотеки С являются специфическими и изменяются при смене компилятора. Так как все участники согласились использовать один и тот же распределитель, предлагаемый СОМ, нет проблемы с освобождением клиентом памяти, которая выделена объектом, скомпилированным в отдельной DLL.

Чтобы понять, как используются на практике блоки памяти, выделенные вызываемым оператором, рассмотрим приводившийся ранее метод GetFromPound:

HRESULT GetFromPound([out] DOG *pDog);

В то время как память для объекта DOG должна быть выделена вызывающей программой (pDog является указателем высшего уровня), память для объекта HUMAN должна быть выделена реализацией метода с использованием распределителя памяти задачи (pDog->pOwner является вложенным в [out]-параметр указателем). Реализация метода выглядела бы примерно так:

STDMETHODIMP GetFromPound(/*[out]*/DOG *pDog)

{

short did = LookupNewDogId();

short hid = LookupHumanId(did);

pDog->nDogID = did;

// allocate memory for embedded pointer

// выделяем память для вложенного указателя

pDog->pOwner = (HUMAN*) CoTaskMemAlloc(sizeof(HUMAN));

if (pDog->pOwner == 0)

// not enough memory

// недостаточно памяти

return R_OUTOFMEMORY;

pDog->pOwner->nHumanID = hid;

return S_OK;

}

Отметим, что метод возвращает специальный HRESULT E_OUTOFMEMORY, указывающий на то, что операция прервана из-за нехватки памяти.

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

DOG fido;

HRESULT hr = p->GetFromPound(&fido);

if (SUCCEEDED(hr)) {

printf(«The dog %h is owned by %h», fido.nDogID, fido.pOwner->nHumanID);

// data has been consumed, so free the memory

// данные использованы, поэтому освобождаем память

CoTaskMemFree(fido.pOwner);

}

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

В только что приведенном примере использован чистый [out]-параметр. Управление [in, out]– параметрами несколько более сложно. Вложенные указатели для [in, out]-параметров должны быть размещены вызывающей программой с помощью распределителя памяти задачи. Если методу требуется повторно распределить память, переданную клиентом, то метод должен сделать это с использованием CoTaskMemRealloc. Если же вызывающая программа не имеет никакой информации для передачи методу, то она может передать ему на входе нулевой указатель, и тогда метод может использовать CoTaskMemRealloc (который без проблем принимает нулевой указатель и делает то, что нужно). Подобным же образом, если у метода нет информации для обратной передачи в вызывающую программу, он может просто освободить память, на которую ссылается вложенный указатель. Рассмотрим следующее определение метода IDL:

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

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

HUMAN *pHuman = (HUMAN*)CoTaskMemAllocc(sizeof(HUMAN));

pHuman->nHumanID = 1522;

DOG fido = { 4111, pHuman };

HRESULT hr = p->SendToVet(&fido); // [in, out]

if (SUCCEEDED(hr)) {

if (fido.pOwner)

printf(«Dog is now owned by %h», fido.pOwner->nHumanID);

CoTaskMemFree(fido.pOwner);

// OK to free null ptr.

// можно освободить нулевой указатель

}

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

STDMETHODIMP MyClass::SendToVet(/*[in, out]*/DOG *pDog)

{

if (fido.pOwner == 0)

fido.pOwner = (HUMAN*)CoTaskMemAlloc(sizeof (HUMAN));

if (fido.pOwner == 0)

// alloc failed

// сбой выделения памяти

return E_OUTOFMEMORY;

fido.pOwner->nHumanID = 22;

return S_OK;

}

Поскольку работа с [in,out]-параметрами в качестве вложенных указателей имеет ряд тонкостей, в документации на интерфейс часто повторяются правила управления памятью для вложенных указателей.

Приведенные выше фрагменты кода используют наиболее удобный интерфейс для СОМ-распределителя памяти задач. До появления версии СОМ под Windows NT основная связь с распределителем памяти задачи осуществлялась через его интерфейс IMallос:

[ uuid(00000002-0000-0000-C000-000000000046),local,object]

interface IMalloc : IUnknown {

void *Alloc([in] ULONG cb);

void *Realloc ([in, unique] void *pv, [in] ULONG cb);

void Free([in, unique] void *pv);

ULONG GetSize([in, unique] void *pv);

int DidAlloc([in, unique] void *pv);

void HeapMinimize(void);

}

Для получения доступа к интерфейсу IMalloc распределителя памяти задачи в СОМ имеется API-функция CoGetMalloc:

HRESULT CoGetMalloc(

[in] DWORD dwMemCtx, // reserved, must be one

// зарезервировано, должно равняться единице

[out] IMalloc **ppMalloc); // put it here!

// помещаем его здесь!

Это означает, что вместо вызова удобного метода CoTaskMemAlloc:

HUMAN *pHuman = (HUMAN*)CoTaskMemAlloc(sizeof(HUMAN));

можно использовать следующую менее удобную форму:

IMalloc *pMalloc = 0;

pHuman = 0;

HRESULT hr = CoGetMalloc(1, &pMalloc);

if (SUCCEEDED(hr)) {

pHuman = (HUMAN*)pMalloc->Alloc(sizeof(HUMAN));

pMalloc->Release();

}

Преимущество последней технологии заключается в том, что она совместима с ранними, до Windows NT, версиями СОМ. Но в целом предпочтительнее использовать CoTaskMemAlloc и другие, поскольку эти методы требуют меньше программного кода и поэтому меньше подвержены ошибкам программирования.

До сих пор обсуждение распределителя памяти задачи было сфокусировано на вопросах, как и когда объекты выделяют память, а клиенты – освобождают ее. Однако не обсуждалось, что происходит, когда объект и клиент размещаются в различных адресных пространствах. Это во многом связано с отсутствием различия в способах реализации клиентов и объектов при использовании интерфейсных маршалеров. СОМ-распределитель памяти задачи получает свою память из закрытого адресного пространства процессов. С учетом этого сокрытие того обстоятельства, что распределитель памяти задачи не может охватить оба адресных пространства, является делом интерфейсной заглушки и интерфейсного заместителя. Когда интерфейсная заглушка вызывает метод объекта, она маршалирует любые [out]– или [in, out]-параметры в ответное ORPC-сообщение. Как показано на рис. 7.1, по завершении этого маршалинга интерфейсная заглушка (которая в конечном счете является внутриапартаментным клиентом данного объекта) освобождает с помощью метода CoTaskMemFree любую память, выделенную вызываемой программой. Это эффективно освобождает всю память, выделенную в течение вызова метода внутри адресного пространства объекта. При получении ответного ORPC-сообщения интерфейсный заместитель с помощью метода CoTaskMemAlloc выделяет пространство для всех параметров, размещаемых в вызываемой программе.




Когда эти блоки памяти освобождаются настоящим клиентом с помощью CoTaskMemFree, это эффективно освобождает всю память, выделенную в результате вызова метода, внутри адресного пространства клиента.

Поскольку программисты печально известны своим пренебрежением к освобождению памяти, иногда бывает полезно следить за активностью распределителя памяти задачи в процессе (или отсутствием таковой активности). Для обеспечения этого контроля СОМ предлагает подключить к распределителю памяти задачи определяемый пользователем шпионский объект (spy object), который будет уведомляться до и после каждого вызова распределителя памяти. Этот шпионский объект, определяемый пользователем, должен реализовать интерфейс IMallocSpy:

[ uuid(0000001d-0000-0000-C000-000000000046),local,object ]

interface IMallocSpy : IUnknown {

ULONG PreAlloc([in] ULONG cbRequest);

void *PostAlloc([in] void *pActual);

void *PreFree([in] void *pRequest,[in] BOOL fSpyed);

void PostFree([in] BOOL fSpyed);

ULONG PreRealloc([in] void *pRequest,[in] ULONG cbRequest,

[out] void **ppNewRequest,[in] BOOL fSpyed);

void *PostRealloc([in] void *pActual, [in] BOOL fSpyed);

void *PreGetSize([in] void *pRequest, [in] BOOL fSpyed);

ULONG PostGetSize([in] ULONG cbActual,[in] BOOL fSpyed);

void *PreDidAlloc([in] void *pRequest, [in] BOOL fSpyed);

int PostDidAlloc([in] void *pRequest, [in] BOOL fSpyed, [in] int fActual);

void PreHeapMinimize(void);

void PostHeapMinimize(void);

}

Отметим, что для каждого метода IMalloc интерфейс IMallocSpy имеет два метода: один, вызываемый СОМ до того, как действующий распределитель памяти задачи начнет свою работу, и второй, вызываемый СОМ после того, как распределитель памяти выполнил свою работу. В каждом «предметоде» (premethod) предусмотренный пользователем шпионский объект может изменять параметры, передаваемые пользователем распределителю памяти. В каждом «постметоде» (postmethod) шпионский объект может изменять результаты, возвращаемые действующим распределителем памяти задачи. Это дает возможность шпионскому объекту выделять дополнительную память, чтобы добавить к каждому блоку памяти отладочную информацию. В СОМ имеется API-функция для регистрации шпиона распределения памяти (Malloc spy) всего процесса:

HRESULT CoRegisterMallocSpy([in] IMallocSpy *pms);

В каждом процессе может быть зарегистрирован только один шпион распределения памяти (CoRegisterMallocSpy возвратит CO_E_OBJISREG в том случае, если уже зарегистрирован другой шпион). Для удаления шпиона распределения в СОМ предусмотрена API-функция CoRevokeMallocSpy:

HRESULT CoRevokeMallocSpy(void);

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


Массивы

По умолчанию указатели, передаваемые через параметры, полагаются указателями на единичные экземпляры, а не на массивы. Для передачи массива в качестве параметра можно использовать синтаксис С для массивов и/или специальные атрибуты IDL для представления различной информации о размерности массива. Простейший способ передачи массивов – задать размерность во время компиляции:

HRESULT Method1([in] short rgs[8]);

Такое задание называется массивом постоянной длины (fixed array) и является наиболее простым для выражения на языке IDL и одновременно – наиболее простым и компактным представлением во время выполнения. Для такого массива интерфейсный заместитель выделит 16 байт (8 * sizeof (short)) в сообщении ORPC-запроса, а затем скопирует в сообщение все восемь элементов. Как только сервер получает ORPC-запрос, интерфейсная заглушка будет использовать память непосредственно из принимаемого блока в качестве аргумента функции, как показано на рис. 7.2.



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

Только что показанный метод полезен, если во всех случаях единственно разумной длиной массива является 8. Это позволяет вызывающей программе пересылать любой выбранный ею массив из коротких целых чисел (shorts), при условии, что этот массив состоит только из восьми элементов:

void f(IFoo *pFoo)

{

short rgs[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };

pFoo->Method1(rgs);

}

На практике предсказание подходящей длины массива невозможно, так как слишком малая длина означает, что будет передано недостаточно элементов, а слишком большая длина приведет к чрезмерному объему передаваемого сообщения. Более того, если массив состоит из сложных типов данных, то маршалинг элементов за пределами фактического размера массива может обойтись весьма дорого и/или привести к ошибкам маршалинга. Тем не менее, массивы постоянной длины полезны в тех случаях, когда размер массива не изменяется и известен во время формирования интерфейса.

Чтобы можно было определять размеры массивов во время выполнения, IDL (и используемый сетевой протокол NDR) разрешает вызывающей программе задавать длину массива на этапе выполнения. Массивы такого типа называются совместимыми (conformant). Максимальный допустимый индекс совместимого массива можно задавать либо во время выполнения, либо во время компиляции, а длина, называемая соответствием (conformance) массива, передается раньше чем текущие элементы, как это показано на рис. 7.3. Как и в случае массива постоянной длины, совместимые массивы могут передаваться в реализацию метода непосредственно из передаваемого буфера без какого-либо дополнительного копирования, так как в передаваемом сообщении всегда присутствует все содержимое массива.

Чтобы предоставить вызывающей программе возможность задать соответствие массива, IDL использует атрибут [size_is]:

HRESULT Method2([in] long cElems,

[in, size_is(cElems)] short rgs[*]);

или


  • Страницы:
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33