ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Часть 16 Часть 3. Расширенное использование ObjectWindows Глава 14. Управление памятью Обычно вам никогда не приходится иметь дело непосредственно со средствами управления памятью Windows. Это за вас делает Turbo Pascal. Вы просто используете обычные программы New и Dispose (или GetMem и FreeMem), которые вы всегда используете для размещен ия динамических переменных, а затем уже компилятор заботится о выделении необходимой области из глобальной динамической области Windows. Функция ObjectWindows MemAlloc кроме того может быть использована с дополнительной защитой проверки защиты динамическ ой области. (MemAlloc и защита динамической области описаны в Главе 19.) Однако, иногда могут возникнуть ситуации, когда вам нужно иметь дело непосредственно с менеджером памяти. Эти ситуации рассматриваются в следующих разделах, вместе с рекомендациями по размещению и использованию памяти Windows. Использование менеджера памяти При разработке программ на Turbo Pascal для Windows вы можете столкнуться с ситуациями, в которых требования вашей программы к памяти превышают объем, который может непосредственно предоставить компилируемая программа. Например, предположим, что ваше при ложение имеет функцию, которая загружает с диска файл побитового распределения и отображает его. Программе требуется некоторое пространство для хранения данных побитового распределения до отображения их средствами Windows. Данные побитового распределения могут быть очень большими, возможно даже слишком большими, чтобы разместиться в переменной Pascal. Для разрешения этой проблемы Windows имеет функции, которые позволяют вам выделять и манипулировать памятью вне собственной области памяти программы. Windows является многозадачной системой, поэтому несколько приложений могут использовать память одновременно. Windows управляет памятью с целью предоставления каждому приложению максимально эффективного использования памяти. В данной главе вам будет расс казано о типах имеющейся памяти Windows, и будут даны рекомендации по созданию и организации доступа к памяти Windows из ваших приложений. Как Windows управляет памятью В большинстве основанных на DOS систем управления памятью выделенная вами память остается зафиксированной в заданном местоположении в процессе использования. Однако, в Windows память может быть перемещаемой и высвобождаемой. Выделенный как перемещаемый б лок памяти не имеет фиксированного адреса. Windows может перемещать его в случае необходимости для наилучшего использования имеющейся памяти. Например, два отдельно выделенных блока памяти могут быть сдвинуты в непрерывный участок, высвобождая при этом б ольшой блок свободной памяти. Память Windows также может быть выделена как высвобождаемая. Высвобождаемая память аналогична перемещаемой памяти в том смысле, что Windows может переместить блок, но и высвободить его, выделяя блок нулевой длины. Это высвобождение приводит к разрушению всех данных, содержащихся в блоке. В случае необходимости приложение должно выделить блок памяти и загрузить его корректными данными. Этот процесс перемещения и высвобождения блоков памяти создает большое непрерывное пространство памяти и называется упло тнением. На Рис.14.1 показан пример уплотнения. Сначала (слева) имеются три непрерывных блока свободной памяти, разделенных блоками 1 и 2. Предположим, что возник запрос на память, объем которой превышает любой из имеющихся трех свободных блоков памяти. Предполож им, что блоки 1 и 2 перемещаемые. Уплотнение сдвигает эти блоки вместе, что создает большой непрерывный участок свободной памяти (в центре). Теперь предположим, что все еще недостаточно свободной памяти. Если блок 2 еще и высвобождаемый, то он будет удал ен из памяти (ему будет выделена нулевая длина), и объем доступной непрерывной свободной памяти станет еще больше. ЪДДДДДДДДДДДДї ЪДДДДДДДДДДДДї ЪДДДДДДДДДДДДї і свободно і і і і і ГДДДДДДДДДДДДґ і і і і і Блок 2 і і свободно і і свободно і ГДДДДДДДДДДДДґ і і і і і свободно і і і і і ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ і і і і і Блок 2 і і і і Блок 1 і ГДДДДДДДДДДДДґ ГДДДДДДДДДДДДґ і і і і і і і і і Блок 1 і і Блок 1 і ГДДДДДДДДДДДДґ і і і і і свободно і і і і і АДДДДДДДДДДДДЩ АДДДДДДДДДДДДЩ АДДДДДДДДДДДДЩ до уплотнения после уплотнения после уплотнения Рис.14.1. Пример уплотнения Регуляторы и адреса Из приведенных выше рассуждений вам может показаться, что с точки зрения приложения памятью Windows достаточно сложно управлять. К счастью это не соответствует действительности. Когда ваше приложение выделяет память Windows, оно получает регулятор блока памяти, а не его указатель. Этот регулятор не является адресом физического места памяти. Он больше похож на уникальное имя, которое приложение использует для ссылки на блок при вызове функций по управлению памятью. Можно представить себе регулятор как ук азатель на указатель. Если регулятор равен 0, то он некорректен. Однако, без адреса невозможно получить доступ к данным перемещаемого блока памяти. Для получения адреса блока его нужно локализовать. Локализация блока возвращает указатель на начало блока. Полученный в процессе локализации блока указатель будет действов ать на всем протяжении локализации блока, даже если блок перемещаемый и высвобождаемый. После окончания процесса доступа к данным блока его нужно деаллокировать. Если этого не сделать, то управление памятью Windows станет не таким эффективным, поскольку Windows не сможет переместить или высвободить локализованный блок для увеличения размеров доступной свободной памяти. После деаллокации блока полученный в процессе его локализации указатель будет некорректным. Недействующий указатель будет равен nil. Локальная и глобальная память Windows позволяет вашему приложению создавать и использовать блоки памяти. Эта память может выделяться из локальной и из глобальной динамической области. Глобальная динамическая область это вся память, которая свободна (не выделена) и доступна для всех п риложений. Windows, приложения Windows и выделенные приложениями Windows блоки глобальной памяти размещаются в глобальной динамической области. Локальная динамическая область это память, доступная только вашему приложению. Для каждого специфического запроса памяти нужно сделать выбор между локальной и глобальной памятью. Этот выбор может зависеть от ряда факторов: Локальная память: - Быстрый доступ. - Ограниченный размер (менее 64К байт). Глобальная память: - Блок выровнен на границу 32 байт. - Двадцать байт на блок зарезервировано Windows на перекрытие. - Системные ограничения не регулятор соответствуют режимам Standard и 386-Enhanced. - Медленный доступ. - Больший объем доступной памяти. Определяющим фактором обычно является требуемый объем памяти для решения вашей задачи. Если вам нужно более 1К памяти, то вероятно лучше использовать глобальную память. С другой стороны, если нужны маленькие блоки памяти, которые используются короткое вр емя, наилучшим вариантом выбора будет локальная память. Использование локальной динамической области Локальная динамическая область содержит память, к которой могут получить доступ только конкретные элементы вашего приложения. В Windows локальная динамическая область обычно устанавливается в сегменте данных приложения. Сегмент данных также содержит стек , глобальные переменные программы и глобальные переменные всех модулей, используемых программой. Все оставшееся пространство сегмента данных (до 64К) доступно для использования в качестве локальной динамической области. Поскольку Windows автоматически не предоставляет локальную динамическую область вашему приложению, вы должны задать размер локальной динамической области в процессе компиляции с помощью директивы компилятора $M. Второй параметр директивы компилятора $M у станавливает размер локальной динамической области вашего приложения (по умолчанию 8К). Дополнительную информацию по использованию $M можно найти в "Руководстве программиста", Глава 21, "Директивы компилятора". На максимальный размер локальной динамической области также влияет и то, будет ли сегмент данных, содержащий локальную динамическую область, фиксированным или перемещаемым. Если он фиксированный, то вы можете только выделить локальный блок с максимальным размером равным размеру динамической области, заданным вами при компиляции и компоновке. Если сегмент данных перемещаемый и сделан запрос на дополнительную память, который превышает заданный вами размер локальной динамической памяти, то Windows в попытк е удовлетворить запрос выделит дополнительную память (до границы 64К). Если это будет нужно, Windows может переместить сегмент данных для получения дополнительной памяти, отменяя все удаленные указатели на локальные данные. Размещение и доступ к локальной памяти Функция Windows LocalAlloc выделяет блок памяти, размер и тип которого задан в качестве параметров в вызове функции. Вы должны решить будет ли блок фиксированным, перемещаемым или перемещаемым и высвобождаемым (для того чтобы быть высвобождаемым блок дол жен быть перемещаемым). Эти опции задаются параметром Flags в вызове функции: - lmem_Fixed для фиксированного блока памяти. - lmem_Movable для перемещаемого блока памяти. - lmem_Movable or lmem_Discardable для высвобождаемого блока памяти. Когда в локальной динамической области выделяется новый блок, другие блоки локальной динамической области могут быть перемещены или высвобождены. Для предотвращения высвобождения этих блоков к параметру Flags нужно добавить lmem_NoDiscard. Для предотвращ ения перемещения или высвобождения других блоков нужно добавить lmem_NoCopmact. Функция LocalAlloc возвращает регулятор блока памяти, если выделение прошло успешно, и возвращает 0 если операция не удалась. Если выделение прошло успешно, то блок памяти будет по крайней мере затребованного размера, а может быть и больше. Для определен ия его действительного размера используется функция LocalSize. Максимально возможный размер локального блока определяется размером локальной динамической области. Даже если у вас есть регулятор блока, все все еще не можете получить доступ к данным. Для этого нужно локализовать блок, чтобы получить его адрес в памяти. Это делается вызовом функции LocalLock. LocalLock временно фиксирует местоположение блока в локаль ной динамической области и возвращает указатель на блок. Возвращаемый этой функцией адрес будет действовать до деаллокирования блока функцией LocalUnlock. LocalLock увеличивает значение счетчика локализаций блока. Счетчик локализаций блока, ссылочный сче тчик, является счетчиком числа фиксирований блока в памяти. Всегда нужно проверять возвращаемое функцией LocalLock значение. Не гарантируется, что это будет корректный указатель. Возвращаемое значение будет nil, если был передан некорректный регулятор или блок был высвобожден (блок должен быть lmem_Discardable). Следующий код выделяет блок из 256 байт локальной памяти: var LocalHandle; PtrLocalData: Pointer; begin LocalHandle:=LocalAlloc(lmem_Movable or lmem_Discardable, 256); if LocalHandle <> 0 then begin PtrLocalData:=LocalLock(LocalHandle); if PtrLocaalData <> nil then begin {обработка данных с использованием в качестве указателя PtrLocalData} LocalUnlock(LocalHandle); end else {ошибка блокировки}; end else {ошибка назначения}; end; Если блок локальной памяти был выделен с атрибутом lmem_Fixed, он не будет перемещаться в памяти. Следовательно, вам не нужно вызывать LocalLock для локализации объекта с фиксированным адресом. В этом и только в этом случае возвращаемый из вызова LocalAl loc 16-битовый регулятор будет 16-битовым адресом блока памяти. Высвобождение блоков локальной памяти Вы можете высвободить блоки локальной памяти использованием функции LocalFree. Высвобождение локального блока удаляет его содержимое из локальной динамической области и удаляет его регулятор из таблицы имеющихся регуляторов локальной памяти. Этот регулят ор уже нельзя повторно использовать. Приложение должно всегда высвобождать все блоки памяти до своего завершения и должно высвобождать любой блок памяти, которой ей больше не нужен. Можно явно высвободить блок локальной памяти с использованием функции LocalDiscard. Высвобождаемый локальный блок изменяет свой размер на нулевой, при этом его содержимое удаляется из локальной динамической области. Однако, его регулятор все еще остается доступным и может быть повторно использован вызовом функции LocalReAlloc с регулятором и новым значением размера. Вы можете сократить некоторое время на обработке вашего приложения высвобождением и повторным выделением регуляторов вместо их удаления и п овторного создания. var LocalHandle: THandle; begin {LocalHandle уже назначен} LocalDiscard(LocalHandle); {повторное использование регулятора для создания нового блока памяти} LocalHandle:=LocalReAlloc(LocalHandle, 256, lmem_Movable); ... end; Блок локальной памяти не может быть удален или высвобожден, если его счетчик локализаций равен нулю. Перемещение и модификация блоков локальной памяти Вы можете изменить размер блока сохранив при этом его содержание с использованием функции LocalReAlloc. Windows выделит необходимый размер для блока, если заданный вами размер меньше текущего. Если новый заданный размер больше текущего, то новая область будет содержать нули, если задан lmem_ZeroInit. В противном случае она будет содержать неопределенные данные. Как и для LocalAlloc имеющийся локальный блок может быть перемещен или высвобожден при повторном размещении блока с использованием LocalReAlloc. Вы можете задать lmem_NoDiscard или lmem_NoCompact для предотвращения высвобождения или перемещения других блоков при повторном размещении. LocalReAlloc может также быть использован для изменения атрибутов блока c lmem_Movable на lmem_Discardable. В дополнение к новому атрибуту вы должны задать lmem_Modify. Нельзя использовать LocalReAlloc с атрибутом lmem_Modify для изменения с/на lmem_Fixe d. Данный код меняет тип блока на высвобождаемый: var LocalHandle: THandle; begin LocalHandle:=LocalAlloc(lmem_Movable, 256); LocalHandle:=LocalReAlloc(LocalHandle, 256, lmem_Modify or lmem_Discardable); end; Запрос блоков локальной памяти Для получения информации относительно блоков локальной памяти можно использовать функции LocalSize и LocalFlags. LocalSize возвращает размер локального блока. При выделении памяти может быть создан блок большего размера, чем был запрошен, поэтому вам в с лучае необходимости следует использовать LocalSize для определения действительного размера блока. var LocalHandle: THandle; BlockSize: Word; begin LocalHandle:=LocalAlloc(lmem_Movable, 256); if LocalHandle <> 0 then BlockSize:=LocalSize(LocalHandle); end; LocalFlags используется для определения состояния локального счетчика блока памяти, является ли блок высвобождаемым и, если это так, был ли он высвобожден. var LocalHandle: THandle; Flags, LockCount: Word; begin {предположим, что блок был назначен и возможно блокирован} Flags:=LocalFlags(LocalHandle); LockCount:=Flags and lmem_LockCount; end; Вы можете использовать функцию LocalCompact для получения размера максимального блока свободной памяти в локальной динамической области. Для этого нужно просто задать в качестве параметра ноль. Замечания о программировании Вы всегда должны деаллокировать ваши перемещаемые и высвобождаемые блоки памяти как только доступ к ним закончен. Это тем более важно, если вы будете выделять другие блоки в локальной динамической области. Если эти блоки не деаллокировать, то Windows не может их переместить для создания достаточной свободной памяти для будущих выделений. Вы также должны удалить все блоки памяти до выхода из вашей программы. Использование глобальной динамической области Глобальная динамическая область это системная память, которая совместно используется Windows и приложениями. Размеры доступной вашему приложению глобальной динамической области памяти зависят от режима, в котором работает Windows. Поскольку глобальная п амять используется совместно, ваше приложение должно пытаться использовать ее максимально эффективно, иначе пострадают общие характеристики функционирования системы. Размещение и доступ к глобальной памяти Функция GlobalAlloc выделяет блок памяти, размер и тип которого задан в качестве параметров в вызове функции. Как и при выделении локальной памяти вы должны решить, будет ли блок фиксированным, перемещаемым или перемещаемым и высвобождаемым (для того что бы быть высвобождаемым блок должен быть перемещаемым). Эти опции задаются параметром Flags в вызове функции: - gmem_Fixed для фиксированного блока памяти. - gmem_Movable для перемещаемого блока памяти. - gmem_Movable or gmem_Discardable для высвобождаемого блока памяти. Когда в глобальной динамической области выделяется новый блок, другие блоки глобальной динамической области могут быть перемещены или высвобождены. Для предотвращения высвобождения этих блоков к параметру Flags нужно добавить gmem_NoDiscard. Для предотвр ащения перемещения или высвобождения других блоков нужно добавить gmem_NoCopmact. Функция LocalAlloc возвращает регулятор блока памяти, если выделение прошло успешно, и возвращает 0 если операция не удалась. Возвращаемое GlobalAlloc значение всегда нужно проверять с целью определения успешности проведения операции выделения. Если выде ление прошло успешно, то блок памяти будет по крайней мере затребованного размера, а может быть и больше. Для определения его действительного размера используется функция GlobalSize (описанная ниже). Максимально возможный размер глобального блока который Windows может выделить в режиме Standard - 1Mбайт, в режиме 386-Enhanced - 64Mбайта. Для получения адреса блока глобальной памяти нужно локализовать этот блок. Это делается вызовом функции GlobalLock. В реальном режиме GlobalLock временно фиксирует местоположение блока в глобальной динамической области, в других режимах блок не фиксирует ся. Однако, во всех режимах GlobalLock возвращает удаленный указатель на блок, который будет действовать до деаллокирования блока функцией GlobalUnlock. Всегда нужно проверять возвращаемое функцией GlobalLock значение. Не гарантируется, что это будет корректный указатель. Возвращаемое значение будет nil, если был передан некорректный регулятор или блок был высвобожден (блок должен быть выделен как gmem_D iscardable). Следующий код выделяет блок глобальной памяти: type PBigArray=^TBigArraay; TBigArray=array[0..20000] of Byte; var GlobalHandle; PtrGlobalData: PBigArray; Value: Byte; begin GlobalHandle:=GlobalAlloc(gmem_Movable, 20000); if GlobalHandle <> 0 then begin PtrGlobalData:=GlobalLock(GlobalHandle); if PtrGlobalData <> nil then begin {обработка данных с использованием в качестве указателя GlobalData} {самый простой пример} PtrGlobalData:=255; Value:=PtrGlobalData^(0); GlobalUnlock(GlobalHandle); end else {ошибка блокировки}; end else {ошибка назначения}; end; В режиме Real, GlobalLock должен зафиксировать блок в памяти до момента его деаллокирования. Счетчик локализации увеличивается на единицу, показывая тем самым действительную фиксацию памяти и запрещая удаление или высвобождение до момента деаллокации. Вы зов GlobalUnlock уменьшает значение счетчика локализации на единицу. В режимах Windows Standard и 386-Enhanced значение счетчика локализации не увеличивается, если только блок не высвобождаемый, т.к. блок в действительности не фиксируется в памяти. Указа тель всегда будет иметься, даже если Windows может переместить блок в памяти. В отличие от блоков локальной памяти, если глобальный блок выделен с атрибутом gmem_Fixed, этот блок все равно нужно локализовать до обращения к его данным. В этом случае возвращаемый GlobalAlloc регулятор не будет указательом блока. Другие способы локализации блоков глобальной памяти Есть ряд дополнительных функций, которые можно использовать для локализации и деаллокации глобальной памяти. GlobalFix локализует блок в памяти и предохраняет его от перемещения, независимо от режима работы Windows. Его счетчик локализации увеличивается на единицу, т.к. блок действительно фиксируется. Не используйте GlobalFix без крайней необходимости, т.к. он может воздействовать на уплотнение памяти Windows. Очень немногие приложения требуют фиксации памяти таким способом, поскольку указатель остается доступным даже в случае перемещения блока. GlobalUnlock деаллокирует блок и уменьшает значение его счетчика локализации. GlobalWire перемещает блок в нижнюю область памяти и локализует его, увеличивая при этом значение счетчика локализации на единицу. Удобно использовать GlobalWire для блоков памяти, которые нужно локализовать на длительное время. Блок памяти помещается в ее нижнюю область, поэтому будучи фиксированным он не будет влиять на уплотнение памяти Windows. GlobalUnWire деаллокирует блок и уменьшает счетчик локализации. Высвобождение блоков глобальной памяти Блоки глобальной памяти могут быть удалены и высвобождены аналогично локальным блокам. Единственное отличие состоит в именах функций, GlobaaalFree и GlobalDiscard. Перемещение и модификация блоков глобальной памяти Вы можете изменить размер блока сохранив при этом его содержание с использованием функции GlobalReAlloc. Windows выделит необходимый размер для блока, если заданный вами размер меньше текущего. Если новый заданный размер больше текущего, то новая область будет содержать нули, если задан gmem_ZeroInit. В противном случае она будет содержать неопределенные данные. Как и для GlobalAlloc имеющийся глобальный блок может быть перемещен или высвобожден при повторном размещении блока с использованием GlobalReAl loc. Вы можете задать gmem_NoDiscard или gmem_NoCompact для предотвращения высвобождения или перемещения других блоков при повторном размещении. GlobalReAlloc может также быть использован для изменения атрибутов блока c gmem_Movable на gmem_Discardable. В дополнение к новому атрибуту вы должны задать gmem_Modify. Можно использовать GlobalReAlloc с атрибутом gmem_Modify для изменения с/на gmem_Fix ed. Обратите внимание на то, что этого нельзя делать для локальных блоков с LocalReAlloc. Если вы повторно размещаете блок памяти с новым размером, который превосходит 64К, Windows может возвратить новый регулятор для этого блока. Если ваше приложение работает в стандартном режиме, то это применимо к повторному размещению блока, превышающего 65519 байт. Поэтому вы должны сохранить старый регулятор и проверить новый регулятор, который возвращается GlobalReAlloc. Если GlobalReAlloc завершился успешно, вы должны сделать старый регулятор равным новому. В противном случае вам нужно хранить старый регулятор: var GlobalHandlle: THandle; TempHandle: THandle; begin {TempHandle первоначально назначен для for 32K} {переназначение последней границы 64K} TempHandle:=GlobalAlloc(GlobaalHandle, 102400), gmem_Moveble); if TempHandle <> 0 then GlobalHandle:=TempHandle; {в случае его изменения} else {блок не был переназначен} end; Запрос блоков глобальной памяти Для получения информации относительно блоков глобальной памяти можно использовать функции GlobalSize и GlobalFlags. GlobalSize возвращает размер глобального блока. GlobalFlags используется для определения состояния счетчика локализации блока памяти, явля ется ли блок высвобождаемым и, если это так, был ли он высвобожден. GlobalFlags также показывает, был ли блок выделен как gmem_DDEShare или как gmem_Not_Banked. var GlobalHandle: THandle; flags: Word; begin Flags:=GlobalFlags(GlobalHandle); {test if block is discardable} if Flags and gmem_Discardable then {block is discardable} end; Вы можете использовать функцию GlobalCompact для получения размера максимального блока свободной памяти в глобальной динамической области. Для этого нужно просто задать в качестве параметра ноль. Если задать ноль, то никакого уплотнения не происходит. Ес ли уплотнение действительно имеет место, то возвращаемое значение есть максимальный свободный блок. Если в качестве параметра GlobalCompact задать -1, то форсированно будут освобождены все нелокализованные блоки. var GlobalHandle: THandle; largestSize: Longint; begin largestSize:=GlobalCompact(Long(0)); GlobalCompact(Long(-1)); {forcibly discard all unlocked blocks} end; Изменение глобального высвобождения Для определения блока, который высвобождаемый блок должен быть высвобожден в случае необходимости, Windows использует алгоритм "наиболее давно использованного" (LRU - least recentle used). Используя функции GlobalLRUOldest и GlobalLRUNewest вы можете зад ать, где находится конкретный блок памяти в списке на высвобождение. Вызов GlobalLRUOldest покажет Windows, что заданный в качестве параметра функции блок будет высвобождаться следующим. GlobalLRUNewest ставит указанный блок в конец списка на высвобожден ие. Он будет высвобожден последним. Получение предупреждений о нехватке памяти Когда Windows замечает, что тратит слишком много времени на уплотнение памяти, всем окнам верхнего уровня текущих работающих приложений будет послано сообщение wm_Compacting. Это сообщение указывает на недостаточный объем памяти системы. При этом каждое приложение должно немедленно высвободить как можно больше глобальной памяти. Замечания о программировании Хорошим приемом программирования является деаллокирование блоков вашей глобальной памяти сразу же после окончания доступа к ним. Если не деаллокировать эти блоки, то Windows не может их сдвинуть для создания достаточного объема свободной памяти при буду щих размещениях. Перед выходом из вашей программы вы также должны освободить все блоки памяти. Если вы решите использовать глобальную память, вам следует избегать выделения маленьких блоков памяти (менее 128 байт) в глобальной динамической области из-за перекрытия и выравнивания по байтам глобальной памяти. Вы также должны избегать выделения больш ого числа блоков глобальной памяти. Лучше объединять их в меньшее число блоков большего размера. Никогда не передавайте обычные регуляторы глобальной памяти между приложениями для создания областей памяти совместного использования. Для создания памяти совместного доступа используйте только DEE и буфер вырезанного изображения. См. Главу 16, "Динамиче ский обмен данными". |