Чтобы понять, как используются на практике блоки памяти, выделенные вызываемым оператором, рассмотрим приводившийся ранее метод 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;