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