Операционная система Microsoft Windows 3.1 для программиста© Александр Фролов, Григорий ФроловТом 13, М.: Диалог-МИФИ, 1993, 284 стр. 2.3. Работа с памятью в приложениях WindowsВ этом разделе мы расскажем вам о типах памяти, используемых приложениями Windows и о том, как приложения могут заказать для себя память. Глобальная динамическая памятьВ Windows версии 3.1 область глобальной памяти общая для всех приложений Windows. Теоретически одно приложение может заказать для себя блок памяти из глобальной области и передать его идентификатор другому приложению, однако такая практика не приветствуется, так как в следующих версиях Windows адресные пространства приложений могут быть разделены (для передачи данных между приложениями необходимо использовать механизм динамической передачи данных DDE, который будет описан в одном из следующих томов "Библиотеки системного программиста"). Получение глобального блока памятиДля получения глобального блока памяти вы должны использовать функцию GlobalAlloc : HGLOBAL WINAPI GlobalAlloc(UINT fuAlloc, DWORD cbAlloc); Параметр fuAlloc определяет тип выделяемой памяти. Размер блока памяти в байтах должен передаваться через параметр cbAlloc, причем вы можете заказать блок памяти размером больше, чем 64 Кбайт. Для стандартного режима работы Windows можно заказать блок памяти размером до 1 Мбайт без 80 байт, для расширенного - до 16 Мбайт без 64 Кбайт. Функция возвращает идентификатор глобального блока памяти или NULL, если Windows не может выделить память указанного объема. Параметра fuAlloc должен быть указан как логическая комбинация следующих значений:
Идентификатор, полученный от функции GlobalAlloc, нельзя использовать для адресации памяти непосредственно. Напомним, что пока вы не зафиксировали блок памяти, его логический адрес недоступен. Приведем для примера фрагмент кода, в котором мы получаем перемещаемый блок памяти размером 200000 байт, причем во все байты полученного блока записываются нулевые значения: hmemGlobal = GlobalAlloc(GHND, 200000l); В следующем фрагменте мы заказываем удаляемый блок памяти размером 200000 байт, который никак не инициализируется: hmemGlobalDisc = GlobalAlloc( GMEM_MOVEABLE | GMEM_DISCARDABLE, 200000l); Фиксирование и расфиксирование блока памятиДля получения доступа к полученному блоку памяти его необходимо зафиксировать, вызвав функцию GlobalLock : void FAR* WINAPI GlobalLock(HGLOBAL hglb); Функция GlobalLock фиксирует блок памяти, идентификатор которого передается ей через параметр hglb и возвращает логический адрес зафиксированного блока или NULL, если указанный блок удален или произошла ошибка. В защищенном режиме работы Windows продолжает перемещать блоки памяти, зафиксированные функцией GlobalLock, изменяя базовый адрес в локальной таблице дескрипторов. Даже для фиксированных блоков памяти вызов этой функции все же необходим, потому что нужно получить логический адрес блока памяти. Для каждого блока памяти Windows поддерживает счетчик фиксирований. Этот счетчик увеличивается при вызове функции GlobalLock и уменьшается при расфиксировании блока функцией GlobalUnlock : BOOL WINAPI GlobalUnlock(HGLOBAL hglb); Если содержимое счетчика уменьшилось до нуля, функция возвращает значение FALSE. В противном случае возвращается TRUE. Файл windowsx.h содержит макрокоманды, облегчающие работу с глобальными блоками памяти. Например, макрокоманда GlobalAllocPtr получает блок памяти и фиксирует его: #define GlobalAllocPtr(flags, cb) \ (GlobalLock(GlobalAlloc((flags), (cb)))) В данном случае идентификатор полученного блока памяти не сохраняется, так как он не нужен. Вы можете освободить блок памяти, зная только его логический адрес (см. следующий раздел). Определение идентификатора блока памяти по его адресуС помощью функции GlobalHandle вы можете, зная селектор блока памяти, определить его идентификатор: DWORD WINAPI GlobalHandle(UINT uGlobalSel); Параметр uGlobalSel указывает селекторную компоненту логического адреса блока памяти. Младшее слово возвращаемого значения содержит идентификатор блока памяти, старшее - селектор блока памяти. В случае ошибки возвращается нулевое значение. В файле windowsx.h определена макрокоманда GlobalPtrHandle , упрощающая получение идентификатора блока памяти по его логическому адресу: #define GlobalPtrHandle(lp) \ ((HGLOBAL)LOWORD(GlobalHandle(SELECTOROF(lp)))) Макрокоманда SELECTOROF определена в файле windows.h и предназначена для получения селекторной компоненты логического адреса: #define SELECTOROF(lp) HIWORD(lp) В файле windows.h есть также определения для макрокоманды OFFSETOF , возвращающей компоненту смещения, и макрокоманда MAKELP , конструирующая указатель из компонент смещения и селектора: #define OFFSETOF(lp) LOWORD(lp) #define MAKELP(sel, off) ((void FAR*)MAKELONG((off), (sel))) #define MAKELONG (low, high) ((LONG)(((WORD)(low)) | \ (((DWORD)((WORD)(high))) << 16))) Если вы работаете с транслятором Borland C++ for Windows, вместо этих макрокоманд можете использовать знакомые вам макрокоманды FP_SEG , FP_OFF и MK_FP , описанные в файле dos.h: #define FP_SEG(fp)((unsigned)(void _seg*)(void far*)(fp)) #define FP_OFF(fp)((unsigned)(fp)) #define MK_FP(seg,ofs)((void _seg*)(seg)+(void near*)(ofs)) Работа с удаляемыми блоками памятиДля того чтобы заказать удаляемый блок памяти, вы должны указать флаги GMEM_DISCARDABLE и GMEM_MOVEABLE , например: hmemGlDiscard = GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE, 200000l); При необходимости Windows может удалить полученный таким образом блок из памяти, сохранив только его идентификатор, и использовать ранее распределенную этому блоку памяти для других приложений. При попытке зафиксировать удаленный блок с помощью функции GlobalLock приложение получит от этой функции значение NULL. В этом случае следует вызвать функцию GlobalFlags , предназначенную для определения состояния блока памяти, и проверить, находится ли данный блок в удаленном состоянии. Приведем прототип функции GlobalFlags: UINT WINAPI GlobalFlags(HGLOBAL hglb); Функция возвращает состояние блока памяти, указанного своим единственным параметром. Младший байт возвращаемого значения содержит содержимое счетчика фиксаций блока памяти. В старшем байте могут быть установлены флаги GMEM_DISCARDABLE и GMEM_DISCARDED. Если установлен флаг GMEM_DISCARDABLE, проверяемый блок памяти может быть удален Windows в процессе дефрагментации свободной области памяти. Если же установлен флаг GMEM_DISCARDED, удаление блока памяти уже произошло. Для получения доступа к удаленному блоку памяти его необходимо восстановить, вызвав функцию GlobalReAlloc. Эта функция позволяет изменить характеристики существующего блока памяти. Приведем прототип функции GlobalReAlloc : HGLOBAL WINAPI GlobalReAlloc(HGLOBAL hglb, DWORD cbNewSize, UINT fuAlloc); Параметр hglb указывает идентификатор восстанавливаемого блока памяти. При помощи параметра cbNewSize вы должны указать размер блока памяти, причем можно восстановить удаленный блок памяти и изменить его размер одновременно. Параметр fuAlloc определяет тип восстановленного блока памяти. Можно указывать логическую комбинацию следующих флагов:
Восстановив блок памяти, вы должны зафиксировать его функцией GlobalLock и затем восстановить прежнее содержимое, так как после удаления от блока остался только идентификатор. Изменение блока памятиКак мы уже говорили, функция GlobalReAlloc , описанная выше, позволяет изменить характеристики глобального блока памяти. Если вам, например, надо изменить размер заказанного ранее блока памяти, сделав его равным 51200 байт, вы можете для этого использовать следующий фрагмент кода: hmemGlobal = GlobalReAlloc(hmemGlobal, 51200, GMEM_MODIFY | GMEM_DISCARDABLE | GMEM_MOVEABLE | GMEM_ZEROINIT); После вызова функции блок памяти будет перемещаемый и удаляемый, причем если раньше его размер был меньше 51200 байт, во все байты дополнительной памяти будут записаны нулевые значения. Учтите, что при увеличении размера блока может возникнуть ситуация нехватки памяти, поэтому проверяйте значение идентификатора, возвращаемой функцией GlobalReAlloc, на неравенство константе NULL. Вы можете инициировать удаление блока памяти при помощи функции GlobalReAlloc, если укажите нулевой размер блока и флаг GMEM_MOVEABLE. В файле windows.h имеется определение макрокоманды GlobalDiscard, при помощи которой приложение может принудительно удалить блок из памяти: #define GlobalDiscard(h) GlobalReAlloc(h, 0L, GMEM_MOVEABLE) Определение размера блока памятиС помощью функции GlobalSize вы можете определить размер блока памяти по его идентификатору: DWORD WINAPI GlobalSize(HGLOBAL hglb); Эта функция возвращает размер блока памяти, идентификатор которого задан параметром hglb. Если указанный блок памяти не существует или удален, возвращается нулевое значение. Дефрагментация памятиЕсли вызвать функцию GlobalCompact , операционная система Windows выполнит объединение всех свободных блоков в один. В качестве параметра этой функции необходимо указать требуемый размер непрерывного блока свободной памяти: DWORD WINAPI GlobalCompact(DWORD dwMinFree); Функция возвращает размер самого большого доступного непрерывного блока памяти, причем, если параметр на равен 0 или -1, выполняется дефрагментация памяти и удаление блоков, отмеченных как удаляемые. Если параметр функции указан как 0 или -1, функция не выполняет дефрагментацию памяти, но возвращает правильное значение с учетом возможного выполнения дефрагментации. Получение памяти в первом мегабайте адресного пространстваЕсли у вас возникает необходимость заказать память, доступную приложениям MS-DOS, и располагающуюся в первом мегабайте адресного пространства, воспользуйтесь функцией GlobalDosAlloc : DWORD WINAPI GlobalDosAlloc(DWORD cbAlloc); Параметр cbAlloc определяет размер блока в байтах. Старшее слово возвращаемого значения содержит сегментную компоненту адреса реального режима, соответствующего полученному блоку памяти. Это значение может использоваться для доступа к блоку памяти в реальном режиме работы процессора. Младшее слово возвращаемого значения содержит селектор для доступа к полученному блоку в защищенном режиме. Память, заказанная при помощи функции GlobalDOSAlloc, является фиксированной, поэтому вам не следует вызывать функцию GlobalLock для фиксирования и получения логического адреса. Для освобождения блока памяти, полученного при помощи функции GlobalDOSAlloc, следует вызвать функцию GlobalDosFree : UINT WINAPI GlobalDosFree(UINT uSelector); В качестве параметра uSelector этой функции следует передать селекторную компоненту блока памяти, расположенного в первом мегабайте адресного пространства. В случае успешного завершения возвращаемое значение равно нулю. При ошибке возвращается селектор, указанный в параметре uSelector. Учтите, что заказывать памяти в первом мегабайте можно только в случае крайней необходимости, когда, вам, например, нужно обеспечить одновременный доступ к памяти со стороны приложений Windows и программ MS-DOS. Уменьшение свободного пространства в первом мегабайте адресного пространства отрицательно сказывается на производительности работы приложений Windows. Освобождение глобального блока памятиДля освобождения глобального блока памяти, полученного от функции GlobalAlloc, вы должны использовать функцию GlobalFree : HGLOBAL WINAPI GlobalFree(HGLOBAL hglb); Идентификатор освобождаемого блока передается функции в качестве ее единственного параметра. Функция возвращает NULL при успешном завершении или значение hglb при ошибке. Перед освобождением зафиксированных блоков памяти их следует предварительно расфиксировать. Вы можете узнать содержимое счетчика фиксаций блока при помощи функции GlobalFlags, описанной выше. Для освобождения памяти, полученной при помощи макрокоманды GlobalAllocPtr, удобно использовать макрокоманду GlobalFreePtr , описанную в файле windowsx.h: #define GlobalFreePtr(lp) \ (GlobalUnlockPtr(lp),(BOOL)GlobalFree(GlobalPtrHandle(lp))) Данная макрокоманда расфиксирует, а затем и освобождает блок памяти, заданный своим логическим адресом. Фиксирование линейного адреса блока памятиВ некоторых случаях вам необходимо запретить изменение линейного адреса блока памяти, которое выполняется в процессе перемещения. Такое изменение выполняется заменой базового адреса в соответствующем дескрипторе локальной таблицы дескрипторов. В результате при перемещении блока логический адрес остается постоянным (так как при перемещении блока ему не присваивается новый дескриптор, а логический адрес состоит из ссылки на дескриптор, номера запрошенного кольца защиты и смещения), в то время как линейный может изменяться. Если драйвер какого-либо устройства ввода/вывода работает с линейным адресом буфера, память, отведенная для такого буфера в некоторых случаях должна быть зафиксирована функцией GlobalFix : void WINAPI GlobalFix(HGLOBAL hglb); Параметр функции указывает идентификатор фиксируемого блока памяти. Как только отпадет необходимость в фиксировании блока памяти, его следует расфиксировать, вызвав функцию GlobalUnfix : void WINAPI GlobalUnfix(HGLOBAL hglb); Единственный параметр этой функции должен содержать идентификатор блока памяти, который будет расфиксирован. Фиксирование страниц блока памятиВ расширенном режиме работы операционной системы Windows версии 3.1 используется виртуальная память. Как мы уже говорили, при использовании виртуальной памяти вся глобальная область памяти делится на страницы размером 4 Кбайт. Эти страницы могут располагаться в физической оперативной памяти или на диске в специальном файле виртуальной памяти. Если приложение обращается к странице, которая отсутствует в физической оперативной памяти, она загружается туда из файла виртуальной памяти. Однако на загрузку страницы памяти из файла требуется значительное время. В некоторых случаях необходимо обеспечить постоянное присутствие блока памяти в физической оперативной памяти. Фиксирование блока памяти функцией GlobalFix не предотвращает сброс страниц памяти, распределенных блоку, в файл виртуальной памяти, а всего лишь запрещает перемещение блока памяти в линейном адресном пространстве. Для исключения страниц памяти, принадлежащих указанному блоку памяти, из процесса страничного обмена необходимо использовать функцию GlobalPageLock : UINT WINAPI GlobalPageLock(HGLOBAL hglb); Идентификатор блока, для которого необходимо запретить страничный обмен, указывается через параметр hglb. Операционная система Windows поддерживает счетчик блокирования страничного обмена. Содержимое этого счетчика увеличивается на единицу при каждом вызове функции GlobalPageLock. Функция GlobalPageLock возвращает новое значение счетчика или ноль при ошибке. Как только надобность в блокировке страничного обмена отпадает, следует вызвать функцию GlobalPageUnlock : UINT WINAPI GlobalPageUnlock(HGLOBAL hglb); Эта функция разрешает страничный обмен для блока памяти, заданного параметром hglb. Функция возвращает текущее значение счетчика или ноль при ошибке. Приложение GMEMДля демонстрации использования основных функций управления глобальной памятью мы приведем исходный текст приложения GMEM (листинг 2.3). Листинг 2.3. Файл gmem/gmem.cpp #define STRICT #include <windows.h> #include <windowsx.h> #include <dos.h> #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { BYTE szBuf[100]; HGLOBAL hmemGlobal; HGLOBAL hmemGlDiscard; LPVOID lpvoidGlobal; LPVOID lpvoidGlDiscard; DWORD dwMaxFreeMem; // Определяем размер доступной памяти dwMaxFreeMem = GlobalCompact(-1l); wsprintf(szBuf, "Доступно памяти:\t%lu\n", dwMaxFreeMem); MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK); // -------------------------------------------------------- // Работаем с перемещаемым блоком памяти // -------------------------------------------------------- // Дефрагментируем память для получения блока // размером 100000 байт dwMaxFreeMem = GlobalCompact(100000l); // Заказываем буфер размером 100000 байт hmemGlobal = GlobalAlloc(GHND, 100000l); if(hmemGlobal != NULL) { // Если буфер получен, фиксируем его в памяти lpvoidGlobal = GlobalLock(hmemGlobal); if(lpvoidGlobal != (LPVOID) NULL) { // Если блок успешно зафиксирован, // выводим значения идентификатора блока // и логический адрес блока wsprintf(szBuf, "hmemGlobal=\t%04.4X\n" "lpvoidGlobal=\t%04.4X:%04.4X", hmemGlobal, FP_SEG(lpvoidGlobal), FP_OFF(lpvoidGlobal)); MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK); // ----------------------------------------- // Можно работать с полученным блоком памяти // Записываем в первый байт блока символ S *(LPSTR)lpvoidGlobal = 'S'; // ----------------------------------------- // Разрешаем перемещение блока GlobalUnlock(hmemGlobal); } else { MessageBox(NULL, "Ошибка при фиксировании блока", "Global Block", MB_OK); } // Отдаем блок памяти операционной системе GlobalFree(hmemGlobal); } else { MessageBox(NULL, "Мало памяти для перемещаемого блока", "Global Block", MB_OK); } // -------------------------------------------------------- // Работаем с удаляемым блоком памяти // -------------------------------------------------------- // Заказываем удаляемый блок памяти размером 200000 байт hmemGlDiscard = GlobalAlloc(GMEM_MOVEABLE | GMEM_DISCARDABLE, 200000l); if(hmemGlDiscard != NULL) { // Если мы его получили, удаляем блок GlobalDiscard(hmemGlDiscard); // Пытаемся зафиксировать блок памяти lpvoidGlDiscard = GlobalLock(hmemGlDiscard); if(lpvoidGlDiscard != (LPVOID) NULL) { // Если удалось (чего не должно быть, так как // мы только что удалили блок), выводим // идентификатор блока и логический адрес wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n" "lpvoidGlDiscard=\t%04.4X:%04.4X", hmemGlDiscard, FP_SEG(lpvoidGlDiscard), FP_OFF(lpvoidGlDiscard)); MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK); // Разрешаем перемещение блока GlobalUnlock(hmemGlDiscard); } else { // Если блок памяти не удалось зафиксировать, // проверяем, не был ли он удален if(GlobalFlags(hmemGlDiscard) & GMEM_DISCARDED) { MessageBox(NULL, "Блок удален и мы его восстанавливаем", "Global Block", MB_OK); // Восстанавливаем удаленный блок памяти hmemGlDiscard = GlobalReAlloc(hmemGlDiscard, 200000l, GMEM_MOVEABLE | GMEM_DISCARDABLE); // Фиксируем блок памяти lpvoidGlDiscard = GlobalLock(hmemGlDiscard); if(lpvoidGlDiscard != (LPVOID) NULL) { // Выводим идентификатор и логический адрес // зафиксированного блока памяти wsprintf(szBuf, "hmemGlDiscard=\t%04.4X\n" "lpvoidGlDiscard=\t%04.4X:%04.4X", hmemGlDiscard, FP_SEG(lpvoidGlDiscard), FP_OFF(lpvoidGlDiscard)); MessageBox(NULL, (LPSTR)szBuf, "Global Block", MB_OK); // Освобождаем блок памяти GlobalUnlock(hmemGlDiscard); } else { MessageBox(NULL, "Ошибка при фиксировании блока", "Global Block", MB_OK); } } } // Отдаем удаляемый блок памяти операционной системе GlobalFree(hmemGlDiscard); } else { MessageBox(NULL, "Мало памяти для удаляемого блока", "Global Block", MB_OK); } return 0; } Перед началом работы приложение определяет объем свободной памяти, вызывая функцию GlobalCompact со значением параметра, равным -1. Определенное значение выводится на экран при помощи функции MessageBox. Далее приложение вызывает функцию GlobalCompact еще раз для освобождения непрерывного блока свободной памяти размером 100000 байт. После этого приложение заказывает буфер размером 100000 байт, вызывая функцию GlobalAlloc. В качестве первого параметра этой функции указана константа GHND, соответствующее перемещаемой памяти, инициализированной нулевым значением. В случае успешного получения блока памяти он фиксируется и на экран выводится значение идентификатора блока и его логический адрес. После этого в первый байт полученного блока записывается код символа 'S' и блок расфиксируется. При ошибке выдается сообщение. Далее полученный блок освобождается и возвращается операционной системе, для чего вызывается функция GlobalFree. После этого приложение заказывает удаляемый блок памяти размером 200000 байт и сразу же удаляет его, вызывая макрокоманду GlobalDiscard. Затем приложение предпринимает попытку зафиксировать только что удаленный блок памяти, вызывая функцию GlobalLock. Если блок памяти удалился успешно, функция GlobalLock должна вернуть значение NULL. В этом случае нам надо убедиться в том, что блок был удален, и если это так и есть, восстановить удаленный блок. Для проверки приложение использует функцию GlobalFlags. Если блок, идентификатор которого был передан этой функции в качестве параметра, удален, в возвращаемом значении установлен флаг GMEM_DISCARDED. Для восстановления удаленного блока приложение вызывает функцию GlobalReAlloc, указывая размер и характеристики блока. Затем восстановленный блок фиксируется в памяти и на экран выводится его идентификатор и логический адрес. Перед завершением работы приложения блок расфиксируется и освобождается. Файл определения модуля приложения GMEM приведен в листинге 2.4. Листинг 2.4. Файл gmem/gmem.def ; ============================= ; Файл определения модуля ; ============================= NAME GMEM DESCRIPTION 'Приложение GMEM, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' CODE preload moveable discardable DATA preload moveable multiple Локальная динамическая памятьДля каждого приложения Windows создается автоматический сегмент данных размером 64 Кбайт, в котором располагаются статические данные , стек и локальная область данных (local heap ). Кроме этого, автоматический сегмент данных имеет заголовок размером 16 байт (рис. 2.10).
Рис. 2.10. Автоматический сегмент данных приложения Windows Размер стека определяется оператором STACKSIZE в файле определения модуля: STACKSIZE 8120 Минимальный размер стека, назначаемый Windows для приложений, составляет 5 Кбайт. Следует отметить, что в руководстве к SDK нет точного описания способа определения минимально необходимого объема стека. В этом руководстве предлагается определить этот объем экспериментально, причем подчеркивается, что результаты переполнения стека непредсказуемы. В отличие от стека, размер локальной области данных может при необходимости увеличиваться автоматически. Начальное значение задается в файле определения модуля при помощи оператора HEAPSIZE : HEAPSIZE 1024 Вы можете указать любое отличное от нуля значение. Для работы с локальной областью данных программный интерфейс Windows содержит функции, аналогичные предназначенным для работы с глобальной областью данных. Получение локального блока памятиДля получения локального блока памяти вы должны использовать функцию LocalAlloc : HLOCAL WINAPI LocalAlloc(UINT fuAlloc, UINT cbAlloc); Параметр fuAlloc определяет тип выделяемой памяти. Размер блока памяти в байтах должен передаваться через параметр cbAlloc. Функция возвращает идентификатор локального блока памяти или NULL, если Windows не может выделить память указанного объема. Параметра fuAlloc должен быть указан как логическая комбинация следующих значений:
Приведем фрагмент кода, в котором мы получаем из локальной области памяти перемещаемый блок размером 2000 байт, причем во все байты полученного блока записываются нулевые значения: hmemLocal = LocalAlloc(LHND, 2000); В следующем фрагменте мы заказываем удаляемый блок памяти размером 200 байт, который никак не инициализируется: hmemLocalDisc = LocalAlloc( LMEM_MOVEABLE | LMEM_DISCARDABLE, 200); Фиксирование и расфиксирование блока памятиДля получения доступа к полученному блоку памяти его необходимо зафиксировать, вызвав функцию LocalLock : void NEAR* WINAPI LocalLock(HLOCAL hloc); Функция LocalLock фиксирует блок памяти, идентификатор которого передается ей через параметр hloc и возвращает логический адрес зафиксированного блока или NULL, если указанный блок удален или произошла ошибка. Так как операционная система Windows версии 3.1 работает только в защищенном режиме, все блоки памяти, зафиксированные при помощи функции LocalLock, будут перемещаемыми. Перемещение блоков памяти выполняется методом изменения базового адреса в локальной таблице дескрипторов, причем логический адрес перемещаемого блока остается неизменным. Поэтому перемещение фиксированных блоков памяти происходит для приложений незаметно. Для каждого блока памяти Windows поддерживает счетчик фиксирования. Этот счетчик увеличивается при вызове функции LocalLock и уменьшается при расфиксировании блока функцией LocalUnlock : BOOL WINAPI LocalUnlock(HLOCAL hloc); Если содержимое счетчика уменьшилось до нуля, функция возвращает значение FALSE. В противном случае возвращается TRUE. Определение идентификатора блока памяти по его адресуС помощью функции LocalHandle вы можете определить идентификатор локального блока памяти: HLOCAL WINAPI LocalHandle(void NEAR* pvMem); Параметр pvMem указывает адрес локального блока памяти. Возвращаемое значение содержит идентификатор локального блока памяти. В случае ошибки возвращается нулевое значение. Работа с удаляемыми блоками памятиДля получения доступа к удаленному локальному блоку памяти его необходимо восстановить, вызвав функцию LocalReAlloc. Эта функция аналогична функции GlobalReAlloc и позволяет изменить характеристики существующего блока памяти. Приведем прототип функции LocalReAlloc : HLOCAL WINAPI LocalReAlloc(HLOCAL hloc, UINT cbNewSize, UINT fuAlloc); Параметр hloc указывает идентификатор восстанавливаемого блока памяти. При помощи параметра cbNewSize вы должны указать размер блока памяти, причем можно восстановить удаленный блок памяти и изменить его размер одновременно. Параметр fuAlloc определяет тип восстановленного блок памяти. Можно указывать логическую комбинацию следующих флагов:
Восстановив блок памяти, вы должны зафиксировать его функцией LocalLock и затем восстановить его прежнее содержимое. Определение характеристик локального блока памятиДля определения характеристик локального блока памяти предназначена функция LocalFlags , аналогичная рассмотренной нами ранее функции GlobalFlags: UINT WINAPI LocalFlags(HLOCAL hloc); Функция возвращает состояние блока памяти, указанного своим единственным параметром. Младший байт возвращаемого значения содержит содержимое счетчика фиксаций блока памяти. В старшем байте могут быть установлены флаги LMEM_DISCARDABLE и LMEM_DISCARDED. Если установлен флаг LMEM_DISCARDABLE, проверяемый блок памяти может быть удален Windows в процессе дефрагментации свободной области памяти. Если же установлен флаг LMEM_DISCARDED, удаление блока памяти уже произошло. Определение размера блока памятиС помощью функции LocalSize вы можете определить размер блока памяти по его идентификатору: UINT WINAPI LocalSize(HLOCAL hloc); Эта функция возвращает размер блока памяти, идентификатор которого задан параметром hloc. Если указанный блок памяти не существует или удален, возвращается нулевое значение. Дефрагментация локального блока памятиФункция LocalCompact выполняет дефрагментацию свободного пространства в локальной области данных: UINT WINAPI LocalCompact(UINT uMinFree); Функция возвращает размер самого большого доступного непрерывного блока в локальной области памяти, причем, если параметр на равен 0 или -1, выполняется дефрагментация памяти и удаление блоков, отмеченных как удаляемые. Если параметр функции указан как 0, функция не выполняет дефрагментацию памяти, но возвращает правильное значение с учетом возможного выполнения дефрагментации. Уменьшение размера локального блока памятиДля уменьшения размера существующего локального блока памяти можно использовать функцию LocalShrink : UINT WINAPI LocalShrink(HLOCAL hloc, UINT cbNewSize); Параметр hloc указывает идентификатор изменяемого локального блока памяти. Новые размеры блока памяти задаются параметром cbNewSize. Возвращаемое значение в случае успеха равно новому размеру блока памяти. В процессе получения памяти при помощи функции LocalAlloc размер использованной локальной памяти будет расти, пока не достигнет 64 Кбайт. После освобождения локальных блоков данных размер локальной области не уменьшится сам по себе. Для восстановления этого размера вы должны вызвать функцию LocalShrink. Освобождение локального блока памятиДля освобождения локального блока памяти, полученного от функции LocalAlloc, вы должны использовать функцию LocalFree : HLOCAL WINAPI LocalFree(HLOCAL hloc); Идентификатор освобождаемого блока передается функции в качестве ее единственного параметра. Функция возвращает NULL при успешном завершении или значение hloc при ошибке. Перед освобождением зафиксированных блоков памяти их следует предварительно расфиксировать. Инициализация локальной области данных в заданном сегментеДля создания и инициализации локальной области данных в заданном сегменте вы можете воспользоваться функцией LocalInit : BOOL WINAPI LocalInit(UINT uSegment, UINT uStartAddr, UINT uEndAddr); Параметр uSegment перед вызовом функции должен содержать идентификатор сегмента, который будет содержать локальную область данных. Параметр uStartAddr определяет начальный адрес локальной области данных, а параметр uEndAddr - конечный адрес локальной области данных. Первые 16 байт в сегменте данных необходимо зарезервировать для системы. Приложение LMEMПриведем исходный текст приложения LMEM, которое работает аналогично приложению GMEM, но с использованием локальной области памяти (листинг 2.5). Листинг 2.5. Файл lmem/lmem.cpp #define STRICT #include <windows.h> #include <windowsx.h> #include <dos.h> #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { BYTE szBuf[100]; HLOCAL hmemLocal; HLOCAL hmemLoDiscard; void* pLocal; void* pLoDiscard; UINT uMaxFreeMem; // Определяем размер доступной памяти uMaxFreeMem = LocalCompact(0); wsprintf(szBuf, "Доступно памяти:\t%u\n", uMaxFreeMem); MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK); // -------------------------------------------------------- // Работаем с перемещаемым блоком памяти // -------------------------------------------------------- // Дефрагментируем память для получения блока // размером 1000 байт uMaxFreeMem = LocalCompact(1000); // Заказываем буфер размером 1000 байт hmemLocal = LocalAlloc(GHND, 1000); if(hmemLocal != NULL) { // Если буфер получен, фиксируем его в памяти pLocal = LocalLock(hmemLocal); if(pLocal != NULL) { // Если блок успешно зафиксирован, // выводим значения идентификатора блока // и логический адрес блока wsprintf(szBuf, "hmemLocal=\t%04.4X\n" "pLocal=\t\t%04.4X", hmemLocal, pLocal); MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK); // ----------------------------------------- // Можно работать с полученным блоком памяти // Записываем в первый байт блока символ S *(PSTR)pLocal = 'S'; // ----------------------------------------- // Разрешаем перемещение блока LocalUnlock(hmemLocal); } else { MessageBox(NULL, "Ошибка при фиксировании блока", "Local Block", MB_OK); } // Отдаем блок памяти операционной системе LocalFree(hmemLocal); } else { MessageBox(NULL, "Мало памяти для перемещаемого блока", "Local Block", MB_OK); } // -------------------------------------------------------- // Работаем с удаляемым блоком памяти // -------------------------------------------------------- // Заказываем удаляемый блок памяти размером 2000 байт hmemLoDiscard = LocalAlloc(LMEM_MOVEABLE | LMEM_DISCARDABLE, 2000); if(hmemLoDiscard != NULL) { // Если мы его получили, удаляем блок LocalDiscard(hmemLoDiscard); // Пытаемся зафиксировать блок памяти pLoDiscard = LocalLock(hmemLoDiscard); if(pLoDiscard != NULL) { wsprintf(szBuf, "hmemLoDiscard=\t%04.4X\n" "pLoDiscard=\t%04.4X", hmemLoDiscard, pLoDiscard); MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK); // Разрешаем перемещение блока LocalUnlock(hmemLoDiscard); } else { // Если блок памяти не удалось зафиксировать, // проверяем, не был ли он удален if(LocalFlags(hmemLoDiscard) & LMEM_DISCARDED) { MessageBox(NULL, "Блок удален и мы его восстанавливаем", "Local Block", MB_OK); // Восстанавливаем удаленный блок памяти hmemLoDiscard = LocalReAlloc(hmemLoDiscard, 256, LMEM_MOVEABLE | LMEM_DISCARDABLE); // Фиксируем блок памяти pLoDiscard = LocalLock(hmemLoDiscard); if(pLoDiscard != NULL) { // Выводим идентификатор и логический адрес // зафиксированного блока памяти wsprintf(szBuf, "hmemLoDiscard=\t%04.4X\n" "pLoDiscard=\t%04.4X", hmemLoDiscard, pLoDiscard); MessageBox(NULL, (LPSTR)szBuf, "Local Block", MB_OK); // Освобождаем блок памяти LocalUnlock(hmemLoDiscard); } else { MessageBox(NULL, "Ошибка при фиксировании блока", "Local Block", MB_OK); } } } // Отдаем удаляемый блок памяти операционной системе LocalFree(hmemLoDiscard); } else { MessageBox(NULL, "Мало памяти для удаляемого блока", "Local Block", MB_OK); } return 0; } Файл определения модуля приложения LMEM приведен в листинге 2.6. Листинг 2.6. Файл lmem/lmem.def ; ============================= ; Файл определения модуля ; ============================= NAME LMEM DESCRIPTION 'Приложение LMEM, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 4096 CODE preload moveable discardable DATA preload moveable multiple В этом файле начальное значение локальной области данных установлено равным 4096 байт. Проверяя работу приложения LMEM, вы можете попробовать уменьшить размер локальной области данных, например, до величины 1 Кбайт, а затем заказать локальный блок памяти размером 10 Кбайт. В этом случае несмотря на то, что сразу после запуска приложения в локальной области данных будет свободно всего несколько сотен байт, запрос на 10 Кбайт будет удовлетворен за счет автоматического увеличения размера локальной области данных. Статическая памятьСтатические данные, описанные в приложении Windows с использованием ключевого слова static или объявленные как внешние переменные располагаются в автоматическом сегменте данных приложения (рис. 2.10). В документации к SDK не рекомендуется в моделях памяти small и medium использовать дальние указатели на статические данные (См. раздел 16.5 руководства, который называется Traps to Avoid When Managing Program Data). Дело в том, что автоматический сегмент данных приложения является перемещаемым. Операционная система Windows фиксирует сегмент данных при активизации приложения и расфиксирует его во время переключения на другие приложения. Поэтому логический адрес сегмента данных может изменяться. Следующий способ является недопустимым: static LPSTR lpstrDlgName = "MyDlg"; ........ hDlg = CreateDialog(hInst, lpstrDlgName, hWndParent, (DLGPROC) lpDialogProc); В фрагменте кода, приведенном выше, содержимое указателя lpstrDlgName устанавливается загрузчиком. В руководстве отмечается, что в процессе перемещения сегмента значение указателя может измениться (так как при перемещении сегмента может измениться значение селектора), что приведет к ошибке. Рекомендуется в указанной выше ситуации использовать ближний статический указатель с явным преобразованием типа к LPSTR: static PSTR pstrDlgName = "MyDlg"; ........ hDlg = CreateDialog(hInst, (LPSTR)pstrDlgName, hWndParent, (DLGPROC) lpDialogProc); В процессе явного преобразования типа используется текущее содержимое регистра DS, а не то, которое использовалось при загрузке приложения в память. Автоматическая памятьАвтоматические данные располагаются, как и статические, в автоматическом сегменте данных (рис. 2.10), назначаемым приложению при его загрузке в память. К автоматическим данным относится стек, параметры функций и локальные переменные. Как мы уже говорили, размер стека определяется оператором STACKSIZE в файле определения модуля и не увеличивается автоматически во время работы приложения. Дополнительная память в структуре класса окнаПри регистрации класса окна вы можете в поле cbClsExtra структуры WNDCLASS указать размер дополнительной области памяти, которая будет зарезервирована в структуре данных, описывающей класс окна. В эту область можно записать данные, предназначенные для использования всеми окнами, создаваемыми на базе класса. Для работы с этой дополнительной памятью предназначены функции SetClassWord, SetClassLong, GetClassWord, GetClassLong. Функция SetClassWord устанавливает в структуре, описывающей класс для окна hwnd, новое значение wNewWord, при этом смещение устанавливаемого слова определяется параметром offset: WORD WINAPI SetClassWord(HWND hwnd, int offset, WORD wNewWord); Для параметра offset вы должны использовать значения от нуля до указанного в поле cbClsExtra минус 2 или следующие значения:
С помощью перечисленных выше четырех значений вы можете изменить характеристики класса окна. Эти изменения будут учитываться только для вновь создаваемых окон. В случае ошибки функция SetClassWord возвращает нулевое значение. Функция GetClassWord позволяет вам прочитать содержимое слова дополнительной области памяти со смещением offset: WORD WINAPI GetClassWord(HWND hwnd, int offset); Для этой функции вы можете использовать те же значения, что и для предыдущей, плюс еще два:
Функция GetClassWord возвращает значение указанного слова из структуры класса окна или нулевое значение при ошибке. Функция SetClassLong аналогична функции SetClassWord, но работает с двойными словами: LONG WINAPI SetClassLong(HWND hwnd, int offset, LONG nVal); Для параметра offset дополнительно можно указать значение GCL_WNDPROC, при этом функция заменит адрес функции окна. Мы пользовались этим приемом в приложении SMARTPAD, перехватывая управление у стандартной функции окна органа управления класса "edit". В случае ошибки функция SetClassLong возвращает нулевое значение. С помощью функции GetClassLong вы можете получить из структуры класса окна значение двойного слова, расположенного со смещением offset: LONG WINAPI GetClassLong(HWND hwnd, int offset); Для этой функции можно указать положительное смещение или одну из двух констант - GCL_WNDPROC и GCL_MENUNAME. В первом случае функция GetClassLong возвратит адрес функции окна для данного класса, во втором - указатель на строку имени меню, указанного при регистрации класса. Дополнительная память в структуре окнаПри регистрации класса окна функцией RegisterClass вы можете в поле cbWndExtra структуры WNDCLASS указать размер дополнительной области памяти, которая будет зарезервирована в структуре, описывающей каждое окно, создаваемое на базе данного класса. Для работы с этой дополнительной памятью предназначены функции SetWindowWord, SetWindowLong, GetWindowWord, GetWindowLong. Функция SetWindowWord устанавливает в структуре, описывающей окно hwnd, новое значение wNewWord, при этом смещение устанавливаемого слова определяется параметром offset: WORD WINAPI SetWindowWord(HWND hwnd, int offset, WORD wNewWord); Для параметра offset вы должны использовать значения от нуля до указанного в поле cbClsExtra минус 2 или следующие значения:
В случае ошибки функция SetWindowWord возвращает нулевое значение. Функция GetWindowWord позволяет вам прочитать содержимое слова дополнительной области памяти в структуре окна со смещением offset: WORD WINAPI GetWIndowWord(HWND hwnd, int offset); Для этой функции вы можете использовать следующие значения:
Функция GetWindowWord возвращает значение указанного слова из структуры класса окна или нулевое значение при ошибке. Функция SetWindowLong аналогична функции SetWindowWord, но работает с двойными словами: LONG WINAPI SetWindowLong(HWND hwnd, int offset, LONG nVal); Для параметра offset дополнительно можно указать следующие значения:
Если параметр hwnd содержит идентификатор диалоговой панели, вы можете использовать еще несколько значений:
В случае ошибки функция SetWindowLong возвращает нулевое значение. С помощью функции GetWindowLong вы можете получить из структуры окна значение двойного слова, расположенного со смещением offset: LONG WINAPI GetWindowLong(HWND hwnd, int offset); Для этой функции можно указать положительное смещение или одну из констант, описанных выше для функции SetWindowLong . Ресурсы приложенияУправление ресурсами было рассмотрено нами в предыдущем томе "Библиотеки системного программиста". Как вы знаете, ресурсы представляют собой данные, расположенные в файле загрузочного модуля приложения и доступные только для чтения. В файле описания ресурсов вы можете указать, что для хранения ресурсов следует использовать фиксированную, перемещаемую или удаляемую память. Вы можете описать ресурсы, как загружаемые в память при запуске приложения (PRELOAD) или по требованию (LOADONCALL). Загрузка ресурсов в оперативную память выполняется такими функциями, как LoadIcon или CreateDialog . Для загрузки ресурсов, имеющих нестандартный формат, вы должны использовать функции FindResource (поиск ресурса и получение идентификатора ресурса) и LoadResource (загрузка ресурса и получение идентификатора блока памяти, в который загружен найденный ресурс). Все эти функции были описаны в предыдущем томе, однако для удобства мы приведем их краткое описание еще раз. Приведем прототип функции FindResource : HRSRC WINAPI FindResource(HINSTANCE hInst, LPCSTR lpszName, LPCSTR lpszType); Параметр hInst является идентификатором модуля, содержащего ресурс. Для извлечения ресурса из приложения вы должны указать его идентификатор, передаваемый функции WinMain через параметр hInstance. Параметр lpszName должен содержать адрес имени ресурса. Для загрузки произвольных данных в качестве этого параметра следует передать указатель на строку, содержащую идентификатор ресурса. Функции FindResource в качестве третьего параметра можно передавать идентификаторы предопределенных типов ресурсов, список которых приведен ниже.
Вы можете использовать функцию FindResource для загрузки таких ресурсов, как пиктограммы или курсоры, указав ей тип ресурса, соответственно, RT_ICON или RT_CURSOR. Однако в документации к SDK сказано, что загрузку предопределенных ресурсов, таких как пиктограммы и курсоры, следует выполнять специально предназначенными для этого функциями (LoadIcon, LoadCursor и т. д.). После того как ресурс найден, его следует загрузить, вызвав функцию LoadResource : HGLOBAL WINAPI LoadResource(HINSTANCE hinst, HRSRC hrsrc); Параметр hinst представляет собой идентификатор модуля, из файла которого загружается ресурс. Если ресурс загружается из файла вашего приложения, используйте значение hInstance, полученное через соответствующий параметр функции WinMain. В качестве второго параметра этой функции следует передать значение, полученное от функции FindResource. Для получения логического адреса загруженного ресурса его необходимо зафиксировать в памяти, вызвав функцию LockResource : void FAR* WINAPI LockResource(HGLOBAL hGlb); В качестве параметра hGlb функции LockResource следует передать идентификатор ресурса, полученный от функции LoadResource. Функция LockResource фиксирует данные в памяти и возвращает дальний указатель на соответствующий буфер. После фиксирования Windows не станет удалять сегмент с ресурсами из памяти, так что приложение сможет использовать данные в любой момент времени. После того как приложение использовало ресурс и он стал ненужен, следует расфиксировать память ресурса, вызвав функцию UnlockResource . Функция определена через функцию GlobalUnlock следующим образом: BOOL WINAPI GlobalUnlock(HGLOBAL hGlb); #define UnlockResource(h) GlobalUnlock(h) Перед завершением работы приложения следует освободить полученный ресурс, вызвав функцию FreeResource : BOOL WINAPI FreeResource(HGLOBAL hGlb); В качестве параметра hGlb следует передать идентификатор ресурса, полученный от функции LoadResource. |