Получение локального блока памяти
Для получения локального блока памяти вы должны использовать функцию LocalAlloc :
HLOCAL WINAPI LocalAlloc(UINT fuAlloc, UINT cbAlloc);
Параметр fuAlloc определяет тип выделяемой памяти. Размер блока памяти в байтах должен передаваться через параметр cbAlloc.
Функция возвращает идентификатор локального блока памяти или NULL, если Windows не может выделить память указанного объема.
Параметра fuAlloc должен быть указан как логическая комбинация следующих значений:
Флаг | Описание |
LMEM_DISCARDABLE | Заказывается удаляемый блок памяти. Этот флаг должен использоваться совместно с флагом LMEM_MOVEABLE |
LMEM_FIXED | Заказывается фиксированный блок памяти (в защищенном режиме работы блок памяти будет перемещаемым, даже если он заказан с использованием флага LMEM_FIXED, однако в процессе перемещения будет изменяться только линейный адрес, но не логический). Этот флаг несовместим с флагом LMEM_MOVEABLE |
LMEM_MOVEABLE | Заказывается перемещаемый блок памяти. Логический адрес такого блока может изменяться в процессе перемещения. Этот флаг несовместим с флагом LMEM_FIXED |
LMEM_NOCOMPACT | Для удовлетворения запроса памяти не следует выполнять объединение всех свободных участков памяти в один и удалять блоки памяти, отмеченные как удаляемые |
LMEM_NODISCARD | Для удовлетворения запроса памяти не следует выполнять объединение всех свободных участков памяти в один |
LMEM_ZEROINIT | Во все байты блока необходимо записать нулевые значения |
NONZEROLHND | Синоним для LMEM_MOVEABLE |
NONZEROLPTR | Синоним для LMEM_FIXED |
LHDN | Синоним для комбинации флагов LMEM_MOVEABLE и LMEM_ZEROINIT |
LPTR | Синоним для комбинации флагов LMEM_FIXED и LMEM_ZEROINIT |
Приведем фрагмент кода, в котором мы получаем из локальной области памяти перемещаемый блок размером 2000 байт, причем во все байты полученного блока записываются нулевые значения:
hmemLocal = LocalAlloc(LHND, 2000);
В следующем фрагменте мы заказываем удаляемый блок памяти размером 200 байт, который никак не инициализируется:
hmemLocalDisc = LocalAlloc( LMEM_MOVEABLE | LMEM_DISCARDABLE, 200);
Получение памяти в первом мегабайте адресного пространства
Если у вас возникает необходимость заказать память, доступную приложениям MS-DOS, и располагающуюся в первом мегабайте адресного пространства, воспользуйтесь функцией GlobalDosAlloc :
DWORD WINAPI GlobalDosAlloc(DWORD cbAlloc);
Параметр cbAlloc определяет размер блока в байтах.
Старшее слово возвращаемого значения содержит сегментную компоненту адреса реального режима, соответствующего полученному блоку памяти. Это значение может использоваться для доступа к блоку памяти в реальном режиме работы процессора.
Младшее слово возвращаемого значения содержит селектор для доступа к полученному блоку в защищенном режиме.
Память, заказанная при помощи функции GlobalDOSAlloc, является фиксированной, поэтому вам не следует вызывать функцию GlobalLock для фиксирования и получения логического адреса.
Для освобождения блока памяти, полученного при помощи функции GlobalDOSAlloc, следует вызвать функцию GlobalDosFree :
UINT WINAPI GlobalDosFree(UINT uSelector);
В качестве параметра uSelector этой функции следует передать селекторную компоненту блока памяти, расположенного в первом мегабайте адресного пространства.
В случае успешного завершения возвращаемое значение равно нулю. При ошибке возвращается селектор, указанный в параметре uSelector.
Учтите, что заказывать памяти в первом мегабайте можно только в случае крайней необходимости, когда, вам, например, нужно обеспечить одновременный доступ к памяти со стороны приложений Windows и программ MS-DOS. Уменьшение свободного пространства в первом мегабайте адресного пространства отрицательно сказывается на производительности работы приложений Windows.
Позиционирование
Для выполнения операции позиционирования внутри файла приложения Windows могут использовать функцию _llseek :
LONG WINAPI _llseek( HFILE hf, // идентификатор файла LONG lOffset, // смещение int nOrigin); // способ использования смещения
Функция _llseek перемещает указатель текущей позиции в файле на lOffset байт, причем направление смещения зависит от значения параметра nOrigin следующим образом:
Значение | Описание |
SEEK_SET | Указатель текущей позиции в файле перемещается на lOffset байт от начала файла |
SEEK_CUR | Указатель текущей позиции в файле перемещается на lOffset байт от текущей позиции |
SEEK_END | Указатель текущей позиции в файле перемещается на lOffset байт от конца файла |
Таким образом, с помощью этой функции вы можете обеспечить прямой доступ к файлу.
Функция возвращает новое значение текущей позиции от начала файла или HFILE_ERROR при ошибке.
Коды ошибок в Windows
Код ошибки | Описание |
0x0001 | Неправильная операция (Invalid function) |
0x0002 | Файл не найден (File not found) |
0x0003 | Путь не найден (Path not found) |
0x0004 | Слишком много открытых файлов (Too many open files) |
0x0005 | Доступ запрещен (Access denied) |
0x0006 | Неправильный идентификатор (Invalid handle) |
0x0007 | Область испорчена (Arena trashed) |
0x0008 | Мало памяти (Not enough memory) |
0x0009 | Неправильный блок (Invalid block) |
0x000A | Плохая среда (Bad environment) |
0x000B | Плохой формат (Bad format) |
0x000C | Неправильный доступ (Invalid access) |
0x000D | Неправильные данные (Invalid data) |
0x000F | Неправильное устройство (Invalid drive) |
0x0010 | Текущий каталог (Current directory) |
0x0011 | В операции перемещения используется другое устройство (Not same device) |
0x0012 | Больше нет файлов (No more files) |
0x0013 | Ошибка защиты записи (Write protect error) |
0x0014 | Плохое устройство (Bad unit) |
0x0015 | Не готов (Not ready) |
0x0016 | Неправильная команда (Bad command) |
0x0017 | С помощью циклического контроля четности обнаружена ошибка (CRC error) |
0x0018 | Неправильная длина (Bad length) |
0x0019 | Ошибка позиционирования (Seek error) |
0x001A | Диск подготовлен не в среде MS-DOS (Not MS-DOS disk) |
0x001B | Сектор не найден (Sector not found) |
0x001C | Конец бумаги (Out of paper) |
0x001D | Ошибка при записи (Write fault) |
0x001E | Ошибка при чтении (Read fault) |
0x001F | Общий сбой (General failure) |
0x0020 | Ошибка при попытке совместного использования файла (Sharing violation) |
0x0021 | Ошибка при блокировании файла (Lock violation) |
0x0022 | Неправильный диск (Wrong disk) |
0x0023 | Недоступен блок управления файлом (File control block unavailable) |
0x0024 | Превышен допустимый размер разделяемого буфера (Sharing buffer exceeded) |
0x0032 | Не поддерживается (Not supported) |
0x0033 | Удаленного абонента нет в списке (Remote not listed) |
0x0034 | Повторное использование имени (Duplicate name) |
0x0035 | Плохой сетевой путь (Bad netpath) |
0x0036 | Сеть занята (Network busy) |
0x0037 | Устройство не существует (Device does not exist) |
0x0038 | Слишком много команд (Too many commands) |
0x0039 | аппаратный сбой адаптера (Adaptor hardware error) |
0x003A | Плохой ответ от сети (Bad network response) |
0x003B | Неожиданная ошибка в сети (Unexpected network error) |
0x003C | Плохой удаленный адаптер (Bad remote adaptor) |
0x003D | Переполнение очереди печати (Print queue full) |
0x003E | Мало места для спулинга (No spool space) |
0x003F | Печать отменена (Print canceled) |
0x0040 | Сетевое имя удалено (Netname deleted) |
0x0041 | Сетевой доступ запрещен (Network access denied) |
0x0042 | Неправильный тип устройства (Bad device type) |
0x0043 | Неправильное сетевое имя (Bad network name) |
0x0044 | слишком много имен (Too many names) |
0x0045 | Слишком много сеансов (Too many sessions) |
0x0046 | совместное использование приостановлено (Sharing paused) |
0x0047 | Запрос не принят (Request not accepted) |
0x0048 | Переназначение приостановлено (Redirection paused) |
0x0050 | Файл уже существует (File exists) |
0x0051 | Двойной блок управления файлом (Duplicate file control block) |
0x0052 | Не могу сделать (Cannot make) |
0x0053 | Сбой обработчика прерывания INT 24h (Interrupt 24 failure) |
0x0054 | Вне структуры (Out of structures) |
0x0055 | Уже присвоено (Already assigned) |
0x0056 | Неправильный пароль (Invalid password) |
0x0057 | Неправильный параметр (Invalid parameter) |
0x0058 | Ошибка в сети при записи (Net write fault) |
Приложение DISCARD
В предыдущей главе мы приводили исходные тексты приложений GMEM и LMEM, которые, кроме всего прочего, демонстрировали приемы работы с удаляемой памятью. В этих приложениях мы заказывали удаляемые блоки памяти из, соответственно, глобальной и локальной области памяти, удаляли их а затем пытались зафиксировать, чтобы получить их линейный адрес. Так как блоки памяти были удалены, нам приходилось их восстанавливать.
Мы отметили, что приложение может создать собственную функцию извещения, которая будет получать управление при попытке Windows удалить глобальные блоки памяти, заказанные как удаляемые (если для них определен флаг GMEM_NOTIFY). Однако, как мы уже говорили, функция извещения должна располагаться в DLL-библиотеке, причем в фиксированном сегменте кода, так как она может быть вызвана из любого контекста, а не только из контекста вашего приложения.
Теперь, когда мы умеем создавать свои DLL-библиотеки, мы можем испытать работу механизма оповещения о попытке удаления глобального блока памяти .
Приложение DISCARD (листинг 3.5), сделанное на базе приложения GMEM, заказывает удаляемый блок памяти и назначает собственную процедуру извещения об удалении глобальных блоков памяти. Процедура размещена в DLL-библиотеке dll.dll, исходные тексты которой мы также приведем.
Приложение DLLCALL
Исходный текст простейшей DLL-библиотеки приведен в листинге 3.1. Как видно из листинга, в библиотеке определены всего три функции - LibMain, WEP и Msg.
Приложение DMENU
В приложении DMENU, имитирующем работу с документами (например, с текстами), мы использовали большинство описанных выше функций, предназначенных для динамического создания и изменения меню. Проект этого приложения не включает файл описания ресурсов и, соответственно, не использует шаблон меню.
Сразу после запуска приложения в полосе меню определены два временных меню - "File" и "Help" (рис. 1.15). В меню "File" вы можете использовать строки "New" и "Open", предназначенные, соответственно, для создания нового документа или загрузки документа из файла. Кроме этих двух строк вы можете выбрать строку "Exit", завершающую работу приложения. Строка "Demo Version" заблокирована и отмечена галочкой. Так как мы еще не научились работать с принтером, строки "Print", "Page Setup" и "Printer Setup" заблокированы и отображаются серым цветом.
Рис. 1.15. Исходный вид меню приложения DMENU
Пока вы не создали новый документ или не загрузили документ, созданный ранее, строки "Close", "Save", "Save as..." заблокированы. Так как документ не загружен, его нельзя закрыть или сохранить, поэтому соответствующие строки в меню отображаются серым цветом.
После того, как вы выберете строку "New" или "Open", внешний вид меню приложения изменится (рис. 1.16).
Рис. 1.16. Изменения в меню приложения DMENU
Так как приложение DMENU рассчитано на "работу" с одним документом, после загрузки документа строки "New" и "Open" блокируются. Для их разблокирования вы должны закрыть документ, выбрав строку "Close". В этом случае меню приложения примет исходный вид, представленный на рис. 1.15.
После загрузки документа в меню "File" разблокируются строки "Close", "Save" и "Save as...". Кроме этого, появляется новое временное меню "Edit", аналогичное меню "Edit" предыдущего приложения. Меню "Edit" присутствует в окне только тогда, когда загружен документ. Если документ не загружен, то редактировать нечего. В этом случае меню "Edit" не нужно.
Многие приложения Windows изменяют меню похожим образом. Внешний вид меню может зависеть от типа обрабатываемого документа, от используемых параметров и режимов (краткое меню, полное меню, расширенное меню и т. д.) или от текущего состояния документа.
Исходный текст главного файла приложения DMENU, реализующего описанный выше алгоритм изменения меню, представлен в листинге 1.5.
Приложение GMEM
Для демонстрации использования основных функций управления глобальной памятью мы приведем исходный текст приложения GMEM (листинг 2.3).
Приложение GMENU
В заключение первой главы, посвященной меню, приведем приложение GMENU, которое демонстрирует использование графики в меню. Меню верхнего уровня приложения GMENU содержит временное меню "LineStyle", которое используется для выбора одного из стилей линии (рис. 1.21).
Рис. 1.21. Графические изображения bitmap в строках меню
В данном случае использование графики из-за большей наглядности предпочтительнее текстового описания внешнего вида линий, такого как "тонкая линия", "толстая линия", пунктирная линия" и "волнистая линия".
Мы также создали собственный символ для отметки строки меню в виде закрашенного кружка (рис. 1.22).
Рис. 1.22. Отметка строки меню при помощи графического изображения bitmap
Главный файл приложения GMENU приведен в листинге 1.16.
Приложение LMEM
Приведем исходный текст приложения LMEM, которое работает аналогично приложению GMEM, но с использованием локальной области памяти (листинг 2.5).
Приложение MENU
Теперь, когда мы рассказали вам о том, как создать шаблон меню и само меню, приведем исходные тексты простого приложения, работающего с меню. Оно создает меню, описанное нами ранее в разделе "Создание шаблона меню". Вид главного окна приложения был представлен на рис. 1.9.
В листинге 1.1 приведен исходный текст основного файла приложения.
Приложение SMARTPAD
Для иллюстрации всего сказанного выше относительно использования меню и органа управления Toolbar мы приведем исходные тексты приложения SMARTPAD, которое реализует основные функции, связанные с редактированием текста (рис. 1.19).
Рис. 1.19. Главное окно приложения SMARTPAD
Так как мы пока еще не умеем работать с принтерами и шрифтами, первая версия нашего редактора текста несколько ограничена. Кроме того, пока не реализованы функции меню "Help". Тем не менее это приложение имеет Toolbar, использует акселераторы для доступа к основным функциям, модифицирует системное меню и по щелчку правой клавиши мыши создает в окне редактирования плавающее меню.
На примере этого приложения мы демонстрируем не только способы работы с различными типами меню, но и способ "перехвата" управления у функции окна органа управления класса "edit".
Орган управления Toolbar выделен в отдельный класс (в терминах языка программирования С++), исходные тексты которого находятся в двух файлах. Вы сможете с помощью этого класса без труда (почти) создавать в приложениях свой собственный Toolbar.
Основной файл приложения SMARTPAD приведен в листинге 1.8.
Приложение WINHOOK
Когда в нашей стране впервые появилась операционная система Microsoft Windows, она не могла работать с символами кириллицы. Поэтому наши программисты создали несколько приложений, исправляющих этот недостаток.
Для того чтобы Windows стала "понимать" русские символы, необходимо решить несколько задач.
Во-первых, клавиатура персонального компьютера IBM PC не предназначена для ввода символов кириллицы. В операционной системе MS-DOS эта проблема решалась с помощью резидентных программ, перехватывающих аппаратное прерывание от клавиатуры и заменяющих коды символов. Для операционной системы Microsoft Windows этот способ непригоден, так как для работы с клавиатурой используется специальный драйвер.
Во-вторых, необходимо обеспечить отображение символов кириллицы на экране монитора. В MS-DOS для отображения символов кириллицы в текстовом режиме можно было выполнить загрузку таблиц знакогенератора, расположенных в памяти видеоконтроллера. Операционная система Windows не использует текстовый режим, и, соответственно, не использует таблицы знакогенератора. Зато для нее предусмотрен механизм шрифтов различного типа - матричных, векторных и масштабируемых. Очевидно, что единственный способ отображения символов кириллицы в среде Windows - это подготовка версий шрифтов с кириллицей.
В-третьих, необходимо обеспечить правильную работу таких функций, как OemToAnsi и AnsiToOem. Эти функции должны выполнять преобразования, учитывая кодировку символов, которая сама по себе зависит от национального языка.
Есть и другие проблемы использования кириллицы как во всей системе в целом, так и в отдельных приложениях, например, обеспечение правильного формата даты и времени, сортировка, замена строчных букв на прописные, проверка грамматики и правописания, вставка переносов и т. д. Короче говоря, проблема работы с кириллицей и русским языком в Windows сложнее, чем может показаться на первый взгляд.
В этом разделе мы приведем исходные тексты приложения WINHOOK, которое решает первую из перечисленных выше задач, т.
е. обеспечивает ввод с клавиатуры символов кириллицы. Приложение WINHOOK позволяет вам изменять раскладку клавиатуры с используемой по умолчанию на определенную в ресурсах приложения. Переключение раскладки выполняется при помощи левой или правой клавиши <Control>. Сразу после запуска приложения включается стандартная раскладка клавиатуры. Если вы нажмете на клавишу <Control> три раза, то услышите звуковой сигнал, после чего раскладка клавиатуры меняется на альтернативную и вы можете вводить символы кириллицы.
Приложение WINHOOK создает "непотопляемое" окно, которое располагается над поверхностью любого другого окна в Windows. Вы можете перемещать это окно мышью или изменять его размеры. В зависимости от используемой раскладки клавиатуры в окне отображается одно из двух слов: DEFAULT для стандартной раскладки и CYRILLIC для дополнительной (рис. 3.8).
Рис. 3.8. Главное окно приложения WINHOOK при использовании различных раскладок клавиатуры
Если для работы с кириллицей вы используете такие приложения, как CyrWin или ParaWin, перед запуском приложения WINHOOK их следует либо завершить, либо переключить раскладку клавиатуры на стандартную.
Для того чтобы завершить работу приложения WINHOOK, надо сделать его окно текущим, щелкнув по нему левой клавишей мыши, а затем нажать комбинацию клавиш <Alt + F4>.
Основной файл приложения WINHOOK приведен в листинге 3.9.
Проверка идентификатора меню
Для того чтобы убедиться, что идентификатор не является идентификатором меню, вы можете использовать функцию IsMenu :
BOOL WINAPI IsMenu(HMENU hmenu);
Эта функция появилась в программном интерфейсе Windows версии 3.1.
Функция возвращает значение FALSE, если переданный ей через параметр hmenu идентификатор не является идентификатором меню. Можно было бы ожидать, что если функция IsMenu вернула значение TRUE, то проверяемый идентификатор является идентификатором меню, однако в описании функции сказано, что это не гарантируется.
Проверка присутствия share.exe
Как мы уже говорили, в многозадачной среде утилита MS-DOS share.exe приобретает особое значение, выступая координатором доступа работающих параллельно приложений к файлам. Для того чтобы приучить забывчивых или беспечных пользователей не удалять команду загрузки этой утилиты из файла autoexec.bat вы можете сделать так, чтобы ваше приложение выдавала предупреждающее сообщение, если утилита share.exe не загружена.
Однако проблема не так проста, как кажется. Для того чтобы определить, загружена ли утилита share.exe , программы MS-DOS могли воспользоваться функцией 1000h прерывания INT2Fh. Эта функция используется самой утилитой share.exe для предотвращения повторной загрузки.
Но вызвав эту функцию из приложения Windows, вы можете, к своему огорчению, убедиться, что она всегда сообщает о том, что share.exe загружена в память, даже если вы вообще стерли файл share.exe с диска. Это сделано специально, но не для того чтобы затруднить обнаружение share.exe, а для того чтобы предотвратить ее загрузку из виртуальной машины MS-DOS, работающей в среде Windows.
Мы, однако, можем найти выход из этого затруднительного положения, если для определения присутствия share.exe попробуем использовать одну из функций, для выполнения которой она предназначена.
В качестве такой функции проще всего использовать блокирование участка файла (функция 0x5c прерывания INT 21h). Как мы уже говорили, Windows выполняет эмуляцию большого числа функций прерывания MS-DOS, позволяя приложениям Windows обращаться к этим функциям.
В листинге 4.1 приведены исходные тексты приложения ISSHARE, которое проверяет присутствие share.exe, выполняя попытку заблокировать первый байт созданного временного файла.
Работа с большими массивами данных
С помощью функции GlobalAlloc приложения могут заказать блок памяти, превосходящий по своим размерам 64 кбайт. Операционная система разделяет такие блоки памяти на участки размером не более 64 Кбайт, причем для адресации всех этих участков используется несколько дескрипторов, расположенных друг за другом (рис. 2.11).
Рис. 2.11. Адресация большого блока памяти
В процессе выделения приложению большого блока памяти Windows может передвигать дескрипторы перемещаемых сегментов для освобождения нужного количества ячеек таблицы дескрипторов, расположенных рядом.
Для работы с блоками памяти размером более 64 Кбайт приложения, составленные на языках программирования С и С++ должны использовать указатели типа huge . Для таких указателей переключение селекторов, необходимое для получения доступа ко всему блоку памяти, выполняется автоматически.
Если вам потребуется создать приложение Windows или его отдельный модуль на языке ассемблера при помощи MASM, вы можете использовать значение __AHINCR для определения величины, которую нужно прибавить к селектору для того чтобы получить значение селектора, соответствующего следующему дескриптору.
Если вы в приложении Windows определяете данные с помощью ключевого слова huge, указатели на эти данные следует использовать с осторожностью. Несмотря на то, что все операции с указателями huge будут выполняться правильно, при передаче таких указателей функциям из программного интерфейса Windows могут возникнуть ошибки, если эти функции, изменяя полученный указатель, пересекут границу 64 Кбайт.
Работа с файлами
4.1.
4.2.
4.3.
Операционная система Windows версии 3.1, к сожалению, не имеет своей собственной файловой системы. Приложения Windows работают с файловой системой MS-DOS, поэтому они "скованы" ограничениями этой файловой системы. В добавок Windows накладывает свои ограничения, связанные с мультизадачным режимом работы.
Последнее обстоятельство имеет большое значение. Если программа MS-DOS работала с файлами в монопольном режиме, то в операционной системе Windows несколько приложений могут выполнять обращение к одному файлу. Если приложения не будут учитывать возможность доступа к файлам со стороны других приложений, результаты могут оказаться плачевными.
Для предотвращения одновременного доступа на запись к файлам со стороны нескольких приложений можно использовать утилиту MS-DOS share.exe , загрузив ее при помощи файла autoexec.bat. Однако пользователи часто забывают загрузить эту утилиту или не загружают ее специально, надеясь сэкономить таким образом оперативную память.
Если вы разрабатываете приложение, работающее с файлами, вам необходимо убедить пользователя загрузить утилиту share.exe. Здесь возможны разные приемы.
Инсталлятор текстового процессора Microsoft Word for Windows версии 2.0 в процессе установки изменяет файл autoexec.bat, добавляя в него строку загрузки утилиты share.exe.
Однако при оптимизации своей системы пользователь, обнаружив эту строку в файле autoexec.bat после установки текстового процессора, может ее удалить. При этом сам текстовый процессор будет работать нормально (некоторое время).
Поэтому в текстовом процессоре Microsoft Word for Windows версии 6.0 использован более сильный способ "убеждения" пользователей в необходимости утилиты share.exe - если эта утилита не загружена, текстовый процессор выводит на экран сообщение о том, что в системе нет share.exe и отказывается работать.
Работа с локальной таблицей дескрипторов
Для полноты описания мы расскажем вам о функциях, позволяющих модифицировать локальную таблицу дескрипторов, создавая в ней новые дескрипторы или изменяя существующие (виртуальные драйвера могут выполнять аналогичные действия с глобальной таблицей дескрипторов, пользуясь специальным набором функций). Эти функции не рекомендуется использовать без крайней необходимости.
Функция AllocSelector позволяет создать в локальной таблице дескрипторов новый дескриптор или скопировать существующий:
UINT WINAPI AllocSelector(UINT uSelector);
Если параметр uSelector перед вызовом функции содержит селектор, для которого в таблице дескрипторов есть дескриптор, функция AllocSelector копирует этот дескриптор и возвращает селектор, соответствующий копии. Если же этот параметр равен нулю, в локальной таблице дескрипторов создается новый неинициализированный дескриптор, при этом функция возвращает значение селектора, соответствующее созданному дескриптору. При ошибке возвращается нулевое значение.
Если созданный дескриптор больше не нужен, его следует удалить при помощи функции FreeSelector :
UINT WINAPI FreeSelector(UINT uSelector);
Параметр функции должен содержать селектор, соответствующий удаляемому дескриптору. Если функция выполнилась без ошибок, она возвращает нулевое значение. В случае ошибки возвращается значение параметра uSelector.
Перед использованием нового дескриптора его следует проинициализировать - установить базовый адрес и предел.
Для установки базового адреса следует воспользоваться функцией SetSelectorBase , которая впервые появилась в составе программного интерфейса Windows версии 3.1:
UINT WINAPI SetSelectorBase(UINT uSelector, DWORD dwBase);
Для дескриптора, соответствующего селектору uSelector, устанавливается значение линейного адреса, равное dwBase.
Если функция выполнилась успешно, она возвращает значение параметра uSelector. В случае ошибки возвращается нулевое значение.
С помощью функции GetSelectorBase вы можете определить линейный адрес для любого существующего селектора:
DWORD WINAPI GetSelectorBase(UINT uSelector);
При помощи функции SetSelectorLimit вы можете определить предел сегмента, адресуемого селектором uSelector:
UINT WINAPI SetSelectorLimit(UINT uSelector, DWORD dwLimit);
Предел сегмента задается параметром dwLimit. Для процессора 80286 предел не должен превосходить величины 0x10000. О пределах сегментов вы можете прочитать в шестом томе "Библиотеки системного программиста", который называется "Защищенный режим процессоров Intel 80286/80386/80486".
Функция GetSelectorLimit позволяет вам для заданного селектора uSelector определить предел сегмента:
DWORD WINAPI GetSelectorLimit(UINT uSelector);
С помощью функции LockSegment вы можете зафиксировать сегмент, соответствующий селектору uSelector:
HGLOBAL WINAPI LockSegment(UINT uSelector);
Если в качестве параметра этой функции передать значение -1, функция зафиксирует текущий сегмент данных.
Возвращаемое значение определяет зафиксированный сегмент данных или NULL, если сегмент был удален или произошла ошибка.
После использования зафиксированный сегмент необходимо расфиксировать при помощи функции UnlockSegment :
void WINAPI UnlockSegment(UINT uSelector);
В файле windows.h определены макрокоманды LockData и UnlockData , предназначенные, соответственно, для фиксирования и расфиксирования текущего сегмента данных:
#define LockData(dummy) LockSegment((UINT)-1) #define UnlockData(dummy) UnlockSegment((UINT)-1)
Если вам необходимо выполнить код, расположенный в сегменте данных, вы можете выполнить преобразование селекторов, вызвав функцию AllocDStoCSAlias :
UINT WINAPI AllocDStoCSAlias(UINT uSelector);
В качестве параметра этой функции передается селектор сегмента данных, содержащего код. Функция создает новый дескриптор, описывающий данный сегмент как сегмент кода и возвращает соответствующий селектор. После использования полученный селектор следует освободить при помощи функции FreeSelector.
Функция PrestoChangoSelector создает селектор кодового сегмента, соответствующий селектору сегменту данных или наоборот, селектор сегмента данных, соответствующий сегменту кода:
UINT WINAPI PrestoChangoSelector( UINT sourceSel, UINT destSel);
Параметр sourceSel задает исходный селектор, который будет преобразован.
Параметр destSel задает селектор, полученный при помощи функции AllocSelector. Дескриптор, соответствующий этому селектору, получит новые значения атрибутов, которые будут соответствовать атрибутам преобразуемого селектора.
Работа с памятью в приложениях Windows
В этом разделе мы расскажем вам о типах памяти, используемых приложениями Windows и о том, как приложения могут заказать для себя память.
Работа с удаляемыми блоками памяти
Для того чтобы заказать удаляемый блок памяти, вы должны указать флаги 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 определяет тип восстановленного блока памяти. Можно указывать логическую комбинацию следующих флагов:
Флаг | Описание |
GMEM_DISCARDABLE | Если блок памяти был перемещаемый, то теперь дополнительно он будет и удаляемый. Этот флаг должен использоваться совместно с флагом GMEM_MODIFY |
GMEM_MODIFY | Выполняется изменение характеристик существующего блока памяти. этот флаг необходимо указывать вместе с флагами GMEM_DISCARDABLE и GMEM_MOVEABLE |
GMEM_MOVEABLE | Если раньше указанный блок памяти был перемещаемым и удаляемым, содержимое счетчика фиксирования для него равно нулю и новый размер блока указан равным нулю, данный блок будет удален. Если же параметр cbNewSize равен нулю, но блок памяти не является перемещаемым или удаляемым, функция вернет признак ошибки.Для фиксированного блока памяти ненулевой длины данный флаг разрешает перемещение, т. е. преобразует фиксированный блок в перемещаемый. |
GMEM_NODISCARD | Блок памяти не может быть удален операционной системой. Этот флаг должен использоваться совместно с флагом GMEM_MODIFY |
GMEM_ZEROINIT | Если размер блока памяти увеличивается, во все байты дополнительной памяти необходимо записать нулевые значения. Этот флаг несовместим с флагом GMEM_MODIFY |
Восстановив блок памяти, вы должны зафиксировать его функцией GlobalLock и затем восстановить прежнее содержимое, так как после удаления от блока остался только идентификатор.
Расширенный режим работы
Расширенный режим работы Windows доступен в том случае, если в компьютере установлен процессор 80386, 80486, Pentium, и имеется по крайней мере 2 Мбайт расширенной оперативной памяти.
В расширенном режиме включается механизм виртуальной памяти, позволяющий теоретически адресовать до 512 Мбайт памяти. При этом в Windows версии 3.1 можно использовать до 256 Мбайт физической оперативной памяти и создать файл виртуальной памяти размером до 256 Мбайт.
Схема адресации памяти процессора 80386 накладывает ограничение на количество дескрипторов в глобальной и локальной таблице дескрипторов - можно создать не более 8192 дескрипторов в каждой из таблиц. Так как все приложения Windows версии 3.1 используют одну общую локальную таблицу дескрипторов, всего для приложений Windows можно создать не более 8192 дескриптора, описывающих сегменты размером до 64 Кбайт. Однако реально создаются сегменты меньшего размера, поэтому Windows не позволяет приложениям использовать все 512 Мбайт памяти.
Сегодня такие требования к объему оперативной памяти могут показаться излишними. Однако не так давно память объемом 1024 Кбайт (т. е. целый мегабайт!) удовлетворяла всех (или почти всех) пользователей персонального компьютера IMB PC. С широким внедрением персональных компьютеров в область обработки видеоинформации (в том числе в реальном времени) требования к объему оперативной памяти и быстродействию всех системы существенно возрастают. В любом случае уже сейчас многие приложения Windows соглашаются работать только в расширенном режиме.
Ресурсы приложения
Управление ресурсами было рассмотрено нами в предыдущем томе "Библиотеки системного программиста". Как вы знаете, ресурсы представляют собой данные, расположенные в файле загрузочного модуля приложения и доступные только для чтения.
В файле описания ресурсов вы можете указать, что для хранения ресурсов следует использовать фиксированную, перемещаемую или удаляемую память. Вы можете описать ресурсы, как загружаемые в память при запуске приложения (PRELOAD) или по требованию (LOADONCALL).
Загрузка ресурсов в оперативную память выполняется такими функциями, как LoadIcon или CreateDialog . Для загрузки ресурсов, имеющих нестандартный формат, вы должны использовать функции FindResource (поиск ресурса и получение идентификатора ресурса) и LoadResource (загрузка ресурса и получение идентификатора блока памяти, в который загружен найденный ресурс).
Все эти функции были описаны в предыдущем томе, однако для удобства мы приведем их краткое описание еще раз.
Приведем прототип функции FindResource :
HRSRC WINAPI FindResource(HINSTANCE hInst, LPCSTR lpszName, LPCSTR lpszType);
Параметр hInst является идентификатором модуля, содержащего ресурс. Для извлечения ресурса из приложения вы должны указать его идентификатор, передаваемый функции WinMain через параметр hInstance.
Параметр lpszName должен содержать адрес имени ресурса. Для загрузки произвольных данных в качестве этого параметра следует передать указатель на строку, содержащую идентификатор ресурса.
Функции FindResource в качестве третьего параметра можно передавать идентификаторы предопределенных типов ресурсов, список которых приведен ниже.
Идентификатор ресурса | Название ресурса |
RT_ACCELERATOR | Таблица акселераторов |
RT_BITMAP | Изображение bitmap |
RT_CURSOR | Курсор |
RT_DIALOG | Диалоговая панель |
RT_FONT | Шрифт |
RT_FONTDIR | Каталог шрифтов |
RT_ICON | Пиктограмма |
RT_MENU | Меню |
RT_RCDATA | Произвольные данные |
RT_STRING | Таблица строк |
Вы можете использовать функцию 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.
Системное меню
При необходимости вы можете изменить системное меню (рис. 1.6), добавив в него новые строки или горизонтальные разделительные линии.
Прежде всего вам надо получить идентификатор системного меню. Это можно сделать при помощи функции GetSystemMenu :
HMENU WINAPI GetSystemMenu(HWND hwnd, BOOL fRevert);
Параметр hwnd является идентификатором окна, к системному меню которого требуется получить доступ.
Параметр fRevert определяет действия, выполняемые функцией GetSystemMenu. Если этот параметр указан как FALSE, функция GetSystemMenu возвращает идентификатор используемой на момент вызова копии системного меню. Если же значение этого параметра равно TRUE, функция восстанавливает исходный вид системного меню, используемый в Windows по умолчанию и уничтожает все созданные ранее копии системного меню. В последнем случае возвращаемое значение не определено.
После того как вы получили идентификатор системного меню, вы можете использовать функции AppendMenu, InsertMenu или ModifyMenu для изменения внешнего вида системного меню.
Есть одна особенность, которую нужно учитывать при добавлении собственной строки в системное меню. Как мы уже говорили, младшие четыре бита в сообщении WM_SYSCOMMAND могут иметь любые значения. С учетом этого обстоятельства следует выбирать идентификатор для добавляемой в системное меню строки. Очевидно, что значение этого идентификатора должно быть больше 15 и не должно конфликтовать с идентификаторами других строк меню приложения.
Приложение SMARTPAD, которое мы рассмотрим немного позже, добавляет в системное меню разделительную линию и новую строку, а также блокирует строку "Close", предназначенную для удаления окна.
Вначале в этом приложении мы определяем идентификатор системного меню, вызывая функцию GetSystemMenu:
hmenuSystemMenu = GetSystemMenu(hwnd, FALSE);
Далее к системному меню добавляется разделительная линия и строка "About...", для чего используется уже знакомая вам функция AppendMenu:
AppendMenu(hmenuSystemMenu, MF_SEPARATOR, 0, 0); AppendMenu(hmenuSystemMenu, MF_BYCOMMAND | MF_ENABLED, CM_SYSABOUT, "&About...");
В качестве идентификатора мы использовали значение 0x8880 (младшие четыре бита равны нулю):
#define CM_SYSABOUT 0x8880
Для блокирования строки "Close" мы вызываем функцию EnableMenuItem, указывая ей в качестве первого параметра идентификатор системного меню:
EnableMenuItem(hmenuSystemMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
Обработчик сообщения WM_SYSCOMMAND, определенный в функции главного окна приложения SMARTPAD, проверяет значение параметра wParam на совпадение с идентификатором добавленной нами строки без учета младших четырех бит:
case WM_SYSCOMMAND: { if((wParam & 0xfff0) == CM_SYSABOUT) { lpfnDlgProc = (DLGPROC)MakeProcInstance((FARPROC)DlgProc, hInst); DialogBox(hInst, "ABOUT", hwnd, lpfnDlgProc); return 0; } else if((wParam & 0xfff0) == SC_CLOSE) return 0; break; }
Для того чтобы заблокировать строку "Close", мы выполняем обработку сообщения WM_SYSCOMMAND с параметром wParam, равным SC_CLOSE (идентификатор стандартной строки "Close" в системном меню). Обработка заключается в возврате нулевого значения.
Так как ранее мы уже заблокировали эту строку при помощи функции EnableMenuItem, нет необходимости выполнять еще одну блокировку в обработчике сообщения WM_SYSCOMMAND. Мы сделали это исключительно для иллюстрации возможности блокировки строки системного меню при обработке сообщения WM_SYSCOMMAND.
Сообщение WM_COMMAND
Сообщение WM_COMMAND , как мы уже говорили, посылается функции окна приложения, создавшего меню, когда вы выбираете нужную вам строку. Параметр wParam содержит идентификатор строки, определенный в шаблоне меню.
Задача функции окна, обрабатывающей сообщения от меню, заключается в проверке значения параметра wParam и выполнении соответствующей функции.
Сообщение WM_INITMENU
Сообщение WM_INITMENU посылается окну, создавшему меню, в момент отображения меню. Это происходит, когда вы нажимаете на строку в полосе меню или активизируете временное меню при помощи клавиатуры.
Вместе с этим сообщением в параметре wParam передается идентификатор активизируемого меню. Параметр lParam не используется.
Если приложение обрабатывает сообщение WM_INITMENU, соответствующий обработчик должен вернуть нулевое значение. Обработка может заключаться в активизации или деактивизации строк меню, изменении состояния строк (отмеченное галочкой или не отмеченное) и т. п. Немного позже мы опишем функции, предназначенные для динамического изменения внешнего вида и состояния меню.
Сообщение WM_INITMENUPOPUP
Сообщение WM_INITMENUPOPUP посылается окну, когда операционная система Windows готова отобразить временное меню. Младшее слово параметра lParam содержит порядковый номер временного меню в меню верхнего уровня, старшее слово содержит 1 для системного меню или 0 для обычного меню.
Это сообщение можно использовать для активизации или блокирования отдельных строк временного меню.
Сообщение WM_SYSCOMMAND
Сообщение WM_SYSCOMMAND приходит в функцию окна приложения, когда пользователь выбирает строку из системного меню. Параметр wParam, как и для сообщения WM_COMMAND, содержит идентификатор строки меню, в данном случае, идентификатор строки системного меню. Параметр lParam не используется (за исключением идентификатора SC_HOTKEY).
Приведем список идентификаторов с кратким описанием.
Идентификатор | Описание |
SC_CLOSE | Удаление окна (строка "Close") |
SC_HOTKEY | Активизация окна, связанного с комбинацией клавиш, определенной приложением. Младшее слово параметра lParam содержит идентификатор активизируемого окна |
SC_HSCROLL | Свертка по горизонтали |
SC_KEYMENU | Выбор из меню при помощи комбинации клавиш |
SC_MAXIMIZE или SC_ZOOM | Максимизация окна (строка "Maximize") |
SC_MINIMIZE или SC_ICON | Минимизация окна (строка "Minimize") |
SC_MOUSEMENU | Выбор из меню при помощи мыши |
SC_MOVE | Перемещение окна (строка "Move") |
SC_NEXTWINDOW | Переключение на следующее окно |
SC_PREVWINDOW | Переключение на предыдущее окно |
SC_RESTORE | Восстановление нормального положения и размера окна |
SC_SCREENSAVE | Запуск приложения, предназначенного для предохранения экрана дисплея от преждевременного выгорания (screen-saver application), определенного в разделе [boot] файла system.ini |
SC_SIZE | Изменение размера окна (строка "Size") |
SC_TASKLIST | Запуск или активизация приложения Task Manager |
SC_VSCROLL | Свертка по вертикали |
При анализе параметра wParam учтите, что младшие четыре бита этого параметра могут принимать любые значения и должны игнорироваться:
if((wParam & 0xfff0) == SC_SIZE) { return 0; }
Скоро мы расскажем вам о том, как можно добавлять строки в системное меню. При добавлении строк в системное меню вы должны указывать идентификатор строки. Этот идентификатор (с учетом сказанного выше относительно младших четырех битов) вы получите в параметре wParam сообщения WM_SYSCOMMAND при выборе добавленной вами строки.
Создав собственный обработчик для сообщений, приходящих от системного меню, вы можете блокировать отдельные или все строки этого меню. Для блокировки какой-либо строки соответствующий обработчик должен вернуть нулевое значение, как в приведенном выше фрагменте кода, блокирующем изменение размера окна.
Сообщения, поступающие от меню
Меню посылает сообщения в функцию создавшего его окна.
Сообщение WM_INITMENU посылается перед отображением меню и может быть использовано для инициализации. Сообщение WM_COMMAND посылается после того, как пользователь выберет одну из активных строк меню. Системное меню посылает в окно приложения сообщение WM_SYSCOMMAND, которое обычно не обрабатывается приложением (передается функции DefWindowProc). В процессе выбора строки из меню, когда курсор перемещается по строкам меню, функция окна, создавшего меню, получает сообщение WM_MENUSELECT. Перед инициализацией временного меню функция окна получает сообщение WM_INITMENUPOPUP.
Из всех этих сообщений наибольший интерес представляют сообщения WM_INITMENU, WM_INITMENUPOPUP, WM_COMMAND, WM_SYSCOMMAND.
Создание файлов
Для создания файлов вы можете использовать как универсальную функцию OpenFile, описанную нами ранее, так и более простую функцию _lcreat :
HFILE WINAPI _lcreat(LPCSTR lpszFileName, int fuAttribute);
В качестве параметра lpszFileName этой функции необходимо передать адрес строки, содержащей путь к создаваемому файлу в кодировке ANSI.
С помощью параметра fuAttribute можно определить атрибуты создаваемого файла:
Значение атрибута | Описание |
0 | Нормальный файл, для которого разрешено выполнение операций чтения и записи |
1 | Этот файл можно открыть только для чтения |
2 | Скрытый файл |
3 | Системный файл |
Если указанный первым параметром файл не существует, функция _lcreat создает его и открывает для записи, возвращая идентификатор файла. Если файл существует, он обрезается до нулевой длины и затем открывается для чтения и записи.
Создание меню
Даже если в файле описания ресурсов нет определения шаблона меню, приложение может создать меню "с нуля" для любого своего перекрывающегося или временного окна (но не для дочернего). Для создания пустого меню (то есть меню, не содержащего ни одной строки и ни одного временного меню) можно воспользоваться функцией CreateMenu :
HMENU WINAPI CreateMenu(void);
Функция возвращает идентификатор созданного меню или NULL при ошибке.
Как правило, в меню верхнего уровня (в меню приложения) создаются временные меню. Для создания временного меню воспользуйтесь функцией CreatePopupMenu:
HMENU WINAPI CreatePopupMenu (void);
В дальнейшем вы можете добавить в меню верхнего уровня созданные функцией CreatePopupMenu временные меню или отдельные строки, вызвав функцию AppendMenu.
Перед завершением работы приложение должно удалить созданные описанным выше способом меню, для чего следует воспользоваться функцией DestroyMenu.
Для подключения к окну с идентификатором hwnd меню с идентификатором hmenu вы можете воспользоваться функцией SetMenu:
BOOL WINAPI SetMenu (HWND hwnd, HMENU hmenu);
Перед вызовом этой функции вы должны загрузить меню и получить его идентификатор, например, при помощи функции LoadMenu.
Функция SetMenu возвращает TRUE при успешном завершении и FALSE при ошибке.
Создание меню при помощи шаблона
Для создания меню вы можете использовать три метода.
Во-первых, можно описать шаблон меню в файле ресурсов приложения (аналогично шаблону диалоговой панели, но с использованием других операторов). Этот способ больше всего подходит для создания статических меню, не меняющихся или меняющихся не очень сильно в процессе работы приложения.
Во-вторых, можно создать меню "с нуля" при помощи специальных функций программного интерфейса Windows. Этот способ хорош для приложений, меняющих внешний вид меню, когда вы не можете создать заранее подходящий шаблон. Разумеется, второй метод пригоден и для создания статических меню.
В-третьих, можно подготовить шаблон меню непосредственно в оперативной памяти и создать меню на базе этого шаблона.
Создание шаблона меню
Шаблон меню можно создать в текстовом виде либо при помощи приложения Resource Workshop, либо обычным текстовым редактором, например, входящим в состав Borland Turbo C++ for Windows. В любом случае перед сборкой приложения текстовое описание шаблона меню должно находиться в файле ресурсов с расширением имени .rc, указанном в проекте приложения (или в файле, включаемом в файл проекта оператором #include).
Описание шаблона меню имеет следующий вид:
nameID MENU [load] [mem] BEGIN . . . . . . . . . END
Поле nameID используется для идентификации шаблона меню. Оно может указываться либо в виде текстовой строки, либо в виде числа от 1 до 65535.
Параметр load - необязательный. Он используется для определения момента загрузки меню в память. Если этот параметр указан как PRELOAD, меню загружается в память сразу после запуска приложения. По умолчанию используется значение LOADONCALL, в этом случае загрузка шаблона в память происходит только при отображении меню.
Параметр mem также необязательный. Он влияет на тип памяти, выделяемой для хранения шаблона, и может указываться как FIXED (ресурс всегда остается в фиксированной области памяти), MOVEABLE (при необходимости ресурс может перемещаться в памяти, это значение используется по умолчанию) или DISCARDABLE (если ресурс больше не нужен, занимаемая им память может быть использована для других задач). Значение DISCARDABLE может использоваться вместе со значением MOVEABLE.
Между строками BEGIN и END в описании шаблона располагаются операторы описания строк MENUITEM и операторы описания временных меню POPUP.
Оператор MENUITEM имеет следующий формат:
MENUITEM text, id [, param]
Параметр text определяет имя строки меню. Вы должны указать текстовую строку в двойных кавычках, например, "File". Текстовая строка может содержать символы &, \t, \a.
Если в текстовой строке перед буквой стоит знак &, при выводе меню данная буква будет подчеркнута. Например, строка "&File" будет отображаться как "File".
Клавиша, соответствующая подчеркнутой букве, может быть использована в комбинации с клавишей <Alt> для ускоренного выбора строки. Для того чтобы записать в строку сам символ &, его следует повторить дважды. Аналогично, для записи в строку меню символа двойной кавычки " его также следует повторить дважды.
Символ \t включает в строку меню символ табуляции и может быть использован при выравнивании текста в таблицах. Этот символ обычно используется только во временных и плавающих меню, но не в основном меню приложения, расположенном под заголовком главного окна.
Символ \a выравнивает текст по правой границе временного меню или полосы меню.
Параметр id представляет собой целое число, которое должно однозначно идентифицировать строку меню. Приложение получит это число в параметре wParam сообщения WM_COMMAND, когда вы выберете данную строку.
Необязательный параметр param указывается как совокупность атрибутов, разделенных запятой или пробелом. Эти атрибуты определяют внешний вид и поведение строки меню:
Атрибут | Описание |
CHECKED | При выводе меню на экран строка меню отмечается галочкой" " |
GRAYED | Строка меню отображается серым цветом и находится в неактивном состоянии. Такую строку нельзя выбрать. Этот атрибут несовместим с атрибутом INACTIVE |
HELP | Слева от текста располагается разделитель в виде вертикальной линии |
INACTIVE | Строка меню отображается в нормальном виде (не серым цветом), но находится в неактивном состоянии. Этот атрибут несовместим с атрибутом GRAYED |
MENUBREAK | Если описывается меню верхнего уровня, элемент меню выводится с новой строки. Если описывается временное меню, элемент меню выводится в новом столбце |
MENUBARBREAK | Аналогично атрибуту MENUBREAK, но дополнительно новый столбец отделяется вертикальной линией (используется при создании временных меню) |
POPUP text [, param] BEGIN . . . . . . . . . END
Между строками BEGIN и END в описании временного меню располагаются операторы описания строк MENUITEM и операторы описания вложенных временных меню POPUP.
Параметры text и param указываются так же, как и для оператора MENUITEM .
Для того чтобы создать в меню горизонтальную разделительную линию, используется специальный вид оператора MENUITEM :
MENUITEM SEPARATOR
Поясним сказанное выше на простом примере.
Скоро мы приведем исходные тексты приложения, имеющего меню (рис. 1.9). Это приложение называется, конечно, MENU.
Рис. 1.9. Главное окно приложения MENU, имеющего меню
Меню этого приложения состоит из строк "File", "Edit" и "Help". При выборе любой строки на экране появляется одно из трех временных меню.
Меню "File" (рис. 1.10) содержит строки, предназначенные для выполнения стандартных для приложений Windows команд, таких, как создание нового документа (или другого объекта) "New", загрузка документа "Open...", и т. д. Обратите внимание, что после строк "Save as..." и "Printer setup..." располагаются горизонтальные разделительные линии.
Рис. 1.10. Меню "File"
На рис. 1.11 показано временное меню "Edit". Оно организовано в виде таблицы из двух столбцов. В левом столбце находятся названия команд ("Undo", "Cut", "Copy", "Paste"), в правом - обозначения комбинаций клавиш, которые предназначены для ускоренного выбора строки меню.
Рис. 1.11. Меню "Edit"
Временное меню "Help" (рис. 1.12) содержит две неактивные строки ("Index" и "Keyboard"), три неактивные строки, отображаемые серым цветом ("Commands", "Procedures", "Using Help"), горизонтальную разделительную линию и обычную строку "About...").
Рис. 1.12. Меню "Help"
Для того чтобы создать описанное выше меню, в приложении Menu Demo в файле ресурсов определен шаблон меню:
#include "menu.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New", CM_FILENEW MENUITEM "&Open...", CM_FILEOPEN MENUITEM "&Save", CM_FILESAVE MENUITEM "Save &as...", CM_FILESAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", CM_FILEPRINT MENUITEM "Page se&tup...", CM_FILEPAGE_SETUP MENUITEM "P&rinter setup...", CM_FILEPRINTER_SETUP MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END
POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", CM_EDITUNDO MENUITEM "&Cut\tCtrl+X", CM_EDITCUT MENUITEM "&Copy\tCtrl+C", CM_EDITCOPY MENUITEM "&Paste\tCtrl+V", CM_EDITPASTE END
POPUP "&Help" BEGIN MENUITEM "&Index\tF1", CM_HELPINDEX, INACTIVE MENUITEM "&Keyboard", CM_HELPKEYBOARD, INACTIVE MENUITEM "&Commands", CM_HELPCOMMANDS, GRAYED MENUITEM "&Procedures", CM_HELPPROCEDURES, GRAYED MENUITEM "&Using help", CM_HELPUSING_HELP, GRAYED MENUITEM SEPARATOR MENUITEM "&About...", CM_HELPABOUT END END
Шаблон меню начинается с оператора MENU, в котором определено меню с именем APP_MENU. Это меню состоит из трех временных меню, описанных оператором POPUP.
Для определения строк временных меню используется оператор MENUITEM. В качестве второго оператора используются константы, символическое имя которых имеет префикс CM_. Мы определили эти константы в файле menu.hpp, включаемом в файл описания ресурсов оператором #include (можно использовать любые целые неодинаковые значения):
#define CM_HELPABOUT 24346 #define CM_HELPUSING_HELP 24345 #define CM_HELPPROCEDURES 24344 #define CM_HELPCOMMANDS 24343 #define CM_HELPKEYBOARD 24342 #define CM_HELPINDEX 24341
#define CM_EDITPASTE 24324 #define CM_EDITCOPY 24323 #define CM_EDITCUT 24322 #define CM_EDITUNDO 24321
#define CM_FILEEXIT 24338 #define CM_FILEPRINTER_SETUP 24337 #define CM_FILEPAGE_SETUP 24336 #define CM_FILEPRINT 24335 #define CM_FILESAVEAS 24334 #define CM_FILESAVE 24333 #define CM_FILEOPEN 24332 #define CM_FILENEW 24331
Обратите внимание на определение временного меню "Edit". Для того чтобы организовать меню в виде таблицы из двух столбцов, мы использовали символ табуляции \t.
В описании временного меню "Help" используются атрибуты строк INACTIVE и GRAYED. Напомним, что строки с такими атрибутами являются неактивными, их невозможно выбрать.
Разница между этими атрибутами заключается в том, что строка с атрибутом INACTIVE выводится нормальным цветом (как активная строка меню), а строка с атрибутом GRAYED выводится серым цветом.
Вы сможете легко подготовить описание шаблона меню при помощи текстового редактора в файле описания ресурсов, однако удобнее воспользоваться редактором ресурсов Resource Workshop. С помощью этого редактора вы можете создать шаблон меню и выполнить его тестирование, получив в результате текстовое описание шаблона, которое впоследствии можно редактировать. Именно так мы и поступили, создавая описанное выше меню.
Опишем кратко процесс создания шаблона меню при помощи приложения Resource Workshop.
Для того чтобы создать шаблон меню редактором ресурсов Resource Workshop, запустите его и из меню "File" выберите строку "New project...". В появившейся на экране диалоговой панели выберите тип ресурса - файл .RC, вслед за чем нажмите на кнопку "OK". Если файл описания ресурсов проектируемого приложения уже существует, вы можете открыть его, выбрав из меню "File" строку "Open project...".
Далее из меню "Resource" выберите строку "New...". На экране появится диалоговая панель "New resource". В списке "Resource type" выберите строку "MENU" и нажмите кнопку "OK". В главном окне приложения Resource Workshop вы увидите несколько окон, предназначенных для проектирования шаблона меню (рис. 1.13).
Рис. 1.13. Проектирование шаблона меню
Окно "TEST MENU" предназначено для визуальной проверки проектируемого меню. С помощью этого окна в любой момент времени вы можете проверить работу созданного вами меню.
Окно MENU_1 содержит текстовое описание создаваемого меню. Первоначально меню состоит из одного временного меню "Pop-up", в котором определена одна строка "Item". Вы можете выбирать мышью строки описания элементов меню, при этом в левой части окна "MENU" вам будет предоставлена возможность изменить название, идентификатор и атрибуты соответствующего элемента.
Для добавления строки во временное меню в окне "MENU_1" выделите строку, после которой надо добавить новую. Затем из меню "Menu" выберите строку "New menu item". Можно добавить горизонтальную разделительную линию. Для этого из меню "Menu" выберите строку "New separator".
Если вам надо добавить новое временное меню, выделите строку "__EndPopup__" и выберите из меню "Menu" строку "New pop-up".
Для удаления строки или временного меню вам достаточно выделить соответствующую строку в окне "MENU_1" и нажать клавишу <Delete>.
Для того чтобы изменить атрибуты строки меню выделите нужную строку в окне "MENU_1" и в левой части окна "MENU:MENU_1" укажите новые атрибуты.
В поле "Item text" вы можете изменить текст, соответствующей строке меню.
В поле "Item id" можно задать идентификатор строки меню. Это может быть любое целое число. Когда вы создаете меню при помощи Resource Workshop, идентификаторы строк меню присваиваются автоматически. Напомним, что идентификатор строки меню передается функции окна приложения в параметре wParam сообщения WM_COMMAND. Пользуясь этим идентификатором, приложение определяет строку, выбранную вами в меню, после чего выполняет необходимые действия в соответствии с логикой работы приложения.
С помощью группы переключателей "Item type" вы можете изменить тип выделенной вами в окне "MENU_1" строки, превратив обычную строку ("Menu item") в разделительную линию ("Separator").
При помощи переключателя "Break before" можно определить для строк принудительный переход на новую строку или в новый столбец.
Если включен переключатель "No break", элементы меню располагаются как обычно, то есть строка нового временного меню добавляется в полосе меню с левой стороны, а новая строка временного меню располагается в нижней части меню.
Если включен переключатель "Menu bar break", строка названия временного меню располагается не слева, а внизу, образуя "второй этаж" полосы меню.
Эта возможность используется редко.
Переключатель " Menu break" предназначен для размещения строки временного меню в другом столбце меню.
Переключатель "Help break" задает выравнивание названия временного меню по правой границе полосы меню. В приложениях, созданных для Windows версии 3.0, такое расположение использовалось для меню "Help". Однако в стандартных приложениях Windows версии 3.1 выравнивание меню "Help" по правой границе не выполняется.
С помощью переключателя "Initial state" вы можете задать начальное состояние элемента меню как активное ("Enabled"), неактивное ("Disabled") или неактивное с отображением серым цветом ("Grayed"). Можно также указать, что строка меню должна быть отмечена галочкой. Для этого следует установить переключатель "Checked".
Меню "Menu" приложения Resource Workshop содержит строки "New file pop-up", "New edit pop-up" и "New help pop-up". Эти строки предназначены для автоматического создания стандартных меню "File", "Edit" и "Help", которые должны быть в любом стандартном приложении Windows. Мы воспользовались этими строками для создания меню в нашем приложении Menu Demo, упомянутом выше.
Выбрав из меню "Menu" строку "Check duplicates", вы можете проверить идентификаторы строк созданного вами меню. Если в меню есть строки с одинаковыми идентификаторами, на экране появится диалоговая панель с предупреждающим сообщением.
После завершения процесса формирования меню вы можете изменить имя меню в описании шаблона. Для этого из меню "Resource" выберите строку "Rename". На экране появится диалоговая панель "Rename resource", с помощью которой вы сможете изменить имя меню (рис. 1.14).
Рис. 1.14. Изменение имени меню
После того как вы измените имя меню и нажмете на клавишу "OK", на экране появится диалоговая панель с запросом о необходимости создания символической константы, соответствующей данному имени.В этой панели следует нажать на клавишу "No", так как наше приложение будет ссылаться на имя меню при помощи текстовой строки, а не константы.
Стандартные диалоговые панели для открытия файлов
В составе операционной системы Windows версии 3.1 имеется DLL-библиотека commdlg.dll, экспортирующая среди прочих две функции, очень удобные для организации пользовательского интерфейса при открытии файлов. Это функции GetOpenFileName и GetSaveFileName . Мы уже пользовались этими функциями в приложениях OEM2ANSI и OEM3ANSI.
Функция GetOpenFileName выводит на экран стандартную или измененную приложением диалоговую панель "Open", позволяющую выбрать файл (рис. 4.1).
Рис. 4.1. Диалоговая панель "Open"
Функция GetSaveFileName выводит стандартную или измененную приложением диалоговую панель "Save As..." (рис. 4.2).
Рис. 4.2. Диалоговая панель "Save As..."
Внешний вид этих диалоговых панелей определяется структурой типа OPENFILENAME , определенной в файле commdlg.h (этот файл находится в каталоге include системы разработки Borland C++ или Microsoft Visual C++):
typedef struct tagOFN { DWORD lStructSize; HWND hwndOwner; HINSTANCE hInstance; LPCSTR lpstrFilter; LPSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPSTR lpstrFile; DWORD nMaxFile; LPSTR lpstrFileTitle; DWORD nMaxFileTitle; LPCSTR lpstrInitialDir; LPCSTR lpstrTitle; DWORD Flags; UINT nFileOffset; UINT nFileExtension; LPCSTR lpstrDefExt; LPARAM lCustData; UINT (CALLBACK *lpfnHook)(HWND, UINT, WPARAM, LPARAM); LPCSTR lpTemplateName; } OPENFILENAME;
Адрес структуры передается функциям GetOpenFileName и GetSaveFileName в качестве параметра lpofn:
BOOL WINAPI GetOpenFileName(OPENFILENAME FAR* lpofn); BOOL WINAPI GetSaveFileName(OPENFILENAME FAR* lpofn);
Обе функции возвращают ненулевое значение, если пользователь сделал выбор файла, и ноль, если он отказался от выбора, нажав кнопку "Cancel" или выбрав строку "Close" из системного меню диалоговой панели. Нулевое значение возвращается также при возникновении ошибки.
В результате выбора некоторые поля структуры будут заполнены информацией о выбранном файле.
Опишем назначение отдельных полей структуры OPENFILENAME.
Стандартный режим работы
Для работы Windows версии 3.1 в стандартном режиме в компьютере должен быть установлен процессор 80286 или 80386, а также не менее 1-2 Мбайт оперативной памяти.
В стандартном режиме работы всю память можно разделить на стандартную, расположенную в пределах первого мегабайта физического адресного пространства, и расширенную, расположенную выше.
В пределах первых 640 Кбайт стандартной памяти находится операционная система MS-DOS, драйверы и резидентные программы, запущенные до загрузки Windows.
В стандартном режиме Windows версии 3.1 не может адресовать более 16 Мбайт памяти. Такое ограничение накладывается схемой адресации процессора 80286. Даже если вы запустили Windows в стандартном режиме на процессоре 80386, указанное ограничение продолжает действовать, так как в этом случае процессор 80386 будет работать с памятью как его предшественник, процессор 80286.
Статическая и динамическая компоновка
Вспомним старые добрые времена, когда операционная система MS-DOS и компьютеры с памятью 1 Мбайт удовлетворяли запросы подавляющего большинства пользователей. Как создавался загрузочный файл программы для MS-DOS? Вы готовили исходный текст приложения на своем любимом языке программирования (Си, Паскаль, Модула-2 и т. д.), затем транслировали его для получения объектного модуля. После этого в дело включался редактор связей, который компоновал объектные модули, полученные в результате трансляции исходных текстов и модули из библиотек объектных модулей, в один exe-файл. В процессе запуска файл программы загружался полностью в оперативную память и ему передавалось управление.
Таким образом, редактор связей записывал в файл программы все модули, необходимые для работы. Для обращения к внешним по отношению к программам модулям операционной системы MS-DOS и модулям BIOS использовался механизм программных прерываний. В любой момент времени в оперативной и постоянной памяти компьютера находился весь код, необходимый для работы как запущенной программы, так и самой операционной системы MS-DOS.
Если же программа была сложной и не помещалась целиком в 640 Кбайт памяти, на помощь приходили оверлеи, позволяющие загружать в оперативную память только те сегменты программы, которые требовались для решения текущей задачи.
В среде мультизадачной операционной системы, такой как Windows, OS/2 или UNIX, статическая компоновка неэффективна, так как приводит к неэкономному использованию очень дефицитного ресурса - оперативной памяти. Представьте себе, что в системе одновременно работают 5 приложений, и все они вызывают такие функции, как sprintf, memcpy, strcmp и т. д. Если приложения были собраны с использованием статической компоновки, в памяти будут находится одновременно 5 копий функции sprintf, 5 копий функции memcpy, и т. д (рис. 3.1). А ведь программы могут использовать и более сложные функции, например, предназначенные для работы с диалоговыми панелями или масштабируемыми шрифтами!
Рис. 3.1. Вызов функций при использовании статической компоновки
Очевидно, использование оперативной памяти было бы намного эффективнее, если бы в памяти находилось только по одной копии функций, а все работающие параллельно программы могли бы их вызывать.
Практически в любой многозадачной операционной системе для любого компьютера используется именно такой способ обращения к функциям, нужным одновременно большому количеству работающих параллельно программ.
При использовании динамической компоновки загрузочный код нескольких (или нескольких десятков) функций объединяется в отдельные файлы, загружаемые в оперативную память в единственном экземпляре. Программы, работающие параллельно, вызывают функции, загруженные в память из файлов библиотек динамической компоновки, а не из файлов программ.
Таким образом, используя механизм динамической компоновки, в загрузочном файле программы можно расположить только те функции, которые являются специфическими для данной программы. Те же функции, которые нужны всем (или многим) программам, работающим параллельно, можно вынести в отдельные файлы - библиотеки динамической компоновки, и хранить в памяти в единственном экземпляре (рис. 3.2). Эти файлы можно загружать в память только при необходимости, например, когда какая-нибудь программа захочет вызвать функцию, код которой расположен в библиотеке.
Рис. 3.2. Вызов функции при использовании динамической компоновки
В операционной системе Windows версии 3.1 файлы библиотек динамической компоновки (DLL-библиотеки) имеют расширение имени dll, хотя можно использовать любое другое, например, exe. В первых версиях Windows DLL-библиотеки располагались в файлах с расширением имени exe. Возможно поэтому файлы krnl286.exe, krnl386.exe, gdi.exe и user.exe имеют расширение имени exe, а не dll, не смотря на то, что перечисленные выше файлы, составляющие ядро операционной системы Windows, есть ни что иное, как DLL-библиотеки.
Механизм динамической компоновки используется не только в системе Windows.Более того, он был изобретен задолго до появления Windows. Например, в мультизадачных многопользовательских операционных системах VS1, VS2, MVS, VM, созданных для компьютеров IBM-370 и аналогичных (вспомните добрым словом ушедшую в прошлое серию ЕС ЭВМ и операционные системы SVS, TKS, СВМ, МВС), код функций, нужных параллельно работающим программам, располагается в отдельных библиотеках и может загружаться при необходимости в специально выделенную общую область памяти. Операционная система OS/2 также работает с DLL-библиотеками.
Статическая память
Статические данные, описанные в приложении 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, а не то, которое использовалось при загрузке приложения в память.
Структура DLL-библиотеки
DLL-библиотека состоит из нескольких специфических функций и произвольного набора функций, выполняющих ту работу, для которой разрабатывалась данная библиотека. Как мы уже говорили, DLL-библиотека может иметь (а может и не иметь) сегмент данных и ресурсы.
В заголовке загрузочного модуля DLL-библиотеки описаны экспортируемые точки входа, соответствующие всем или некоторым определенным в ней функциям. Приложения могут вызывать только те функции DLL-библиотеки, которые экспортируются ей.
Текст строки меню
С помощью функции GetMenuString вы можете переписать в буфер текстовую строку, соответствующую элементу меню.
int WINAPI GetMenuString(HMENU hmenu, UINT idItem, LPSTR lpsz, int cbMax, UINT fuFlags);
Параметр hmenu определяет меню, для которого будет выполняться операция.
Параметр idItem определяет элемент меню, над которым выполняется операция. Интерпретация этого параметра зависит от значения параметра fuFlags.
Если в параметре fuFlags указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор строки меню, для которого выполняется операция. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер этой строки.
Адрес буфера, в который будет выполняться копирование, задается параметром lpsz, размер буфера без учета двоичного нуля, закрывающего строку, - оператором cbMax. Символы, не поместившиеся в буфер, будут обрезаны.
Функция GetMenuString возвращает количество символов, скопированных в буфер, без учета двоичного нуля, закрывающего строку.
Типы сегментов
В операционной системе MS-DOS с точки зрения процессора все сегменты памяти были одинаковыми. Деление их на сегменты кода и сегменты данных достаточно условное, так как в MS-DOS нет никаких препятствий для того чтобы загрузить в CS:IP адрес любого сегмента, например, сегмента данных программы. Точно также программа могла выполнять любые операции в своем (или чужом) сегменте кода, или в сегменте, который принадлежит операционной системе. Так как в MS-DOS одновременно может работать только одна программа, вся оперативная память отдается ей в полное распоряжение.
В мультизадачной операционной системе Windows память используется одновременно многими приложениями. Схема адресации защищенного режима эффективно защищает ядро Windows от приложений и позволяет в расширенном режиме увеличить размер глобальной области памяти за счет механизма виртуальной памяти.
Однако для приложений нужны блоки памяти, занимающие непрерывное адресное пространство. Если в мультизадачной операционной системе не принимать никаких мер для уменьшения фрагментации адресного пространства, при интенсивной работе приложений очень скоро приложения не смогут получить непрерывный блок памяти даже при достаточном общем объеме свободной памяти.
К счастью, операционная система Windows умеет объединять свободные блоки памяти, используя механизм перемещаемых (moveable ) и удаляемых (discardable ) сегментов.
Операционная система Windows версии 3.0 могла работать как в реальном, так и в защищенном режиме. Перемещение блоков памяти в реальном режиме работы процессора представляет собой нетривиальную задачу, так как необходимо обеспечить работу приложения с блоками памяти, логический адрес которых может произвольно изменяться операционной системой в процессе перемещения. Эта проблема решалась с помощью двухступенчатой схемы получения доступа к блоку памяти.
Согласно этой схеме на первом шаге получения доступа приложение заказывает для себя блок памяти, расположенный в фиксированном или перемещаемом сегменте.
Фиксированный (fixed ) сегмент имеет постоянный логический адрес <селектор:смещение> и не никогда не перемещается в адресном пространстве. Если ваше приложение заказывает для себя память при помощи функции malloc , она получает память в фиксированном сегменте.
Блок памяти, расположенный в перемещаемом сегменте, не имеет постоянного логического адреса. При создании этот блок получает идентификатор, который и используется для ссылки. Windows может в любой момент времени переместить его в любое место адресного пространства для объединения свободных блоков памяти.
Вы можете спросить, а как же пользоваться перемещаемым блоком памяти, не имеющим постоянного адреса?
Очень просто, на втором шаге непосредственно перед использованием перемещаемый блок памяти необходимо зафиксировать, вызвав специальную функцию из программного интерфейса Windows.
После использования блок памяти следует расфиксировать, для того чтобы Windows могла его перемещать.
В защищенном режиме возможно перемещение зафиксированных блоков памяти, при этом их логический адрес не изменяется. Вместо этого изменяется базовый адрес в локальной таблице дескрипторов. Поэтому перемещение зафиксированных блоков памяти происходит для приложений незаметно. Это обстоятельство значительно упрощает приемы работы с сегментами.
Таким образом, даже если ваше приложение, работающее в защищенном режиме, заказало фиксированный блок памяти, он все равно остается перемещаемым! Поэтому, создавая приложения для Windows версии 3.1 вы можете использовать фиксированные блоки памяти почти без ущерба для эффективности работы системы дефрагментации (так как логический адрес фиксированного блока не изменяется, это может привести к невозможности освобождения непрерывного пространства в таблице дескрипторов, нужного для адресации блоков памяти размером, большим 64 Кбайт).
Если же вам и в самом деле необходимо получить блок памяти с фиксированным линейным адресом, придется вызывать специальную функцию GlobalFix , запрещающую изменение базового адреса в локальной таблице дескрипторов.
Кроме того, если нужно обеспечить постоянное присутствие данного сегмента в физической памяти, для такого блока следует отключить механизм страничного обмена, вызвав функцию GlobalPageLock .
Фиксирование линейного адреса и отмена страничного обмена - крайняя мера. Обычные приложения Windows не должны вызывать функции GlobalFix и GlobalPageLock, так как это может привести к снижению производительности работы операционной системы и приложений.
Логический адрес перемещаемого блока памяти, состоящий из селектора и смещения, может произвольно изменяться операционной системой Windows. Для фиксирования блоков памяти в логическом адресном пространстве необходимо использовать функции GlobalLock или LocalLock.
Операционная система Windows версии 3.1 перемещает зафиксированные блоки памяти, изменяя базовый адрес в локальной таблице дескрипторов, поэтому такое перемещение не приводит к изменению логического адреса <селектор:смещение> и незаметно для приложений. Даже если приложение заказывает фиксированный блок памяти, но не вызывает функцию GlobalFix, этот блок памяти будет перемещаемым.
Другой тип сегментов, предусмотренный в Windows, - удаляемые сегменты. В любой момент времени Windows может удалить сегмент из памяти, сохранив, однако, его идентификатор. Память, занимаемая ранее удаляемым сегментом, может быть использована Windows для других приложений.
При попытке зафиксировать удаленный сегмент приложение получает код ошибки. В этом случае приложение должно самостоятельно восстановить содержимое сегмента и затем продолжить свою работу.
В файле определения модуля вы можете указать, что сегмент кода вашего приложения должен быть перемещаемым и удаляемым, а сегмент данных - перемещаемым. Для этого используются параметры moveable и discardable:
CODE preload moveable discardable DATA preload moveable multiple
В этом случае операционная система может перемещать память приложения и при необходимости удалять сегмент кода неактивного приложения из памяти. Если этот сегмент потребуется вновь, Windows самостоятельно загрузит его из exe-файла приложения.
Удаление строк
Для удаления элементов меню, таких, как строки и временные меню, предназначена функция DeleteMenu :
BOOL WINAPI DeleteMenu(HMENU hmenu, UINT idItem, UINT fuFlags);
Параметр hmenu определяет меню, из которого будет удален элемент.
Параметр idItem определяет удаляемый элемент, причем его интерпретация зависит от значения параметра fuFlags.
Если в параметре fuFlags указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор удаляемого элемента меню. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер удаляемого элемента меню.
При удалении временного меню все связанные с ним ресурсы освобождаются.
Для отображения результата удаления меню следует вызвать функцию DrawMenuBar.
В программном интерфейсе Windows определена функция RemoveMenu , имеющая параметры, аналогичные параметрам функции DeleteMenu:
BOOL WINAPI RemoveMenu(HMENU hmenu, UINT idItem, UINT fuFlags);
Эта функция удаляет указанный ей элемент из меню, но не уничтожает связанные с ним ресурсы, поэтому вы можете вновь воспользоваться удаленным элементом меню (если знаете его идентификатор, о том как получить идентификатор временного меню мы расскажем немного позже).
Напомним, что для уничтожения меню используется функция DestroyMenu :
BOOL WINAPI DestroyMenu(HMENU hmenu);
В качестве параметра функции передается идентификатор уничтожаемого меню. Функция освобождает все ресурсы, связанные с уничтоженным меню.
Уменьшение размера локального блока памяти
Для уменьшения размера существующего локального блока памяти можно использовать функцию LocalShrink :
UINT WINAPI LocalShrink(HLOCAL hloc, UINT cbNewSize);
Параметр hloc указывает идентификатор изменяемого локального блока памяти. Новые размеры блока памяти задаются параметром cbNewSize.
Возвращаемое значение в случае успеха равно новому размеру блока памяти.
В процессе получения памяти при помощи функции LocalAlloc размер использованной локальной памяти будет расти, пока не достигнет 64 Кбайт. После освобождения локальных блоков данных размер локальной области не уменьшится сам по себе. Для восстановления этого размера вы должны вызвать функцию LocalShrink.
Управление памятью
2.1.
2.2.
2.3.
2.4.
2.5.
2.6.
2.7.
2.8.
В этой главе мы рассмотрим механизм управления памятью в операционной системе Windows и функции программного интерфейса Windows, предназначенные для работы с оперативной памятью. Если вы раньше составляли программы только для операционной системы MS-DOS, система управления памятью, реализованная в Windows, может показаться вам очень сложной и запутанной. Однако в отличие от MS-DOS, операционная система Windows является мультизадачной и к тому же использует защищенный режим работы процессора. Все это не может не оказать влияние на сложность системы управления памятью.
До сих пор в наших приложениях мы почти не использовали функции управления памятью. Там же где это было абсолютно необходимо, мы получали у Windows буфер памяти с помощью хорошо знакомой вам из MS-DOS функции malloc. В некоторых случаях вы можете продолжать использовать эту функцию, однако для приложений Windows предусмотрены и другие, более мощные средства.
Защищенный режим работы процессора накладывает на приложения свои ограничения. Если программа, составленная для MS-DOS, имеет доступ к любому участку основной, расширенной или дополнительной памяти, а также к любому оборудованию компьютера (через порты ввода/вывода), приложение Windows поставлено в жесткие рамки. Например, приложение может работать только с теми блоками памяти, которые ей принадлежат или получены от операционной системы. Приложение не может выполнять запись в сегмент кода и не может передавать управление в сегмент данных. Что же касается портов ввода/вывода, то в расширенном режиме работы приложение, как правило, не имеет к ним непосредственного доступа. Если приложение попытается вывести данные в порт, это приведет к переключению задачи. Операционная система Windows проверит номер порта и, возможно, предоставит вам возможность что-нибудь в него записать. А возможно, что и не предоставит, выполнив вашу команду ввода/вывода как команду NOP.
Все указанные выше ограничения необходимы в мультизадачной среде.
Если разрешить приложениям обращаться к любым участкам оперативной памяти, недостаточно хорошо отлаженное приложение сможет разрушить операционную систему или другое приложение, работающее параллельно. Если разрешить приложениям работать с портами ввода/вывода, может возникнуть конфликт из-за попытки двух приложений работать одновременно с одним периферийным устройством.
Однако приложение Windows, тем не менее, может делать все что угодно, если оно работает вместе с так называемым виртуальным драйвером или, как его еще называют, драйвером виртуального устройства ввода/вывода. Виртуальный драйвер - это особая разновидность драйверов в Windows. Виртуальные драйверы предназначены для создания виртуальных устройств ввода/вывода, предоставляемых в пользование параллельно работающим приложениям Windows и виртуальным машинам MS-DOS. Так как виртуальные драйверы работают непосредственно с аппаратурой, для них не действует большинство ограничений, накладываемых на приложения Windows. В частности, виртуальный драйвер может обращаться к любой области памяти, выполнять команды ввода/вывода и привилегированные команды процессора.
Поэтому, если создаваемое вами приложение должно работать с нестандартной аппаратурой в режиме реального времени, использовать канал прямого доступа к памяти или аппаратные прерывания, вы должны создать собственный виртуальный драйвер и поставлять его вместе с приложением. Для разработки драйверов вам необходимо приобрести Microsoft Driver Development Kit for Windows 3.1, в состав которого, кроме документации и исходных текстов некоторых стандартных драйверов входит специальная версия ассемблера и редактора связей, а также утилиты, необходимые для создания виртуальных драйверов.
Разработка драйверов для Windows - тема для отдельной книги, которая, возможно, еще будет нами написана. Сейчас мы хотели бы подчеркнуть, что в операционной системе Windows обычные приложения никогда не обращаются напрямую к аппаратуре и не работают с системными областями памяти, такими, как таблицы дескрипторов прерываний или видеопамять.
Все это делают драйверы и виртуальные драйверы. Поэтому ограничения, накладываемые на обычные приложения Windows, никак не сказываются на функциональных возможностях самого приложения. Если ваша задача не решается в рамках обычного приложения, создайте свой драйвер и организуйте связь между драйвером и приложением. Если и драйвер не в силах сделать то, что вам нужно (стандартный драйвер, например, не может выполнять привилегированные команды процессора), создайте виртуальный драйвер, которому разрешено все. Пусть ваше приложение занимается выводом данных на экран и обработкой сообщений от клавиатуры и мыши, а драйвер работает с аппаратурой и выполняет "запретные" привилегированные команды процессора.
В расширенном режиме работы Windows приложения могут пользоваться так называемой виртуальной памятью. Виртуальная память позволяет создать иллюзию работы с оперативной памятью огромного размера - десятки и сотни мегабайт! Реально в физической памяти располагаются только те области виртуальной памяти, к которым чаще всего происходит обращение. Остальные области памяти (страницы памяти) хранятся на диске в специальном файле. Виртуальная память облегчает работу с очень большими массивами данных. Вы можете, например, получить у Windows буфер размером 10 Мбайт и заполнить его содержимым файла, затем выполнить сортировку этого буфера и записать результат в тот же самый или другой файл.
В документации, поставляющейся вместе с SDK, выделяются семь типов памяти, которые могут использоваться приложениями. Это статическая память, автоматическая память, локальная и глобальная память, память, резервируемая при регистрации класса окна, память, резервируемая при создании самого окна, и память, выделяемая для загрузки ресурсов. Вы должны научиться пользоваться всеми типами памяти, так как каждый из перечисленных выше типов памяти предназначен для решения своего круга задач.
Установка фильтра
Для установки фильтра в операционной системе Windows версии 3.1 следует использовать функцию SetWindowsHookEx (в Windows версии 3.0 для этой цели была предназначена функция SetWindowsHook ):
HHOOK WINAPI SetWindowsHookEx( int idHook, // тип фильтра HOOKPROC lpfn, // адрес функции фильтра HINSTANCE hInstance, // идентификатор приложения HTASK hTask); // задача, для которой устанавливается фильтр
Параметр idHook определяет тип встраиваемого фильтра. В качестве значения для этого параметра можно использовать одну из следующих констант:
Константа | Назначение фильтра |
WH_CALLWNDPROC | Для функции окна. Используется в Windows версии 3.0 и 3.1. Можно устанавливать для отдельной задачи или для всей системы |
WH_CBT | Для обучающих программ. Можно устанавливать для отдельной задачи или для всей системы |
WH_DEBUG | Для отладкиИспользуется в Windows версии 3.1. Можно устанавливать для отдельной задачи или для всей системы |
WH_GETMESSAGE | Фильтр сообщений. Получает управление после выборки сообщения функцией GetMessageИспользуется в Windows версии 3.0 и 3.1. Можно устанавливать для отдельной задачи или для всей системы |
WH_HARDWARE | Фильтр для сообщений, поступающих от нестандартного аппаратного обеспечения, такого как система перьевого ввода. Используется в Windows версии 3.1. Можно устанавливать для отдельной задачи или для всей системы |
WH_JOURNALPLAYBACK | Фильтр для "проигрывания" событийИспользуется в Windows версии 3.0 и 3.1. Можно устанавливать только для всей системы |
WH_JOURNALRECORD | Фильтр для записи событийИспользуется в Windows версии 3.0 и 3.1. Можно устанавливать только для всей системы |
WH_KEYBOARD | Фильтр сообщений, поступающих от клавиатуры. Используется в Windows версии 3.1. Можно устанавливать для отдельной задачи или для всей системы |
WH_MOUSE | Фильтр сообщений, поступающих от мыши. Используется в Windows версии 3.1. Можно устанавливать для отдельной задачи или для всей системы |
WH_MSGFILTER | Фильтр сообщений, который получает управление после выборки, но перед обработкой сообщений, поступающих от диалоговых панелей или меню. Используется в Windows версии 3.0 и 3.1. Можно устанавливать для отдельной задачи или для всей системы |
WH_SHELL | Фильтр для получения различных извещений от операционной системы Windows. Используется в Windows версии 3.1. Можно устанавливать для отдельной задачи или для всей системы |
WH_SYSMSGFILTER | Фильтр вызывается операционной системой после того, как диалоговая панель или меню получат сообщение, но перед обработкой этого сообщения. Данный фильтр может обрабатывать сообщения для любых запущенных приложений Windows. Используется в Windows версии 3.0 и 3.1. Можно устанавливать только для всей системы |
Параметр lpfn функции SetWindowsHookEx должен определять адрес функции встраиваемого фильтра (или, иными словами, функции перехвата сообщений).
Функция фильтра может находиться либо в приложении, устанавливающем фильтр, либо (что значительно лучше) в DLL-библиотеке. Если функция находится в приложении, или в DLL-библиотеке, загружаемой явным образом при помощи функции LoadLibrary, в качестве параметра lpfn следует использовать значение, полученное от функции MakeProcInstance. Если же для импортирования функций из DLL-библиотеки используется библиотека импорта (как в описанном ниже приложении WINHOOK), параметр lpfn может содержать непосредственный указатель на функцию фильтра.
Через параметр hInstance функции SetWindowsHookEx следует передать идентификатор модуля, в котором находится встраиваемая функция фильтра. Если функция фильтра определена внутри приложения, в качестве этого параметра необходимо использовать идентификатор текущей копии приложения, передаваемой через соответствующий параметр функции WinMain. Если же функция фильтра находится в DLL-библиотеке, данный параметр должен содержать идентификатор модуля библиотеки, передаваемый через параметр hInstance функции LibMain.
Последний параметр функции hTask должен содержать идентификатор задачи, для которой определяется функция фильтра. Если последний параметр указан как NULL, фильтр встраивается для всех задач, работающих в системе.
Если приложение встраивает фильтр для себя, оно должно определить идентификатор задачи, соответствующий собственной копии приложения. Это проще всего сделать, вызвав функцию GetCurrentTask :
HTASK WINAPI GetCurrentTask(void);
Можно определить идентификатор задачи исходя из идентификатора окна, созданного этой задачей. Для этого следует воспользоваться функцией GetWindowTask :
HTASK WINAPI GetWindowTask(HWND hwnd);
Эта функция возвращает идентификатор задачи, создавшей окно с идентификатором hwnd.
Функция SetWindowsHookEx возвращает 32-разрядный идентификатор функции фильтра, который следует сохранить для дальнейшего использования, или NULL при ошибке.
В приведенном ниже фрагменте кода, взятом из приложения WINHOOK, устанавливается фильтр для сообщений, поступающих во все приложения от клавиатуры:
hhook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KbHookProc, hInst, NULL);
Исходный текст функции фильтра приведен (с сильными сокращениями) ниже:
extern "C" LRESULT CALLBACK KbHookProc(int code, WPARAM wParam, LPARAM lParam) { ... ... ... // Вызываем следующий в цепочке перехватчик return CallNextHookEx(hhook, code, wParam, lParam); }
После выполнения всех необходимых действий функция фильтра передает управление по цепочке другим фильтрам (что необязательно). Для этого вызывается функция CallNextHookEx :
LRESULT WINAPI CallNextHookEx( HHOOK hHook, int code, WPARAM wParam, LPARAM lParam);
Параметр hHook содержит идентификатор текущей функции фильтра.
Параметр code содержит код фильтра, который должен передаваться следующему фильтру в цепочке.
Параметры wParam и lParam содержат, соответственно, 16- и 32-битовый дополнительные параметры.
Параметры code, wParam и lParam функции CallNextHookEx полностью соответствуют параметрам функции фильтра, которая будет рассмотрена нами позже.
Утилита HEAPWALK
В составе SDK имеется утилита HEAPWALK , с помощью которой вы можете выполнить анализ содержимого глобальной области памяти. Для любого модуля, загруженного в память (приложения или библиотеки DLL) эта утилита показывает адреса, идентификаторы, размеры и атрибуты блоков памяти. С помощью этой утилиты вы можете проследить за тем, как ваше приложение работает с глобальной областью памяти. Вы можете обнаружить в отлаживаемом приложении часто встречающуюся ошибку, когда оно (приложение) не освобождает полученные им блоки памяти.
Главное окно утилиты HEAPWALK представлено на рис. 2.12.
Рис. 2.12. Главное окно утилиты HEAPWALK
В главном окне находится список, каждая строка которого соответствует одному блоку памяти.
В столбцах списка отображается следующая информация (все численные значения выражаются шестнадцатеричными цифрами):
Название | Описание |
ADDRESS | Адрес блока памяти |
HANDLE | Идентификатор блока памяти |
SIZE | Размер блока памяти в байтах |
LOCK | Содержимое счетчика фиксирования блока памяти. Если для блока памяти запрещен страничный обмен, в этом поле есть буква "P". Если блок памяти зафиксирован и не может быть удален, он обозначается буквой "L" |
FLG | Если блок памяти удаляемый (discardable), в этом поле находится буква "D", если фиксированный - буква "F" |
HEAP | Если объект имеет локальную область памяти, в этом столбце находится буква "Y" |
OWNER | Имя модуля (или приложения), владеющего блоком памяти |
TYPE | Тип объекта (сегмент кода, сегмент данных, ресурс и т. д.) |
С помощью меню "Walk" вы можете выбрать один из трех режимов просмотра глобальной области памяти, выполнить дефрагментацию памяти, запросить удаление удаляемых блоков данных и т. д.
Если в меню "Walk" выбрать строку "Walk Heap", в главном окне утилиты HEAPWALK будет отображаться информация о всех объектах, расположенных в глобальной области памяти.
Для того чтобы просмотреть все удаляемые объекты, выберите из этого меню строку "Walk LRU List".
Те объекты, которые давно не использовались, будут расположены в верхней части списка.
Если выбрать из меню "Walk" строку "GC(0) and Walk", утилита выполнит дефрагментацию глобальной области памяти, запросит блок памяти размером 0 байт и отобразит список объектов.
С помощью строки "GC(-1) and Walk" вы можете предпринять попытку удалить все удаляемые сегменты и просмотреть список объектов.
Остальные строки меню "Walk" описаны в руководстве по утилитам, входящим в состав SDK.
Для удобства отображения вы можете отсортировать блоки памяти в списке при помощи меню "Sort". С помощью этого меню можно выполнить сортировку по адресам блоков памяти (строка "Address" меню "Sort"), по именам модулей, которым принадлежат блоки памяти (строка "Module"), по размеру блоков памяти (строка "Size"), типу объектов (строка "Type"). С помощью строки "Refresh Seg Names" вы можете просмотреть имена сегментов, загруженных в память после запуска утилиты HEAPWALK.
Вы можете выбрать любую строку в списке объектов и просмотреть соответствующий ему блок памяти с помощью меню "Object".
Строка "Show" меню "Object" позволяет просмотреть содержимое блока памяти в виде шестнадцатеричного дампа памяти или ресурса (пиктограммы, изображения bitmap, меню, диалоговой панели и т. д.). На рис. 2.13 в окне "Resource Bitmap" можно увидеть изображение пиктограммы, соответствующей выбранному блоку памяти.
Рис. 2.13. Просмотр пиктограммы
С помощью строки "Discard" можно удалить выбранный объект из памяти.
Объект может быть отмечен как ближайший кандидат на удаление (строка "Oldest") или как объект, который должен быть удален в последнюю очередь (строка "Newest").
Строка "LocalWalk" позволяет вам просмотреть локальную область памяти для выбранного объекта (если у этого объекта есть локальная область памяти). Эта область памяти отображается в отдельном окне (рис. 2.14).
Рис. 2.14. Просмотр локальной области памяти
С помощью меню "Alloc" вы можете заказывать всю свободную память (строка "Allocate All of Memory") и освобождать блоки памяти различного размера (строки "Free All", "Free 1K", ..., "Free 50K", "Free XK").
Временные файлы
Программы MS-DOS иногда создают на диске временные файлы , задавая для них либо фиксированное имя, которое зависит от программы, либо имя, созданное на основе текущей даты и времени.
Первый способ непригоден для приложений Windows, так как в системе могут работать несколько копий одного приложения. Поэтому, если, например, две копии приложения SUPERCAD попытаются создать два временных файла с именем !suprcad.tmp, такая операция получится только у той копии приложения, которая попытается сделать это первой.
В составе программного интерфейса Windows имеется функция GetTempFileName , предназначенная для получения имени временного файла:
int WINAPI GetTempFileName( BYTE bDriveLetter, LPCSTR lpszPrefixString, UINT uUnique, LPSTR lpszTempFileName);
Параметр bDriveLetter задает диск, на котором будет расположен временный файл. Если значение этого параметра равно нулю, временный файл будет создан на текущем диске.
Параметр lpszPrefixString должен содержать указатель на текстовую строку, содержащую префикс, который будет добавлен к имени временного файла. В префиксе необходимо использовать кодировку OEM.
Параметр uUnique должен содержать целое число, которое будет добавлено к префиксу для формирование имени временного файла. Если значение этого параметра равно нулю, Windows будет использовать число, полученное из текущего системного времени.
Подготовленный полный путь для временного файла будет записан в буфер размером не менее 144 байт, адрес которого передается функции через параметр lpszTempFileName. При этом будет использована кодировка OEM.
Функция GetTempFileName возвращает значение, переданное ей через параметр uUnique, или значение, вычисленное исходя из текущего системного времени, если значение параметра uUnique равно нулю.
Учтите, что функция GetTempFileName не создает временные файлы. За создание и удаление временных файлов отвечает само приложение.
Остановимся подробнее на выборе диска для создания временных файлов.
Приложение Windows должно разрешать пользователю назначить каталог, в котором будут расположены временный файлы.
В этом случае пользователь может отвести для всех временных файлов отдельный логический или даже физический диск, что благоприятно скажется на производительности файловой системы.
Для назначения каталога в простейшем случае можно использовать переменную среды TEMP, устанавливаемую в файле autoexec.bat.
Если передать функции GetTempFileName в качестве параметра bDriveLetter нулевое значение, для определения диска, на котором будет расположен временный файл, используется следующий алгоритм.
Если определена переменная среды TEMP, для размещения временного файла используется диск, указанный в этой переменной.
Если переменная TEMP не определена, для размещения временного файла используется первый жесткий диск, обычно C:.
Вы можете в параметре bDriveLetter указать дополнительно константу TF_FORCEDRIVE, определенную в файле windows.h как 0x80. В этом случае для размещения файлов используется указанный в этом параметре диск без учета переменной среды TEMP или буквы, которой обозначается первый установленный в системе жесткий диск.
В программном интерфейсе Windows есть еще одна функция, которая может быть использована для создания временных файлов. Это функция GetTempDrive :
BYTE WINAPI GetTempDrive(char unused);
Параметр функции не используется.
Функция GetTempDrive возвращает имя диска, который можно использовать для создания временных файлов. Если в системе есть жесткие диски, функция возвращает букву, соответствующую первому диску (обычно C:). В противном случае возвращается имя текущего диска.
Таким образом, для приложений Windows можно сформулировать еще одно правило.
Для создания временных файлов используйте имена, полученные от функции GetTempFileName. Не используйте фиксированные имена, закодированные внутри приложений, так как в Windows можно запустить несколько копий одного приложения.
В этой книге мы продолжим
В этой книге мы продолжим изучение программного интерфейса операционной системы Microsoft Windows, начатое в двух предыдущих томах "Библиотеки системного программиста" (том 11 и 12). Как и раньше, мы будем рассматривать базовые понятия Windows, ориентируясь в основном на версию 3.1 этой операционной системы. Полученные вами знания найдут применение и в дальнейшем, когда вы будете создавать приложения для 32-разрядных операционных систем Windows, таких, как Windows 4.0 (проект Chicago) и Windows NT. Особенности этих операционных систем мы рассмотрим позже в следующих томах нашей серии.
Первая глава посвящена созданию меню в приложениях Windows. Практически в любом стандартном приложении Windows есть меню, расположенное под заголовком главного окна приложения, а также системное меню. Операционная система Windows имеет мощную поддержку меню, избавляя программиста от рутинной работы и полностью обеспечивает интерфейс между меню и пользователем.
Для создания меню чаще всего используется шаблон, подготовленный редактором ресурсов Resource Workshop или аналогичным инструментом. Этот шаблон хранится в файле загрузочного модуля приложения вместе с другими ресурсами. Вы также можете создавать меню динамически во время работы приложения, используя специально предназначенные для этого функции программного интерфейса Windows. Мы расскажем вам о различных способах создания меню, а также о том, как подключить к меню клавиатурный интерфейс. Вы сможете создать меню из графических изображений bitmap.
В последнее время практически каждое приложение Windows создает такой орган управления, как Toolbar. Toolbar - это прямоугольное окно с кнопками, расположенное горизонтально или вертикально, причем на кнопках изображены пиктограммы, отражающие их назначение. Так как операционная система Windows версии 3.1 не имеет поддержки органа управления Toolbar, приложения должны создавать его самостоятельно.
В первой главе этого тома мы приведем исходные тексты приложения SMARTPAD, в главном окне которого имеется Toolbar, реализованный как класс C++.
Вы можете использовать этот класс в своих разработках.
Вторая глава - о средствах управления памятью в операционной системе Windows версии 3.1. Управление памятью в Windows намного сложнее, чем в MS-DOS. Вы должны учитывать мультизадачный режим работы операционной системы Windows. Кроме того, все приложения Windows используют защищенный режим работы процессора, существенно отличающийся от реального режима, знакомого вам по MS-DOS. Перед прочтением этой главы мы рекомендуем вам обратиться к тому 6 "Библиотеки системного программиста", который называется "Защищенный режим процессоров Intel 80286/80386/80486". Во второй главе книги, которую вы сейчас читаете, мы рассмотрели только основные вопросы использования защищенного режима. В томе 6 вы найдете более подробное описание.
Во второй главе мы расскажем вам об использовании локальной и глобальной области памяти, об использовании моделей памяти при создании приложений Windows. Вы научитесь работать с блоками памяти большого размера (больше 64 Кбайт). Мы также расскажем о редко используемых, но очень мощных функциях, позволяющих работать с локальной таблицей дескрипторов.
Третья глава посвящена библиотекам динамической загрузки, называемых также DLL-библиотеками. DLL-библиотеки используются практически во всех крупных приложениях Windows. Более того, сама операционная система Windows есть в некотором смысле ни что иное, как набор DLL-библиотек.
Для решения некоторых задач, таких как перехват сообщений перед их поступлением во все приложения, вам не обойтись без создания своих DLL-библиотек. Мы рсскажем вам о структуре DLL-библиотеки, о том, как ее создавать и использовать, а также приведем исходные тексты нескольких приложений, использующих DLL-библиотеки, в том числе исходные тексты приложения, позволяющего вводить в среде Windows с клавиатуры символы кириллицы (очень сильно упрощенный аналог приложений типа CyrWin и ParaWin). Вы научитесь встраивать системные фильтры с помощью функции SetWindowsHookEx.
В четвертой главе мы рассмотрим особенности работы с файлами в среде операционной системы Microsoft Windows версии 3.1. Мультизадачный режим работы накладывает ограничения на то, как ваше приложение пользуется файловой системой. Мы рассмотрим набор функций из программного интерфейса Windows, предназначенных для работы с файлами, в том числе функции _hread и _hwrite, позволяющие прочитать и записать файл очень большого размера за один вызов функции.
Мы также расскажем в этой главе об использовании стандартных диалоговых панелей и соответствующих функций из DLL-библиотеки commdlg.dll. В частности, вы научитесь подключать к стандартным функциям из этой библиотеки разработанные вами шаблоны диалоговых панелей.
Вместе с книгой продается дискета, содержащая исходные тексты всех приложений.
Для трансляции исходных текстов приложений, приведенных в книге, мы пользовались системой разработки Borland Turbo C++ for Windows версии 3.1. Вы можете также работать с Borland C++ версий 3.1 или 4.01, однако для комфортной работы c версией 4.01 требуется как минимум 8 Мбайт оперативной памяти (а лучше 16 Мбайт) и процессор i486. В приложениях, приведенных в этой книге, мы не будем использовать новые возможности системы разработки Borland C++ for Windows версии 4.01 (в этой книге мы описываем программный интерфейс Windows, а не средства разработки), поэтому для ускорения работы мы рекомендуем выбрать Borland Turbo C++ for Windows.
Если же ваш компьютер обладает необходимой производительностью, имеет смысл работать с Borland C++ for Windows версии 4.01. В этом случае вы можете использовать без изменений файлы проектов *.prj, записанные на дискете. Они будут автоматически преобразованы в новые файлы проектов *.ide.
Авторы выражают благодарность сотрудникам АО "Диалог-МИФИ" Виноградовой Елене, Голубеву Олегу Александровичу, Дмитриевой Наталье, Кузьминовой Оксане, Синеву Максиму, Ноженко Сергею, корректору Кустову Виктору, принимавшим активное участие в подготовке этой книги, а также всех остальных томов "Библиотеки системного программиста" и другой нашей серии книг - "Персональный компьютер.Шаг за шагом", адресованной начинающим пользователям персонального компьютера.
Выделение строк
Для выделения строк меню верхнего уровня, расположенных в полосе меню ниже заголовка окна, можно использовать функцию HiliteMenuItem :
BOOL WINAPI HiliteMenuItem(HWND hwnd, HMENU hmenu, UINT idItem, UINT fuHilite);
Параметр hwnd должен содержать идентификатор окна, для которого выполняется операция выделения.
Через параметр hMenu необходимо передать идентификатор соответствующего меню верхнего уровня.
Параметр idItem определяет элемент меню, над которым выполняется операция выделения. Интерпретация этого параметра зависит от значения параметра fuHilite.
Параметр fuHilite может принимать значения MF_HILITE или MF_UNHILITE в комбинации с одним из значений: MF_BYCOMMAND или MF_BYPOSITION.
Для выделения строки меню необходимо использовать значение MF_HILITE. Для отмены выделения строки меню укажите значение MF_UNHILITE.
Если в параметре fuHilite указан флаг MF_BYCOMMAND, параметр idItem определяет идентификатор строки меню, для которого выполняется операция выделения или отмены выделения. Если указан флаг MF_BYPOSITION, параметр idItem определяет порядковый номер этой строки.
Загрузка таблицы акселераторов
Для загрузки таблицы акселераторов следует использовать функцию LoadAccelerators :
HACCEL WINAPI LoadAccelerators(HINSTANCE hInst, LPCSTR lpszTableName);
Параметр hInst определяет идентификатор копии приложения, из ресурсов которого будет загружена таблица акселераторов.
Параметр lpszTableName является указателем на строку, содержащую идентификатор таблицы акселераторов. Если для идентификации ресурса используется целое значение, оно должно быть преобразовано макрокомандой MAKEINTRESOURCE.
Функция LoadAccelerators возвращает идентификатор загруженной таблицы акселераторов или NULL при ошибке.
Загруженная таблица акселераторов автоматически уничтожается при завершении работы приложения.
Приложение SMARTPAD работает с таблицей акселераторов и загружает ее следующим образом:
haccel = LoadAccelerators(hInstance, "APP_ACCELERATORS");
Закрытие файлов
Теперь о том, как закрыть файл. Для закрытия файла вы должны использовать функцию _lclose :
HFILE WINAPI _lclose(HFILE hf);
Идентификатор закрываемого файла передается функции через параметр hf.
Если файл закрыт успешно, функция _lclose возвращает нулевое значение. При ошибке возвращается значение HFILE_ERROR.