Версия для печати


Урок 16. Динамическая память в COM/DCOM
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1363

Антон Григорьев
дата публикации 19-06-2008 12:21

урок из цикла: Использование COM/DCOM в Delphi

Урок 16. Динамическая память в COM/DCOM

В предыдущем уроке при обсуждении атрибутов in и out мы упоминали о том что клиент и сервер могут передавать друг другу указатели на динамически выделяемые блоки памяти и о том, что в некоторых случаях память, выделенную сервером, освобождает клиент, и наоборот. Если к этому добавить то, что мы знаем о маршалинге, получится, что в случае внешнего сервера на самом деле память, выделенную сервером, освобождает его заглушка, а на стороне клиента заместитель снова выделяет память, которую затем и освобождает клиент. Понятно, что для выполнения таких операций клиент и заместитель, а также заглушка и сервер, должны пользоваться единым менеджером памяти. Ни New, ни GetMem не подходят для выделения памяти в таком случае, потому что менеджер памяти Delphi недоступен для заглушки и заместителя.

Разумеется, система предоставляет менеджер памяти, подходящий для использования в COM/DCOM. Мы уже сталкивались с функциями, которые его используют: это функции типа SysAllocString и SysFreeString для строк, а также все функции, связанные с выделением и освобождением памяти для безопасных массивов. Именно благодаря этому строки и безопасные массивы можно передавать между клиентом и сервером.

В общем случае работа с менеджером памяти осуществляется через интерфейс IMalloc. Указатель на этот интерфейс можно получить с помощью функции CoGetMalloc. Эта функция принимает два параметра, первый из которых зарезервирован и должен быть равен 1, второй параметр выходной, через него возвращается интерфейс IMalloc.

Примечание: Функция GoGetMalloc не требует предварительного вызова CoInitialize, так что данный менеджер памяти можно использовать и без инициализации COM. Именно поэтому в Delphi можно работать со строками типа WideString, которые используют данный менеджер памяти, не вызывая CoInitialize.

Интерфейс IMalloc содержит шесть методов: Alloc (выделяет блок памяти заданного размера), Realloc (изменяет размер выделенного ранее блока), Free (освобождает выделенный блок), GetSize (возвращает размер выделенного ранее блока), DidAlloc (позволяет определить, был данный блок выделен этим менеджером памяти) и HeapMinimize (возвращает неиспользуемые в данный момент блоки памяти системе).

В целом работать с системным менеджером памяти не сложнее, чем использовать функции GetMem и FreeMem. Метод HeapMinimize рекомендуется вызывать время от времени, чтобы эффективнее возвращать память системе (судя по всему, системный менеджер памяти работает по тому же принципу, что и менеджер памяти Delphi, т.е. приберегает освобождённую память на всякий случай, чтобы при следующем выделении не выполнять затратную операцию получения памяти у системы).

Существует альтернативный способ работы с системным менеджером памяти: через функции CoTaskMemAlloc, CoTaskMemRealloc и CoTaskMemFree. Эти функции — другой интерфейс к тому же менеджеру памяти: память, выделенную через CoTaskMemAlloc, можно освобождать через IMalloc.Free, а выделенную через IMalloc.Alloc — освобождать через CoTaskMemFree. А IMalloc.DidAlloc для блока, выделенного с помощью CoTaskMemAlloc, вернёт 1, что соответствует тому, что блок выделен данным менеджером памяти. Обычно функции CoTaskMemXXX удобнее, чем интерфейс IMalloc, так как не требуют предварительного вызова CoGetMalloc. Но методы GetSize, DidAlloc и HeapMinimize не имеют аналогов в виде отдельных системных функций, поэтому если они требуются приложению, следует использовать IMalloc.

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

Подчеркнём, что использование системного менеджера памяти необходимо тогда, когда память, выделяемая на одной стороне, может быть освобождена на другой; в остальных же случаях в этом нет необходимости. Например, для чисто входных параметров, передающихся по ссылке, клиент может выделять память где угодно, даже в стеке. Для указателей верхнего уровня в выходных параметрах, для которых память выделяется также клиентом, тоже можно использовать любой менеджер памяти, а также стек, глобальные переменные и т.п.