Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программирование » Сущность технологии СОМ. Библиотека программиста - Дональд Бокс

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

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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 73 74 75 76 77 78 79 80 81 ... 95
Перейти на страницу:

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);

1 ... 73 74 75 76 77 78 79 80 81 ... 95
Перейти на страницу:
Тут вы можете бесплатно читать книгу Сущность технологии СОМ. Библиотека программиста - Дональд Бокс.
Комментарии