Сущность технологии СОМ. Библиотека программиста
Шрифт:
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.