среда, 6 мая 2009 г.

В чём разница между SHGetMalloc, SHAlloc, CoGetMalloc и CoTaskMemAlloc?

Это перевод What's the difference between SHGetMalloc, SHAlloc, CoGetMalloc, and CoTaskMemAlloc? Автор: Реймонд Чен.

Ну, сегодня это уже одно и то же.

Давайте сперва разберёмся с теми, что попроще.

Для начала, CoTaskMemAlloc - это в точности то же самое, что и CoGetMalloc(MEMCTX_TASK) + IMalloc::Alloc, а CoTaskMemFree - это полный эквивалент той же самой CoGetMalloc(MEMCTX_TASK) + IMalloc::Free. CoTaskMemAlloc и CoTaskMemFree (а также реже используемая CoTaskMemRealloc) - это просто удобные оболочки, которые дают вам возможность избегать лишних вызовов CoGetMalloc. Соответственно, вы можете безопасно выделить память, скажем, через CoGetMalloc(MEMCTX_TASK) + IMalloc::Alloc, а затем освободить её через CoTaskMemFree, или наоборот. Это один и тот же менеджер памяти (аллокатор, allocator).

Похожим образом, SHAlloc и SHFree - это просто оболочки вокруг SHGetMalloc, которая выделяет/освобождает память, используя менеджер памяти оболочки. Память, которую вы выделили через SHGetMalloc + IMalloc::Alloc, может быть освобождена через SHFree.

Итак, у нас получается такая схема:

Менеджер памяти ShellМенеджер памяти OLE
SHAlloc/
SHFree
=SHGetMalloc ?? CoGetMalloc=CoTaskMemAlloc/
CoTaskMemFree

Ну а теперь: что это за знаки вопроса?

Если вы прочитаете комментарий в shlobj.h, вы получите подсказку (прим. пер.: решил оставить без перевода):
//===========================================================================
//
// Task allocator API
//
//  All the shell extensions MUST use the task allocator (see OLE 2.0
// programming guild for its definition) when they allocate or free
// memory objects (mostly ITEMIDLIST) that are returned across any
// shell interfaces. There are two ways to access the task allocator
// from a shell extension depending on whether or not it is linked with
// OLE32.DLL or not (purely for efficiency).
//
// (1) A shell extension which calls any OLE API (i.e., linked with
//  OLE32.DLL) should call OLE's task allocator (by retrieving
//  the task allocator by calling CoGetMalloc API).
//
// (2) A shell extension which does not call any OLE API (i.e., not linked
//  with OLE32.DLL) should call the shell task allocator API (defined
//  below), so that the shell can quickly loads it when OLE32.DLL is not
//  loaded by any application at that point.
//
// Notes:
//  In next version of Windowso release, SHGetMalloc will be replaced by
// the following macro.
//
// #define SHGetMalloc(ppmem)   CoGetMalloc(MEMCTX_TASK, ppmem)
//
//===========================================================================

(Да, эти опечатки "guild" и "Windowso" сидят там с 1995)

Это обсуждение решительно намекает на то, что же происходит.

Когда разрабатывалась Windows 95, компьютеры обычно имели около 4 Мб памяти (крутые парни имели 8 Мб). Но Проводник также сильно использовал архитектуру COM для расширений оболочки (shell extension), а загрузка в память библиотеки OLE32.DLL была хорошим ударом в зубы. В таких стеснённых ограничениях на память, даже потеря 4-х Кб памяти была заметна.

Решение: сыграть в "Слабо загрузить OLE?" ("OLE Chicken").

Сама оболочка, как оказалось, не слишком-то использовала COM: единственными поддерживаемыми ею объектами были in-process apartment-threaded COM-объекты без маршалинга. Поэтому команда оболочки написала "мини-COM", который поддерживал только эти операции, и использовала его вместо полноценного COM (ну, им повезло, что один из главных разработчиков оболочки также был супер-экспертом в COM). Оболочка имела свой собственный мини-менеджер памяти, свой собственный биндер, свой собственный мини-цикл drag-drop - всё, что необходимо, если вы не использовали никаких других программ, которые использовали OLE32.

Как только запускалась какая-то программа, которая использовала OLE32, у вас появлялась проблема: теперь у вас в системе было две копии OLE: настоящая и фальшивая версия оболочки. Если ничего не сделать, то вы не сможете обмениваться данными между настоящим-COM и фальшивым-COM-оболочки. Например, вы не сможете перетаскивать (drag/drop) данные между Проводником (который использует фальшивый-COM-оболочки) и окном, которое использует настоящий-COM.

Решение: при поддержке других частей системы, оболочка определяла, что "COM теперь с нами", как только кто-то загружал OLE32.DLL, и тогда она переносила всю свою информацию, которой до этого она управляла сама, в мир настоящего COM. Как только она заканчивала это, все псевдо-COM функции переключались на функции настоящего-COM. Например, после загрузки OLE32.DLL, вызовы фальшивого мини-менеджера памяти Shell перенаправлялись настоящему менеджеру памяти.

Но что такое "Слабо загрузить OLE?"? Это ещё один вариант различных "слабо/не слабо" игр, самой известной из которых, вероятно, является Schedule Chicken (вот здесь есть перевод профессионала на эту тему). В игре в "Слабо загрузить OLE?", каждая программа пыталась избежать загрузки OLE32.DLL так долго, как могла, так чтобы она не стала бы программой, повинной в долгой задержке, пока OLE32.DLL загружается и раскручивается (не забывайте, мы говорим о машинах эры 1995-го, когда выделение 32-х килобайт памяти вызывало на вашу голову гнев команды оптимизации производительности).

Окей, давайте ещё раз посмотрим на этот комментарий.

Открывающий параграф упоминает возможность, когда расширение оболочки не прилинковывает к себе OLE32.DLL. Вариант (1) обсуждает расширение оболочки, которое использует OLE32, в этом случае оно должно использовать официальные OLE функции типа CoGetMalloc. Но вариант (2) обсуждает расширение оболочки, которое не использует OLE32. Таким расширениям оболочки следует использовать функции фальшивого-COM-оболочки типа SHGetMalloc, вместо функций настоящего COM, так, чтобы не создавалось зависимости от OLE32. Поэтому, если OLE32 ещё не была загружена, то и загрузка таких расширений оболочки не приводила к загрузке OLE32, экономя, таким образом, время на загрузку и инициализацию OLE32.DLL.

Итак, законченный вариант нашей схемы для 1995-го года теперь выглядит вот так:

До загрузки OLE32.DLL:

Менеджер памяти ShellМенеджер памяти OLE
SHAlloc/
SHFree
=SHGetMallocCoGetMalloc=CoTaskMemAlloc/
CoTaskMemFree

После загрузки OLE32.DLL:

Менеджер памяти ShellМенеджер памяти OLE
SHAlloc/
SHFree
=SHGetMalloc=CoGetMalloc=CoTaskMemAlloc/
CoTaskMemFree

Последний блок "Note" указывает на путь развития, которым пошла оболочка. Потом оказалось, что загрузка OLE32.DLL стала не такой болезненной как это было в Windows 95, поэтому оболочка могла выкинуть фальшивый-COM и использовать настоящий. В этот момент, обращение к менеджеру памяти Shell - это будет то же самое, что обращение к менеджеру памяти COM.

Вообще-то это время наступило давным-давно. Дни машин с 4 Мб памяти - это уже легенды сегодня. Оболочка выкинула свою мини-реализацию COM и сегодня везде использует полноценный COM.
Поэтому, современная схема - это та, которая со знаком равно. Все четыре функции взаимозаменяемы в Windows XP и выше.

Что, если вы хотите работать на старых системах? Ну, использовать CoTaskMemAlloc/CoTaskMemFree допустимо всегда. Почему? Ну, вы и сами можете догадаться. Поскольку эти функции экспортируются из OLE32.DLL, то факт их использования вами означает, что OLE32.DLL уже загружена, после чего в силу вступает последний вариант схемы, со знаком равно, и все находятся в одной большой счастливой семье.

Случай, когда вам нужно быть осторожным - это если ваша DLL не использует OLE32.DLL. В этом случае вы не знаете, находитесь ли вы в ситуации "До" или "После", поэтому вам лучше бы играть безопасно и использовать менеджер памяти Shell для работы с теми вещами, которые просят его по документации.

Я надеюсь, что это обсуждение также объясняет историческую подоплёку функции SHLoadOLE, которая сегодня просто ничего не делает, посольку OLE теперь загружается всегда. Но в старые дни, это был сигнал оболочке: "окей, настало время brain-dump твой фальшивый-COM в настоящий-COM".

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.