::Главная ->Литература ->Руководство по программированию в Windows | |
Содержание
::Новости::F.A.Q. ::Форум ::Компоненты ::Исходники ::Литература ::Рассылка ::Ссылки
Клуб
::Клуб программистов::Члены клуба ::off-форум ::off-чат
Работа
::Есть программисты::Есть вакансии ::Программы на заказ ::Готовые программы
Другое
::О сайте::Голосование ::Модератору |
Оглавление Windows 3.0/pg/3#3 = 1 = Глава 16. Еще об управлении памятью............................5 16.1 Конфигурации памяти Windows..............................5 16.1.1 Базовая конфигурация памяти............................6 16.1.2 Конфигурация памяти EMS 4.0............................9 16.1.3 Стандартная конфигурация памяти Windows...............15 16.1.4 Конфигурация памяти Windows в расширенном режиме....20 16.2 Использование памяти в прикладных программах Windows......................................................24 16.2.1 Управление автоматическими сегментами данных..........26 16.2.2 Управление блоками локальной динамической области....28 16.2.3 Управление блоками глоабльной динамической области....33 16.2.4 Использование дополнительных байт Окна и класса.......41 16.2.5 Управление ресурсами..................................43 16.3 Использование моделей памяти............................46 16.4 Использование данных типа huge..........................47 16.5. Чeго следует избегать при работе с данными..............48 16.6 Управление памятью для кода программы...................51 16.6.1 Использование атрибутов кодовых сегментов.............51 16.6.2 Использование нескольких кодовых сегментов............52 16.6.3 Балансирование кодовых сегментов......................53 16.6.4 Порядок кодовых сегментов в файле определения....53 16.7 Заключение..............................................54 Глава 17. Параметры принтера.................................55 17.1 Как Windows управляет параметрами принтеров.............55 17.1.1. Параметры принтера и структура DEVMODE................56 17.1.2. Параметры принтера и среда принтера...................57 17.2 Использование функций драйверов устройств...............58 17.3 Получение характеристик драйвера принтера...............60 17.4 Работа с параметрами принтера...........................60 17.4.1 Определение ввода и вывода функции ExtDeviceMode......61 17.4.2 Получение копии параметров принтера...................62 17.4.3 Изменение параметров принтера.........................63 17.4.4 Приспособление параметров принтера для..............64 17.4.5 Изменение параметров принтера без влияния на другие....66 17.4.6 Запрос у пользователя изменения параметров принтера.....................................................67 17.5 Копирование параметров принтера между драйверами........69 17.6 Поддержка собственных параметров принтера...............69 17.7 Работа со старыми драйверами принтеров..................70 17.8 Заключение..............................................70 Глава 18. Шрифты.............................................72 18.1 Запись текста...........................................72 18.2 Использование цвета при записи текста...................72 18.3 Использование заказных шрифтов..........................73 18.4 Создание логического шрифта.............................74 18.5 Использование нескольких шрифтов в одной строке.........76 18.6 Получение информации о выбранном шрифте.................77 18.7 Получение информации о логическом шрифте................78 18.8 Перечисление шрифтов....................................80 18.9 Проверка текстовых возможностей устройства..............81 18.10 Добавление ресурса шрифта..............................83 18.11 Установка выравнивания текста..........................84 18.12 Создание ресурса шрифта................................84 . Windows 3.0/pg/3#3 = 2 = 18.12.1 Создание файлов шрифтов..............................85 18.12.2 Создание описания ресурса шрифта.....................85 18.12.3 Создание шаблона программного модуля.................86 18.12.4 Создание файла определения модуля....................86 18.12.5 Трансляция и компоновка файла ресурса шрифта.........87 18.13 Пример прикладной программы ShowFont...................88 18.14 Заключение.............................................88 Глава 19. Палитры цветов.....................................89 19.1 Что делает палитра цветов...............................89 19.2 Как работают палитры цветов.............................90 19.3 Создание и использование логической палитры.............91 19.3.1 Создание структуры данных LOGPALETTE..................91 19.3.2 Создание логической палитры...........................94 19.3.3 Выбор палитры в контексте устройства..................94 19.3.4 Реализация палитры....................................95 19.4 Рисование с использованием цветов палитры...............95 19.4.1 Прямое определение цветов таблицы.....................95 19.4.2 Непрямое определение цветов палитры...................96 19.4.3 Использование палитры при выводе растровых карт.......98 19.5 Изменение логической палитры............................99 19.6 Реакция на изменения системной палитры.................101 19.6.1 Обработка сообщения WM_QUERYNEWPALETTE...............101 19.6.2 Реакция на сообщение WM_PALETTECHANGED...............102 19.7 Заключение.............................................104 Глава 20. Динамически подключаемые библиотеки...............105 20.1 Что такое DLL..........................................105 20.1.1 Импортируемые библиотеки и DLL.......................106 20.1.2 Модули DLL и прикладной программы....................108 20.1.3 DLL и задачи.........................................109 20.1.4 DLL и стеки..........................................110 20.1.5 Как Windows ищет DLL.................................111 20.2 Когда использовать собственные DLL.....................111 20.2.1 Разделение между прикладными программами.............112 20.2.2 Модификация программ для различных рынков............113 20.2.3 Ловушки окон.........................................114 20.2.4 Драйверы устройств...................................115 20.2.5 Собственные блоки управления.........................116 20.2.6 Управление проектами.................................125 20.3 Создание DLL...........................................125 20.3.1 Создание файла с исходным кодом на С.................126 20.3.2 Создание файла определения модуля....................132 20.3.3. Создание Make-файла.................................134 20.4 Доступ к DLL из прикладных программ....................138 20.4.1 Создание прототипа библиотечных функций..............138 20.4.2 Импортирование библиотечных функций..................139 20.5 Правила владения для объектов Windows..................142 20.6 Пример библиотеки: Select..............................143 20.6.1 Создание функций.....................................144 20.6.2 Создание функции инициализации.......................149 20.6.3 Создание процедуры завершения........................149 20.6.4 Создание файла определения модуля....................150 20.6.5 Создание включаемого файла...........................150 . Windows 3.0/pg/3#3 = 3 = 20.6.6 Трансляция и компоновка..............................150 20.7 Заключение.............................................151 Глава 21. Интерфейс множества документов.....................152 21.1 Структура прикладной программы, использующей MDI.......152 21.2 Инициализация прикладной программы, использующей MDI.........................................................153 21.2.1 Регистрация классов окон.............................153 21.2.2 Создание окон........................................154 21.3 Создание цикла обработки сообщений.....................155 21.4 Создание функции обработки сообщений окна фрейма.......156 21.5 Создание функции обработки сообщений для дочернего...157 21.6 Связывание данных с дочерними окнами...................157 21.6.1 Хранение данных в структуре окна.....................158 21.6.2 Использование свойств окна...........................158 21.7 Управление дочерними окнами............................158 21.7.1 Создание дочерних окон...............................159 21.7.2 Разрушение дочерних окон.............................160 21.7.3 Активизация и деактивизация дочерних окон............160 21.7.4 Расстановка дочерних окон на экране..................161 21.8 Заключение.............................................161 Глава 22. Динамический обмен данными.........................163 22.1 Обмен данными в Windows................................163 22.1.1 Передача данных через системный буфер................163 22.1.2 Динамически подключаемые библиотеки..................164 22.1.3 Динамический обмен данными...........................164 22.1.4 Когда использовать Windows DDE.......................165 22.1.5 DDE с точки зрения пользователя......................165 22.2 Концепции DDE..........................................166 22.2.1 Клиент, сервер и диалог..............................166 22.2.2 Элемент, предмет, приложение.........................167 22.2.3 Постоянная ("горячая" и "теплая") связь по данным....167 22.3 Сообщения DDE..........................................168 22.4 Поток сообщений DDE....................................169 22.4.1 Инициализация диалога................................169 22.4.2 Передача одного элемента.............................171 22.4.3 Установление постоянной связи по данным..............176 22.4.4 Запуск команд в удаленной прикладной программе.......181 22.4.5 Завершение диалога...................................184 22.5 Примеры программ Client и Server.......................186 22.6 Заключение.............................................187 . Windows 3.0/pg/3#3 = 4 = ---------------------------------------------------------------- ПРОГРАММА-СПРАВОЧНИК ПО Microsoft Windows Версия 3.0 Руководство по програмированию в среде Microsoft Windows 3#3 Москва 1991 г. ---------------------------------------------------------------- . Windows 3.0/pg/3#3 = 5 = Глава 16. Еще об управлении памятью. ---------------------------------------------------------------- В главе 15, "Управление памятью", представлена основная информация, которая вам необходима для понимания того, как используется память в прикладных программах Windows. Однако, в некоторых прикладных программах требуется более сложное управление памятью. В данной главе содержится более детальная информация о технике управления памятью Windows и о том, как вы должны писать программы с целью лучшего использования сложных возможностей Windows по управлению памятью. В данной главе содержится информация по следующим вопросам: - Конфигурации памяти Windows. - Использование памяти для хранения данных в прикладных программах Windows. - Использование моделей памяти. - Использование данных типа HUGE. - Управление данными программы. - Управление данными для кода программ. 16.1 Конфигурации памяти Windows. Вы должны знать, что ваша прикладная программа может работать под различными конфигурациями памяти. Чаще всего конфигурация памяти зависит от типа центрального процессора и объема и конфигурации памяти. Windows поддерживает четыре конфигурации: - Базовая (640К) конфигурация памяти (реальный режим Windows). - Конфигурация расширенной памяти по спецификации Lotus-Intel-Microsoft версии 4.0 (LIM EMS 4.0). - Конфигурация памяти стандартного режима. - Конфигурация памяти расширенного режима процессора 386. Объем памяти, используемый Windows, будет меньше установленного в системе, если перед запуском Windows вы запустите другие программы. Поскольку Windows в различных системах использует различные конфигурации памяти, ваша программа должна работать при любой конфигурации. Лучшим способом для этого является следование правилам управления памяти Windows при написании . Windows 3.0/pg/3#3 = 6 = ваших программ. Смотрите раздел 16.5, "Ловушки, которые необходимо избегать при работе с данными программы", в котором приведены эти правила. Следует избегать при возможности кода, который зависит от конфигурации памяти. Однако в некоторых случаях программа должна иметь возможность определить конфигурацию, под которой она работает. Для определения текущей конфигурации памяти можно воспользоваться функцией GetWunFlags. Эта функция возвращает 32-разрядное число, которое содержит биты, определяющие текущую конфигурацию памяти, а также другую информацию о системе пользователя. Оставшаяся часть этого раздела посвящена описанию четырех основных конфигураций памяти Windows. 16.1.1 Базовая конфигурация памяти. Базовая конфигурация памяти Windows подразумевает, что система имеет 640К физической памяти. Перед запуском Windows младшие адреса памяти уже заняты системной BIOS и DOS. Эта часть содержит следующее: - Таблицу векторов прерываний. - Область данных BIOS. - Драйверы устройств DOS. - Любые резидентные программы, которые были запущены перед Windows. Windows и прикладные программы, работающие под Windows, могут использовать только оставшуюся часть памяти. Эта память называется глобальной динамической областью памяти. . Windows 3.0/pg/3#3 = 7 = На рисунке 16.1 показана базовая конфигурация памяти Windows. -----------------------------------------¬ \ A000H (640K) ¦ Сбрасываемые кодовые сегменты ¦ ¦ ¦ ¦ ¦ ¦ ¦ V ¦ ¦ ¦ ¦ ¦ ¦ A ¦ ¦ ¦ ¦ ¦ ¦ Глобальная ¦ Перемещаемые сегменты (кода и данных) ¦ \ динамическая ¦ и ¦ / область данных ¦ сбрасываемые сегменты данных ¦ ¦ ¦ ¦ ¦ ¦ A ¦ ¦ ¦ ¦ ¦ ¦ ¦ Фиксированные сегменты (кода и ¦ ¦ ¦ данных) ¦ ¦ +----------------------------------------+ / ¦ Резидентные программы ¦ ¦ Драйверы устройств ¦ ¦ MS-DOS ¦ ¦ Область данных BIOS ¦ ¦ Таблица векторов прерываний ¦ L----------------------------------------- 0000H Рисунок 16.1 Базовая конфигурация памяти Windows. В главе 15, "Управление памятью", описано, как прикладные программы Windows используют глобальную динамическую область памяти. Сегменты кода и данных всех прикладных программ располагаются в глобальной динамической области памяти, местоположение сегмента зависит от типа сегмента, т.е. является ли он фиксированным, перемещаемым и сбрасываемым, или только перемещаемым. В таблице 16.1 приведен список атрибутов и типов и показано, где они располагаются в глобальной области памяти. . Windows 3.0/pg/3#3 = 8 = Таблица 16.1 Позиции сегментов в глобальной динасической области памяти. --------------------------------------------------------------- Атрибут Тип сегмента Положение в глобальной об- ласти памяти. --------------------------------------------------------------- Фиксированный Код или данные Младшие адреса глобальной области памяти. Сбрасываемый Кодовый Старшие адреса глобальной области памяти. Сбрасываемый Данные Младшая часть глобальной области памяти, но выше фиксированных сегментов. Перемещаемые Код и данные Выше фиксированных сегмен- (но не сбрасы- тов. ваемые) --------------------------------------------------------------- Если прикладная программа выделяет фиксированный сегмент после выделения перемещаемых или сбрасываемых сегментов, Windows пытается переместить перемещаемые сегменты и сбросить сбрасываемые для того, чтобы поместить фиксированные сегмент как можно ниже. Этот процесс помогает уменьшить фрагментацию памяти, но требует затрат времени центрального процессора. Поэтому следует избегать выделения фиксированных сегментов. В базовой конфигурации памяти, если некоторые сегменты определены в файле определения модуля вашей прикладной программы как сбрасываемые, программа может быть больше, чем размер памяти в глобальной области памяти. Если физическая память заполнена и прикладная программа пытается вызвать кодовый сегмент, которого нет в физической памяти, Windows загружает его, предварительно удалив сбрасываемые кодовые сегменты или сегменты данных. Удаление сбрасываемых кодовых сегментов из памяти не отражается на прикладной программе, если сброшенный сегмент понадобится, он будет загружен с диска. С другой стороны, удаление сегментов данных может привести к их потере. Поэтому, если вы объявляете в программе сбрасываемый сегмент данных, вы должны быть уверены, что сможете перезагрузить его содержимое с диска или снова создать любым другим способом. В базовой конфигурации памяти Windows предоставляет больше памяти, удаляя сбрасываемые сегменты из памяти. Windows не выполняет подкачку с диска (подкачку сегментов данных и кода с диска и обратно). Если вашей прикладной программе требуется больший объем виртуальной памяти, чем доступно в базовой конфигурации, она должна сама управлять виртуальной памятью, подкачивая данные с диска и обратно. . Windows 3.0/pg/3#3 = 9 = 16.1.2 Конфигурация памяти EMS 4.0. Windows может использовать конфигурацию памяти EMS 4.0, если в системе установлена расширенная память EMS и имеется драйвер EMS 4.0. В этой конфигурации размер глобальной динамической области памяти больше с точки зрения прикладной программы, чем доступно в базовой конфигурации. Физическое адресное пространство глобальной динамической области памяти расширяется сверх адреса A000H (640K) до F000H. Физические адреса с F000H по FFFFH зарезервированы под BIOS. Некоторые части пространства от A000H по F000H зарезервированы под видеопамять и для адаптеров сети. Таким образом в области между A000H и F000H Windows доступно меньше 320К. Максимально это может быть 288К, но обычно меньше. На рисунке 16.2 сравнивается базовая конфигурация памяти и конфигурация EMS 4.0. . Windows 3.0/pg/3#3 = 10 = Базовая конфигурация. -----------------------------------------¬ \ A000H (640K) ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ Глобальная ¦ ¦ \ динамическая ¦ ¦ / область данных ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ +----------------------------------------+ / ¦ Резидентные программы ¦ ¦ Драйверы устройств ¦ ¦ MS-DOS ¦ ¦ Область данных BIOS ¦ ¦ Таблица векторов прерываний ¦ L----------------------------------------- 0000H Конфигурация EMS 4.0. -----------------------------------------¬ FFFFH ¦ Системное ПЗУ ¦ +----------------------------------------+ ¦ Доступная память ¦ \ +----------------------------------------+ ¦ ¦ Видеопамять ¦ ¦ +----------------------------------------+ ¦ ¦ Доступная память ¦ ¦ +----------------------------------------+ ¦ A000H (640K) ¦ ¦ ¦ ¦ ¦ ¦ Глобальная ¦ ¦ \ динамическая ¦ ¦ / область данных ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ +----------------------------------------+ / ¦ Резидентные программы ¦ ¦ Драйверы устройств ¦ ¦ MS-DOS ¦ ¦ Область данных BIOS ¦ ¦ Таблица векторов прерываний ¦ L----------------------------------------- 0000H Рисунок 16.2 Сравнение базовой конфигурации памяти и конфигурации с EMS 4.0. . Windows 3.0/pg/3#3 = 11 = Память EMS и отображение банков памяти. При использовании конфигурации EMS 4.0 Windows предоставляет прикладной программе больше памяти, чем при работе в базовой конфигурации. Это делается путем отображения объектов из расширенной памяти на некоторую область физической памяти в глобальной динамической области памяти. Отображение возникает, когда Windows изменяет определенные регистры EMS для изменения отображения некоторой области памяти из младшего 1М адресного пространства на некоторую область в расширенной памяти. Установка регистров EMS намного быстрее, чем действительное копирование данных. Рисунок 16.3 иллюстрирует то, как Windows выполняет отображение реального адресного пространства на расширенную память. -----------------------------¬ FFFFH -----------------------------¬ ¦ Системное ПЗУ ¦ ¦ . ¦ +----------------------------+ F000H ¦ Прикладная программа 1 А ¦ ¦ Отображаемая память . ¦ ¦ . ¦ ¦ Windows А ¦ +----------------------------+ ¦ . ¦ ¦ . ¦ +----------------------------+ ¦ ¦ ¦ ¦ Видеопамять ¦ ¦ Прикладная программа 1 В ¦ +----------------------------+ ¦ ¦ ¦ ¦ Отображаемая память . ¦ ¦ . ¦ ¦ Windows ¦ ¦ +----------------------------+ ¦ В ¦ ¦ . ¦ ¦ ¦ ¦ ¦ Прикладная программа 1 А ¦ ¦ . ¦ ¦ . ¦ +- - - - - - - - - - - - - - + +----------------------------+ ¦ Граница отображения EMS ¦ ¦ . ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ Прикладная программа 1 В ¦ ¦ Неотображаемая память ¦ ¦ ¦ ¦ ¦ Windows ¦ ¦ . ¦ ¦ ¦ +----------------------------+ ¦ ¦ ¦ . ¦ ¦ ¦ ¦ Прикладная программа 1 А ¦ ¦ ¦ ¦ . ¦ ¦ ¦ +----------------------------+ ¦ ¦ ¦ . ¦ +----------------------------+ ¦ ¦ ¦ ¦ Резидентные программы ¦ ¦ Прикладная программа 1 В ¦ ¦ Драйверы устройств ¦ ¦ ¦ ¦ ¦ MS-DOS ¦ ¦ . ¦ ¦ Область данных BIOS ¦ +----------------------------+ ¦Таблица векторов прерываний ¦ L----------------------------- 0000H ¦ ¦ ¦ ¦ Рисунок 16.3 Отображение физического адресного пространства на расширенную память. . Windows 3.0/pg/3#3 = 12 = Windows производит отображение кода и данных между глобальной динамической областью данных и расширенной памятью во время "переключения контекста задачи", которое выполняется при передаче управления от одной прикладной программы другой. Windows переключает отображение кода и данных одной прикладной программы из расширенной памяти в глобальную динамическую область данных на отображение кода и данных другой прикладной программы. Отображение имеет место только при переключении контекстов задач. Отображение выполняется только для всей прикладной программы, а не для отдельных блоков памяти. В результате общий объем памяти доступный прикладной программе увеличивается не на общий объем расширенной памяти, а только на доступный объем в диапазоне от A000H до F000H. Это увеличение, однако, может значительно повысить характеристики программы. Также объем доступной программе памяти зависит от того, сколько прикладных программ отображаются на расширенную память, и таким образом зависит от того, сколько программ выполняются одновременно. Отображаемая и неотображаемая память. При отображении кода и данных между глобальной динамической областью памяти и расширенной памятью Windows отображает только некоторые типы объектов. Такой отображаемый код и данные должны находиться выше границы отображения EMS. Отображаемая информация включает следующее: - Перемещаемый или фиксированный код задачи (прикладной программы). - Ресурсы задачи (ресурсы, добавляемые к выполняемому файлу компилятором ресурсов RC). - Сегменты кода и данных приватных библиотек. Библиотека объявляется как приватная, когда она компилируется с указанием ключа -p компилятором ресурсов. - Заголовки модуля здачи (.EXE) в Windows 3.0 и более поздних. Некоторые типы объектов не могут отображаться. Например, Windows не может отображать данные, которые всегда должны быть . Windows 3.0/pg/3#3 = 13 = доступны для Windows или для DLL. О таких неотображаемых объектах мы говорим, что они находятся ниже границы отображения. К неотображаемой информации относится следующая: - База данных задач (данные о задачах, которые используются Windows для управления задачами). - Сегменты данных библиотек. - Фиксированные сегменты кода библиотек. - Заголовки модулей библиотек (.DLL). Остальные типы кода и данных могут располагаться как выше, так и ниже границы отображения, в зависимости от того, куда Windows поместит эту границу. Местонахождение границы отображения зависит от размера глобальной динамической области памяти. Если размер динамической области памяти меньше, чем обычный размер, то граница отображения EMS устанавливается по адресу A000H (640K), и в результате получается небольшой размер области отображения. В этом случае говорят, что Windows работает в режиме EMS с небольшим фреймом отображения. В таком режиме неотображаемыми становятся следующие категории кода и данных (т.е. они помещаются ниже границы отображения): - Ресурсы библиотек. - Сбрасываемые сегменты кода DLL. - Сегменты данных задач. - Блоки глобальной динамической памяти, выделенные задачами. - Заголовки всех модулей. Если доступно достаточно памяти, то Windows может установить границу отображения таким образом, что получится относительно большой размер отображаемой памяти. В этом случае говорят, что Windows работает в режиме EMS с большим фреймом отображения. Граница отображения в таком режиме может быть установлена выше A000H, если выше A000H доступно больше памяти, чем ниже A000H. В таблице 16.2 обобщается информация о том, выше или ниже границы отображения Windows помещает различные категории кода и данных в режиме EMS с большим и малым фреймом отображения. . Windows 3.0/pg/3#3 = 14 = Таблица 16.2 Использование расширенной памяти. --------------------------------------------------------------- Объект Выше или ниже границы отображения Фрейм Небольшой Большой --------------------------------------------------------------- База данных задач Ниже Ниже Сегмент данных библиотеки Ниже Ниже Сегмент кода библиотеки (фиксированный) Ниже Ниже Ресурсы библиотеки Ниже Выше Сегмент кода задачи Выше/Ниже Выше Ресурсы задачи Выше/Ниже Выше Сегмент кода задачи Ниже Выше Сегмент кода библиотеки (сбрасываемый) Ниже Выше Заголовок модуля задачи (.EXE) Ниже Выше Заголовок модуля библиотеки (.DLL) Ниже Ниже Динамически выделяемая память Ниже Выше --------------------------------------------------------------- Если в режиме с небольшим фреймом выше границы сегмента не хватает памяти для размещения всего объема кода и данных, которые можно отобразить, то оставшиеся сегменты размещаются ниже границы отображения. То, как Windows загружает объекты DLL при работе в режиме с большим фреймом отображения, можно изменить с помощью ключа -p компилятора ресурсов при компиляции библиотеки. Смотрите главу 20, "Динамически подключаемые библиотеки". Работа напрямую с расширенной памятью. Поскольку Windows автоматически выполняет отображение, то ваша программа не должна иметь дела с расширенной памятью. Однако при желании вы можете работать с расширенной памятью напрямую, через фрейм страниц размером 64К. Прямое использование расширенной памяти предоставляет вашей программе большее адресное пространство аналогично оверлеям DOS, но работает быстрее. Для этого: - Вы должны компилировать вашу программу с ключем -I компилятора ресурсов. - Ваша прикладная программа должна следовать спецификациям LIM 3.2. - Ваша программа не должна использовать функций, специфичных для EMS 4.0 (за исключением функции 17). . Windows 3.0/pg/3#3 = 15 = Использование этих функций может привести к конфликту с системой управления памятью Windows. Когда прикладная программа выделяет область в расширенной памяти, она конкурирует с другими прикладными программами. Таким образом, вы не должны выделять всю расширенную память для своей программы. 16.1.3 Стандартная конфигурация памяти Windows. Windows по умолчанию использует стандартную конфигурацию памяти при работе на системе, удовлетворяющей следующим требованиям: - Машина на основе процессора 80286, память не меньше 1Мб. - Машина на основе процессора 80386, память не меньше 1Мб но не больше 2Мб. На системах с процессорам 80386, имеющих больше 2Мб памяти, Windows использует конфигурацию памяти расширенного режима процеессора 80386. Эта конфигурация памяти описана в разделе 16.1.4, "Конфигурация памяти расширенного режима процессора 80386". Когда Windows работает в стандартном режиме, глобальная динамическая область памяти делится по крайней мере на две, а обычно на три части, в зависимости от доступного объема памяти. Первая часть, которую Windows использует для глобальной динамической области памяти, - это стандартная память как и в базовой конфигурации. Она начинается сразу за резидентными программами, драйверами устройств, DOS и т.п., и располагается до конца стандартной памяти, которая обычно имеет размер 640К, но на некоторых системах ее может быть меньше. Второй блок - это расширенная память. Windows выделяет блок расширенной памяти с помощью драйвера расширенной памяти XMS и затем использует память напрямую, без обращения к драйверу. Размер этого блока может изменяться в зависимости от того, что пользователь загрузил в расширенную память перед запуском Windows. Windows добавляет третий блок к глобальной динамической области памяти, если он доступен. Это область старших адресов памяти (HMA) и она доступна через драйвер XMS. Этот блок не доступен, если пользователь загрузил программы в HMA. Однако в настоящее время не так много программ, которые могут загружаться в HMA, поэтому этот блок может быть доступен для Windows в стандартной конфигурации памяти. Windows соединяет два или три блока в глобальную динамическую область памяти. Начало (низ) стандартной памяти (640К) является началом (низом) глобальной динамической области . Windows 3.0/pg/3#3 = 16 = памяти. Вершина блока расширенной памяти (старшие адреса) является вершиной глобальной динамической области памяти. Если доступна HMA, то этот блок вставляется между двумя другими. . Windows 3.0/pg/3#3 = 17 = На рисунке 16.4 показана стандартная конфигурация памяти Windows. -----------------------------------¬ Вершина расширенной ¦ Сбрасываемые кодовые сегменты ¦ памяти ¦ ¦ ¦ ¦ . ¦ +----------------------------------+ Выше или равно 11000H ¦ ¦ ¦ ¦ Необязательная память ¦ ¦ HMA +----------------------------------+ 10000H . . . . +----------------------------------+ A000H (640K) ¦ . ¦ ¦ ¦ ¦ ¦ Перемещаемые сегменты (кода и ¦ ¦ данных) и сбрасываемые сегменты ¦ ¦ данных. ¦ ¦ . ¦ ¦ ¦ ¦ ¦ Фиксированные сегменты ¦ +----------------------------------+ ¦ Резидентные программы ¦ ¦ Драйверы устройств ¦ ¦ MS-DOS ¦ ¦ Область данных BIOS ¦ ¦ Таблица векторов прерываний ¦ L----------------------------------- 0000H Как и в других конфигурациях памяти Windows выделяет сбрасываемые кодовые сегменты в верхней части глобальной динамической области памяти, а фиксированные сегменты в младшей части, над которыми располагаются перемещаемые кодовые сегменты и сегменты данных. Некоторые элементы данных Windows должны располагаться в стандартной памяти (640К), поскольку необходим к ним доступ при работе процессора в реальном режиме и не в защищенном. Это префиксы сегментов программ и очереди данных для взаимодействия с последовательным портом. Использование больших блоков памяти при работе в стандартной конфигурации памяти. В стандартной конфигурации памяти Windows работает в защищенном режиме процессоров 80286 или 80386. В реальном режиме дальний адрес создается с помощью 16-разрядного адреса сегмента и 16-разрядного смещения. Адрес сегмента указывает параграф, который представляет собой блок размером 16 байт в . Windows 3.0/pg/3#3 = 18 = адресном пространстве размером 1Мб. Смещение представляет собой адрес в диапазоне от 0 до 64К относительно параграфа, на который указывает адрес сегмента. В стандартном режиме, 16-битовый адрес сегмента представляет собой селектор, аналогичный дескриптору в Windows. Этот дескриптор определяет элемент локальной или глобальной таблицы дескрипторов (LDT или GDT). Элементы этих таблиц определяют, находятся ли в памяти сегменты, на которые ссылается селектор. Если сегменты находятся в памяти, тогда элемент таблицы определяет линейный адрес сегмента. Если вы выделяете большой блок памяти (размером больше 64К), Microsoft C Compiler генерирует код для большого (huge) указателя, выполняющего специальные вычисления, которые позволяют переместить указатель через границу сегмента (64К). Однако он делает это только если указатель явно объявлен как huge или если модуль компилируется с использованием модели памяти huge. Не изменяйте напрямую в дальнем указателе адрес сегмента. Попытка изменения сегментного адреса приведет лишь к появлению неверного селектора. Когда затем такой селектор используется для чтения или записи данных, Windows сообщит о возникновении неустранимой ошибки (GP), или, что тоже возможно, неверный селектор может указывать на другие данные или код. Если вы пишете программу на ассемблере, можно для увеличения дальнего указателя использовать глобальную переменную __ahiner, которая определена в MACROS.INC. Во время загрузки Windows в реальном режиме присваивает этой переменной значение 1000Н, таким образом добавление ее к сегментной части дальнего указателя продвинет дальний указатель на 1000Н параграфов. В стандартном режиме Windows заносит в __ahiner корректное значение, на которое можно увеличивать селектор. Это возможно, поскольку при выделении больших блоков памяти при добавлении 1000Н, Windows выделяет зависимый селектор на зависимый блок памяти в 64К. Это называется "селекторы встык". Ниже приведен пример того, как можно увеличить дальний указатель на 64К: extern __ahincr:abs . . . mov ax,es ; es - адрес сегмента, который вы хотите ; увеличить add ax,__ahincr mov es,ax Более того, в стандартной конфигурации памяти вы можете выделить блок размером до 1Мб. В стандартном режиме все части прикладной программы (код и данные) нормально перемещаемы в линейной памяти. . Windows 3.0/pg/3#3 = 19 = Использование глобальных селекторов. Для выполнения ввода и вывода, отображаемого на память, вы можете использовать следующие константы глобальных селекторов для доступа к соответствующим областям памяти в программах на ассемблере: - __A000H - __B000H - __B800H - __C000H - __D000H - __E000H - __F000H Данный пример иллюстрирует как правильно использовать эти селекторы: mov ax,__A000H mov es,ax Используйте данные селекторы только для поддержки драйверов оборудования, выполняющего ввод/вывод, отображаемый на память. Совмещение кодовых сегментов и сегментов данных. Обычно, вы не можете запустить код, находящийся в сегменте данных. В стандартном режиме такая попытка приведет к появлению неустранимой ошибки нарушения границы. Однако, в редких случаях это может быть необходимо, и это можно выполнить путем совмещения сегмента данных по вопросу. Совмещение приводит к копированию селектора сегмента и изменению поля TYPE таким образом, чтобы с этим сегментом можно было выполнять операции, которые были бы невозможны в обычном случае. Windows предоставляет для совмещения сегментов две функции: - AllocDStoCSAlias - ChangeSelector AllocDStoCSAlias получает селектор сегмента данных и возвращает селектор кодового сегмента. Это позволяет вам . Windows 3.0/pg/3#3 = 20 = заносить команды в стек данных, создать совмещение для сегмента стека и затем выполнить код в стеке. Эта функция выделяет новый селектор. После того, как вам уже не нужен селектор, полученный с помощью AllocDStoCSAlias, вы должны его освободить с помощью функции FreeSelector. Вы должны быть осторожны и не использовать селектор, возвращаемый функцией AllocDStoAlias, если имеется вероятность того, что он был перемещен. Имеется только одна возможность предотвратить перемещение сегмента перед его вызовом, с помощью функции GlobalFix, которая фиксирует сегмент в линейном адресном пространстве. Вы должны быть также уверены в том, что сегмент не будет перемещен, ваша программа потеряет управление, и что ваша программа не выполняет действий по выделению памяти. Обычно это требование приводит к тому, что вашей программе придется получать селектор при каждом обращении на выделение памяти или после каждого случая потери управления. Однако вы можете избавиться от необходимости такого частого выделения и освобождения селектора, если воспользуетесь временным селектором. Функция ChangeSelector предоставляет вам удобный способ совмещения с временным селектором. Она получает два селектора: временный селектор и селектор, который вы хотите совместить. Чтобы этот селектор постоянно совмещался, вы должны сделать следующее: 1. Вызвать AllocateSelector для создания временного селектора. 2. Когда необходимо, вызвать функцию ChangeSelector, передав ей временный селектор и селектор, который вы хотите совместить. Поскольку вы уже выделили селектор, то и освобождать его перед вызовом функции ChangeSelector не нужно. Вам лишь нужно вызывать функцию ChangeSelector во всех случаях, в которых необходимо совместить сегменты, если существует вероятность того, что они были перемещены. 3. Если вам больше не нужно совмещать сегменты, вы должны освободить временный селектор с помощью функции FreeSelector. 16.1.4 Конфигурация памяти Windows в расширенном режиме процессора 80386. Если в пользовательской системе имеется по крайней мере 2Мб доступной расширенной памяти и процессор 80386, то Windows и прикладные программы Windows работают в расширенном режиме процессора 80386. Это вид защищенного режима. В этом режиме . Windows 3.0/pg/3#3 = 21 = Windows, используя специальные средства процессора 80386, реализует систему управления виртуальной памятью, используя подкачку с диска. В результате объем доступной прикладной программе памяти может в несколько раз превышать объем доступной расширенной памяти в системе. Теоретически в этом режиме Windows позволяет адресовать до 4 гигабайт памяти, однако в реальности она ограничена объемом доступной оперативной памяти и памяти на диске. Максимально Windows может выделить в расширенном режиме 80386 объект размером 64Мб. В расширенном режиме процессора 80386 все сегменты (кода и данных) перемещаемы, так же как и листаемые (страничной организации). Примечание: Поскольку в ресширенном режиме 80386 использует возможности защищенного режима, то ограничения по использованию памяти стандартного режима также применимы к расширенному режиму. Ниже описана конфигурация памяти в расширенном режиме процессора 80386: - Глобальная динамическая область памяти представляет собой одно большое виртуальное адресное пространство. В отличие от конфигурации памяти с EMS 4.0, Windows не отображает код и данные между адресным пространством в 1 Мб и вторичной памятью. Вместо этого все прикладные программы разделяют одно и то же виртуальное адресное пространство. - Объем глобального виртуального адресного пространства не ограничен объемом расширенной памяти. Диск используется в качестве вторичной памяти, расширяющей виртуальное адресное пространство. Конфигурация памяти в расширенном режиме процессора больше напоминает базовую конфигурацию памяти, чем конфигурацию с EMS 4.0, или стандартную конфигурацию. На рисунке 16.5 стравнивается базовая конфигурация и конфигурация в расширенном режиме процессора 80386. Базовая конфигурация памяти. -----------------------------------------¬ \ A000H (640K) ¦ Сбрасываемые кодовые сегменты ¦ ¦ ¦ ¦ ¦ ¦ ¦ . ¦ ¦ ¦ ¦ ¦ ¦ . ¦ ¦ ¦ ¦ ¦ ¦ Глобальная ¦ Перемещаемые сегменты (кода и данных) ¦ \ динамическая ¦ и ¦ / область данных ¦ сбрасываемые сегменты данных ¦ ¦ ¦ ¦ ¦ ¦ . ¦ ¦ ¦ ¦ ¦ ¦ ¦ Фиксированные сегменты (кода и ¦ ¦ ¦ данных) ¦ ¦ +----------------------------------------+ / ¦ Резидентные программы ¦ ¦ Драйверы устройств ¦ ¦ MS-DOS ¦ ¦ Область данных BIOS ¦ ¦ Таблица векторов прерываний ¦ L----------------------------------------- 0000H . Windows 3.0/pg/3#3 = 22 = Конфигурация памяти в расширенном режиме процессора 80386. -----------------------------------------¬ \ Вершина ¦ Сбрасываемые кодовые сегменты ¦ ¦ расширенной ¦ ¦ ¦ ¦ памяти ¦ . ¦ ¦ . . ¦ . . ¦ . . ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ Глобальная ¦ ¦ \ динамическая ¦ ¦ / область данных ¦ ¦ ¦ в виртуальном ¦ ¦ ¦ адресном прост- ¦ . ¦ ¦ ранстве ¦ ¦ ¦ ¦ ¦ Перемещаемые сегменты (кода и данных) ¦ ¦ ¦ и ¦ ¦ ¦ сбрасываемые сегменты данных ¦ ¦ ¦ . ¦ ¦ ¦ ¦ ¦ ¦ ¦ Фиксированные сегменты (кода и ¦ ¦ ¦ данных) ¦ ¦ +----------------------------------------+ / . . . . . . +----------------------------------------+ ¦ Резидентные программы ¦ ¦ Драйверы устройств ¦ ¦ MS-DOS ¦ ¦ Область данных BIOS ¦ ¦ Таблица векторов прерываний ¦ L----------------------------------------- 0000H В базовой конфигурации и в конфигурации расширенного режима процессора 80386 фиксированный код и данные размещаются в нижних адресах сегментов. Несбрасываемые перемещаемые сегменты кода и данных и сбрасываемые сегменты данных . Windows 3.0/pg/3#3 = 23 = располагаются над фиксированными сегментами. Сбрасываемые кодовые сегменты располагаются в старших адресах памяти. Windows при работе при конфигурации памяти расширенного режима процессора 80386 минимизирует фрагментацию памяти по сравнению с базовой конфигурацией. Конфигурация памяти в расширенном режиме процессора 80386 отличается от остальных конфигураций тем, что в ней Windows производит обмен сегментами кода и данных между памятью и диском. В других конфигурациях Windows может удалить из памяти сбрасываемые сегменты, но не может сохранить данные на диске, чтобы в дальнейшем их восстановить. Windows в расширенном режиме процессора 386 продолжает выделять физическую память, пока это необходимо. Затем Windows для освобождения памяти начинает перемещать страницы размером 4К кода и данных из памяти на диск. Windows выполняет операции листания блоками по 4К, а не блоками разного размера, соответствующего размеру кодовых сегментов и сегментов данных. Этот блок может содержать только часть сегмента кода или данных или пересекать границы двух или более сегментов. Эта страничная организация для прикладных программ прозрачна. Если прикладная программа пытается осуществить доступ к сегменту данных или кода, который сброшен на диск, то процессор 80386 вырабатывает для Windows прерывание "ошибка страницы". Windows осуществляет подкачку необходимых прикладной программе страниц и сброс ненужных на диск. Windows выбирает страницы, которые будут сброшены на диск, пользуясь алгоритмом LRU ("наиболее давно не используемый"). Такая система виртуальной памяти предоставляет дополнительную память, объем которой ограничен только размером файла подкачки Windows, который резервируется на диске. Windows определяет необходимый размер файла подкачки на основе информации об объеме доступной физической памяти и доступного пространства на диске. Пользователь может изменить размер файла подкачки, определив его в соответствующем элементе в файле SYSTEM.INI, и может определить постоянный файл подкачки с помощью служебной программы SWAPFILE. Запросы Windows на загрузку сегментов кода и данных работают на вершине схемы страничной организации виртуальной памяти Windows. Таким образом Windows рассматривает виртуальную память как имеющую 640К базовой памяти, которая используется для определения какие сегменты кода или данных необходимо сбросить. Однако Windows удаляет сбрасываемые сегменты только в случае исчерпания виртуальной памяти. Предотвращение сброса страниц памяти на диск. Иногда возникает необходимость в том, чтобы некоторая область памяти всегда находилась в памяти и не сбрасывалась на . Windows 3.0/pg/3#3 = 24 = диск. Например, процедура библиотеки DLL должна всегда находиться в памяти, чтобы она была вызвана сразу же в ответ на прерывание без ожидания того, что система загрузит необходимые сегменты. В таких случаях можно заблокировать блок памяти для предотвращения его сброса на диск. Для блокировки вызовите функцию GlobalPageLock, передав ей в качестве параметра глобальный селектор блокируемого сегмента. Эта функция увеличивает счетчик блокированных блоков данного сегмента. До тех пор, пока этот счетчик не равен нулю, сегмент остается по тем же физическим адресам и не сбрасывается на диск. После того, как блокировка уже не нужна, вызовите функцию GlobalPageUnlock для уменьшения счетчика блокированных страниц. В других режимах эти функции не выполняют никакой работы. Примечание: Блокирование памяти может применяться только в критических ситуациях. Не блокируйте в памяти например электронную таблицу, блокирование влияет на характеристики работы всех прикладных программ, включая вашу. 16.2 Использование памяти в прикладных программах Windows. Windows поддерживает семь типов данных, каждый из которых используется в различных ситуациях. Ниже описаны эти типы данных и предлагается, какой тип в каком случае использовать. Тип Описание --------------------------------------------------------------- Статические Статические данные включают все переменные С, данные которые явно или не явно объявлены как static. Статические данные включают также переменные, объявленные как внешние, явно (с использованием ключевого слова extern) или неявно (объявлены в начале файла с исходным текстом перед телами функций). Автоматические Это переменные, выделяемые в стеке при вызове данные функций. К ним относятся параметры функций и локально объявленные переменные. Смотрите раз- дел 16.2.1 "Управление автоматическими сегмен- тами данных". Локальные дина- Локальные динамические данные выделяются с мические данные помощью функции LocalAlloc. Локальные динами- ческие данные выделяются в локальном динами- ческом сегменте данных, для которого в прик- ладной программе определен сегмент DS. Выделе- ние блоков памяти в локальной динамической об- ласти памяти аналогично использованию функции С malloc в прикладных программах для DOS, ис- пользующих малую и среднюю модели памяти. Смотрите раздел 16.2.2, "Управление блоками ло- кальной динамической области памяти". . Windows 3.0/pg/3#3 = 25 = Глобальные дина- Это данные, выделяемые в глобальной динамичес- мические данные кой области памяти с помощью функции GlobalAlloloc. Глобальная динамическая область памяти - это системный ресурс. Выделение блоков памяти в глобальной динамической области памяти аналогично выделению памяти в программах под DOS c помощью стандартной библиотечной функции С malloc в большой и компактной моделях памяти. Отличие состоит в том, что в Windows вы выделяете память в области, потенциально разделяемой с другими прикладными программами, тогда как под DOS вся динамическая область памяти предоставлена в ваше распоряжение. Смотрите раздел 16.2.3, "Управление блоками глобальной динамической области памяти". Дополнительные Ваша прикладная программа может создать окно байты окна таким образом, что во внутренней структуре окна, которую поддерживает Windows, будут выделены дополнительные байты. Для этого зарегистрируйте класс окна (с помощью функции RegisterClass), и укажите, что каждому окну данного класса должны выделяться дополнительные байты. Для этого укажите ненулевое значение в поле cbWndExtra структуры WNDCLASS, которую вы передаете функции RegisterClass. Вы затем можете записать или получить данные из этой области с помощью функций SetWindowWord, SetWindowLong, GetWindowWord и GetWindowLong. Смотрите раздел 16.2.4, "Дополнительные байты в структурах данных окна и класса". Дополнительные Класс можно создать таким образом, что для байты класса него после структуры WNDCLASS будут выделены дополнительные байты. При регистрации класса окна вы можете указать ненулевое значение в поле cbClassExtra. Затем вы можете записать данные или получить данные из этой области с помощью функций SetClassWord, SetClassLong, GetClassWord и GetClassLong. GetWindowLong. Смотрите раздел 16.2.4, "Дополнительные байты в структурах данных окна и класса". Ресурсы Ресурсы - это неизменяемые наборы данных, хра- нящиеся в области ресурсов выполняемого файла. Эти данные загружаются в память, откуда ваша программа может их использовать. Вы можете определить собственные ресурсы, которые будут содержать те данные (только для чтения), которые вы хотите сохранить. Вы подключаете . Windows 3.0/pg/3#3 = 26 = ресурсы к своему файлу .EXE или .DLL с помощью компилятора ресурсов. В период выполнения программы вы получаете доступ к ресурсам через различные функции библиотек Windows. Смотрите раздел 16.2.5, "Управление ресурсами". --------------------------------------------------------------- 16.2.1 Управление автоматическими сегментами данных. Каждая прикладная программа имеет один сегмент данных, называемый "автоматический сегмент данных", который может иметь размер до 64К. Автоматический сегмент данных содержит следующие типы данных: Тип Описание --------------------------------------------------------------- Заголовок задачи 16 байт информации, которую Windows поддержи- вает для каждой прикладной программы. Они всегда размещаются в первых 16 байтах автома- тического сегмента данных. Статические Все переменные С, явно или неявно объявленные данные как static или extern явно или неявно. Стек Стек используется для хранения автоматических данных. Стек имеет фиксированный размер, но его активная часть увеличивается и уменьшается в соответствии с вызовом функций и возвратом из функций. При каждом вызове функции адрес возврата помещается в активную область стека, так же как и параметры, передаваемые функции. Локальная дина- Содержит все локальные динамические данные, мическая область которые выделяются функцией LocalAlloc. данных --------------------------------------------------------------- На рисунке 16.6 приведен вид автоматического сегмента данных прикладной программы. ------------------------------------¬ \ ¦ Локальная динамическая область ¦ ¦ ¦ данных ¦ ¦ +-----------------------------------+ ¦ ¦ Стек ¦ \ До 64 К +-----------------------------------+ / ¦ Статические данные ¦ ¦ +-----------------------------------+ ¦ ¦ Заголовок задачи ¦ ¦ L------------------------------------ / Рисунок 16.6 Автоматический сегмент данных. . Windows 3.0/pg/3#3 = 27 = Для прикладной программы размер стека всегда фиксирован. Вы определяете его размер в файле определения модуля прикладной программы оператором STACKSIZE. Windows автоматически выделяет стек минимального размера 5К. Вы можете поэкспериментировать со своей программой, чтобы определить оптимальный размер стека вашей программы. Однако результаты переполнения стека непредсказуемы. Размер локальной динамической области памяти определяется в соответствии со значением, указанным в операторе HEAPSIZE в файле определения модуля прикладной программы. Локальная динамическая область памяти растет при вызове функции LocalAlloc. Исходный размер локальной динамической области памяти должен по меньшей мере вмещать текущие переменные среды. Рекомендуемый минимум равен 1К. Если прикладной программе не требуется доступ к переменным среды, то вы можете скомпоновать вашу прикладную программу с объектным файлом, который предотвращает помещение этой информации в локальную динамическую область памяти. Смотрите главу 14, "Язык С и язык ассемблера". Если автоматический сегмент данных вашей программы не блокирован или не фиксированный, то возможно, что он будет перемещен, когда ваша программа вызовет функцию LocalAlloc. Если локальная динамическая область данных должна расти, то Windows ищет место в физической памяти для размещения всего автоматического сегмента данных и перемещает его туда. Пока для доступа к данным из автоматического сегмента данных ваша программа использует ближние указатели, никаких проблем не возникает. В случае необходимости вы можете предотвратить перемещение автоматического сегмента данных даже при вызове функции LocalAlloc с помощью функции LockData. Однако блокирование автоматического сегмента данных может привести к тому, что вызов функции LocalAlloc завершится неудачно, если сегмент не может быть перемещен и если фиксированный сегмент находится над автоматическим сегментом. Если прикладная программа запрашивает больше, чем доступно памяти из локальной динамической области, то она растет до тех пор, пока размер сегмента не достигнет 64К. Однако при освобождении объектов в локальной динамической области памяти она не сжимается. Вы можете сделать это вручную функцией LocalShrink. Эта функция вначале сжимает локальную динамическую область памяти и затем обрезает автоматический сегмент до необходимого размера. LocalShrink не может обрезать ни ниже последнего выделенного объекта, ни ниже указанного размера локальной динамической области памяти. В файле описания модуля прикладной программы вы можете указать, что автоматический сегмент данных должен быть фиксированным или перемещаемым, аналогично другим сегментам. Вы должны описать автоматический сегмент данных как перемещаемый и множественный, если у вас нет веских причин, . Windows 3.0/pg/3#3 = 28 = чтобы не делать этого. Автоматический сегмент данных по умолчанию имеет параметр PRELOAD. Ниже приведен пример объявления автоматического сегмента данных в файле описания модуля прикладной программы: DATA MOVEABLE MULTIPLE Определив автоматический сегмент данных как перемещаемый, вы позволяете Windows перемещать его при изменении размера. Если он будет фиксированным, то Windows выделяет память только в случае, если доступно достаточно памяти. Таким образом, если вы используете фиксированный сегмент, то вы должны задать в операторе HEAPSIZE достаточный размер динамической области памяти. Чтобы каждый экземпляр вашей прикладной программы получил свой автоматический сегмент данных, необходимо определить его как множественный (MULTIPLE). Атрибут SINGLE применим только к автоматическим сегментам данных DLL. Фактически DLL должны быть определены с этим атрибутом, поскольку DLL должны иметь только один экземпляр. 16.2.2 Управление блоками локальной динамической области памяти. В Windows локальная динамическая область данных может располагаться в любом сегменте. Однако чаще всего она располагается в автоматическом сегменте данных. Функция LocalInit определяет указанную область внутри сегмента данных в качестве локальной динамической области памяти. Вызов функции LocalAlloc и других функций для работы с локальной динамической областью памяти работает с сегментом, на который указывает регистр DS. Функции работы с локальной динамической областью памяти работают, если она была инициализирована LocalInit. Если вы разрабатываете DLL, которой требуется локальная динамическая область памяти, то вы должны вызвать LocalInit в процессе инициализации библиотеки. Необходимо отметить, что LocalInit блокирует сегмент данных. Поэтому, после вызова LocalInit для библиотеки вам, возможно, понадобится сделать сегмент перемещаемым. Вы можете сделать это с помощью функции UnlockData. Если вы разрабатываете прикладную программу Windows, то в противоположность DLL вам не нужно для автоматического сегмента данных вызывать функцию LocalInit. Windows самостоятельно вызывает LocalInit, используя информацию о размещении остальных данных в автоматическом семгенте данных (заголовок задачи, статические данные, стек) и информацию о размере локальной динамической области памяти, указанную в файле описания модуля прикладной программы. . Windows 3.0/pg/3#3 = 29 = Организация локальной динамической области памяти аналогична организации глобальной динамической области памяти: - Фиксированные блоки размещаются на дне локальной динамической области памяти. - Перемещаемые, несбрасываемые блоки размещаются над фиксированными блоками. - Сбрасываемые блоки выделяются от вершины глобальной динамической области памяти. . Windows 3.0/pg/3#3 = 30 = Данная организация приведена на рисунке 16.7. -----------------------------------¬ Вершина ¦ Сбрасываемые блоки ¦ ¦ ¦ ¦ ¦ . ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ . ¦ ¦ ¦ ¦ ¦ Перемещаемые блоки ¦ ¦ . ¦ ¦ ¦ ¦ ¦ Фиксированные блоки ¦ L----------------------------------- Дно Рисунок 16.7 Организация локальной динамической области памяти. Когда Windows добавляет новые блоки к локальной динамической области памяти прикладной программы, перемещаемые блоки могут перемещаться для уменьшения размера динамической области памяти. Кроме этого Windows может удалять сбрасываемые блоки для освобождения места для новых. Windows не перемещает фиксированные блоки в локальной динамической области памяти. Выделение памяти в локальной динамической области памяти. Функция LocalAlloc позволяет вам выделить блок памяти указанного размера в локальной динамической области памяти и определить характеристики блока. Характеристики определяют, является ли блок перемещаемым или фиксированным, и может ли перемещаемый блок сбрасываться. Допустимы следующие комбинации флагов: - LMEM_FIXED - LMEM_MOVEABLE - LMEM_MOVEABLE и LMEM_DISCARDABLE При выделении блока в локальной динамической области данных другие блоки могут перемещаться или сбрасываться. В некоторых случаях возможно вы захотите, чтобы при выделении нового блока памяти не происходила реорганизация локальной динамической области памяти. Возможно вам необходимо гарантировать, что указатели на перемещаемые блоки памяти останутся верными. Чтобы гарантировать, что блоки не будут сброшены при выделении нового, укажите при вызове LocalAlloc в параметре wFlags значение LMEM_NODISCARD. Чтобы гарантировать, что блоки не будут ни сброшены, ни перемещены, укажите значение LMEM_NOCOMPACT. . Windows 3.0/pg/3#3 = 31 = LocalAlloc возвращает дескриптор локального блока памяти. Если в локальной динамической области не хватает памяти, то функция возвращает NULL. При использовании остальных функций работы с блоками памяти, которые описаны ниже, вы пользуетесь дескриптором, полученным от функции LocalAlloc. Блокирование и разблокирование блоков памяти. Для многих программистов на С, привыкших к использованию функции malloc, работа с дескрипторами памяти может оказаться новой. Т.к. выделяемые в локальной динамической области памяти объекты могут перемещаться при добавлении новых объектов, то вы не можете подразумевать, что указатели на эти объекты останутся верными. Дескрипторы предоставляют вам константную ссылку на перемещаемый объект. Поскольку дескриптор является не прямым указателем, то его необходимо "разрешить" для получения адреса локального объекта. Это делается с помощью функции LocalLock. LocalLock временно фиксирует объект в определенном месте локальной динамической области памяти. Таким образом гарантируется, что ближний адрес, возвращаемый функцией LocalLock, будет определять данный объект до тех пор, пока вы не вызовите LocalUnlock. Следующий пример демонстрирует, как можно использовать LocalLock для получения адреса перемещаемого объекта. HANDLE hLocalObject; char NEAR * pcLocalObject; /* в средней и малой модели NEAR можно не указывать */ if(hLocalObject = LocalAlloc(LMEM_MOVEABLE,32)) { if(pcLocalObject = LocalLock(hLocalObject)) { /* pcLocalObject используется как ближний указа- тель на локальный объект */ . . . LocalUnlock(hLocalObject); else { /* Реакция на невозможность блокировки */ } } else { /* Невозможно выделить место под 32 байта */ } Если вы выделяете блок памяти с атрибутом LMEM_FIXED, то уже гарантируется, что блок не будет перемещаться в памяти. . Windows 3.0/pg/3#3 = 32 = Следовательно, вы не должны вызывать функцию LocalLock для блокирования фиксированного блока памяти. Следовательно, вы не можете получить адреса объекта с помощью функции LocalLock. Вместо этого в качестве ближнего адреса используется дескриптор, возвращаемый функцией LocalAlloc. Ниже приведен пример: char NEAR * pcLocalObject; /* в средней и малой модели NEAR можно не указывать */ if(зсLocalObject = LocalAlloc(LMEM_MOVEABLE,32)) { /* pcLocalObject используется как ближний указа- тель на локальный объект Не нужно блокировать и разблокировать фиксированный объект */ . . . } else { /* Невозможно выделить место под 32 байта */ } Вы должны избегать того, чтобы в момент, когда необходимо выделить новый блок, перемещаемый объект оставался блокированным в памяти. Это уменьшает эффективность работы системы управления памятью Windows. Windows вынужден работать вокруг блокированного объекта, чтобы выделить память в области перемещаемых блоков памяти. Изменение блока локальной памяти. Для изменения размера блока памяти без потери его содержимого вы можете использовать функцию LocalRealloc. Если вы укажете меньший размер, Windows обрежет блок. Если укажете больший размер, то в случае задания атрибута LMEM_ZEROINIT, оставшаяся часть блока будет заполнена нулями, в противном случае содержимое оставшейся части блока неопределено. Также как и в случае с LocalAlloc, вызов LocalRealloc может привести к перемещению и сбросу блоков памяти. Для предотвращения сброса блоков, вы можете указать значение LMEM_NODISCARD, для предотвращения сброса и перемещения можно указать значение LMEM_NOCOMPACT. Вы можете также использовать LocalRealloc для изменения атрибутов блока с LMEM_MOVEABLE на LMEM_DISCARDABLE и наоборот. Для этого вы должны указать LMEM_MODIFY: hLocalObject = LocalAlloc(32,LMEM_MOVEABLE) . . . Windows 3.0/pg/3#3 = 33 = . hLocalObject = LocalRealloc(hLocalObject, 32, LMEM_MODIFY | LMEM_DISCARDABLE)); Вы не можете использовать LMEM_MODIFY с LocalRealloc для изменения атрибутов блока с и на LMEM_FIXED. Освобождение и сброс блоков локальной памяти. Функции Windows LocalDiscard и LocalFree позволяют соответственно сбросить и освободить блок локальной памяти. Между сбросом и освобождением блока имеется отличие. При сбросе локального блока его содержимое удаляется из локальной динамической области памяти, однако дескриптор этого блока остается корректным. При очистке блока памяти удаляется не только его содержимое, но и его дескриптор из таблицы корректных дескрипторов. Блок локальной памяти может быть сброшен или удален только в том случае, если он не заблокирован. Если вы собираетесь в дальнейшем использовать дескриптор, то вы можете не очищать блок, а сбросить его. Чтобы повторно использовать дескриптор сброшенного блока, укажите его в вызове функции LocalRealloc, вместе с ненулевым значением размера нового блока. При повторном использовании дескриптора вы исключаете необходимость освобождать старый и создавать новый дескриптор. Кроме этого, повторное использование дескриптора позволяет вам перед попыткой выделить блок локальной памяти определить доступный объем памяти. Замораживание памяти. Вы можете временно заморозить локальную память с помощью функции LocalFreeze для гарантии того, чтобы быть уверенным в том, что при последующих вызовах LocalAlloc и LocalRealloc никакие блоки не будут повторно выделены или сброшены. Вызов LocalFreeze аналогичен указанию в последующих вызовах LocalAlloc и LocalRealloc атрибута LMEM_NOCOMPACT. Вы отменяете замораживание памяти функцией LocalMelt. Получение информации о блоке локальной памяти. Функции LocalSize и LocalFlags предоставляют вам информацию о блоках локальной памяти. LocalSize возвращает размер блока. LocalFlags определяет, сбрасываемый ли блок, и если сбрасываемый, то сброшен ли он. LocalFlags также сообщает состояние счетчика блокировок данного блока. . Windows 3.0/pg/3#3 = 34 = 16.2.3 Управление блоками глоабльной динамической области данных. Глобальная динамическая область памяти представляет собой системный ресурс, который разделяется разными прикладными программами. Прикладная программа может запросить Windows выделить блок памяти в глобальной динамической области памяти с помощью функции GlobalAlloc. Windows использует эту же функцию для выделения памяти для своих собственных нужд. Таким образом, с помощью функций, описанных в данном разделе, вы получаете доступ к возможностям системы управления памятью Windows, которые Windows использует для собственных нужд. Кроме этого, эти функции позволяют вашей программе конкурировать или сотрудничать с самой системой на том же уровне привилегий. Неправильное использование этих привилегий уменьшает возможности вашей программы по взаимодействию с Windows и с другими прикладными программами. Следующие описания помогут вам определить, где лучше выделять блок памяти в каждом конкретном случае: в глобальной или локальной динамической области памяти. - Вы должны адресовать блок, выделенный в локальной динамической области памяти, с помощью ближнего указателя (после его разрешения с помощью функции LocalLock). С другой стороны, вы должны адресовать блок, выделенный в глобальной динамической области памяти, с помощью дальнего указателя (после его разрешения с помощью функции GlobalLock). - Локальная динамическая область данных относительно малый ресурс, поскольку она размещается в автоматическом сегменте данных прикладной программы, размер которого ограничен 64К. Автоматический сегмент данных кроме глобальной динамической области памяти включает стек и статические данные. Поэтому глобальная динамическая область данных намного больше. Если объект памяти входит в текущий "рабочий набор", то вы должны попытаться расположить его в локальной динамической области памяти с тем, чтобы использовать преимущества ближней адресации. В текущий "рабочий набор" входят данные, к которым вы должны часто обращаться во время выполнения длительных операций. Объекты, доступ к которым производится реже, помещаются в глобальную динамическую область данных. В некоторых прикладных программах довольно чувствителен обмен данными между глобальной и локальной областями памяти при изменении "рабочего набора". При разработке структуры объектов глобальной памяти вам часто приходится выбирать между возможностью разбить объект на элементарные подобъекты или возможностью соединить небольшие объекты в один более сложный. При выборе, вы должны принимать во внимание следующее: . Windows 3.0/pg/3#3 = 35 = - Каждый глобальный объект требует дополнительно примерно 20 байт. - Глобальные объекты выровнены по границе разделов по 32 байта. Первые 16 байт зарезервированы под обычный заголовок блока памяти. При работе Windows в стандартной конфигурации памяти или в конфигурации памяти расширенного режима процессора 80386 число дескрипторов памяти ограничено 8192, из которых только часть принадлежит каждой прикладной программе. В основном, вам следует избегать выделения небольших объектов. Такой объект (128 или меньше байт) несет еще дополнительной информации 15 процентов, плюс остаток, примерно 16 байт, когда размер блока не является кратным 32. Эта дополнительная информация может быть выровнена, но она всегда присутствует. Вы должны особенно избегать выделения большого числа (несколько сотен) мелких объектов, если их можно объединить в несколько больших объектов. Это не только уменьшает объем дополнительной информации, но также исключает необходимость использования большого числа дескрипторов из ограниченного набора. При учете вышеприведенных замечаний выделение блоков памяти в глобальной динамической области памяти аналогично работе с блоками памяти в локальной динамической области памяти. Управление локальной памятью описано в разделе 16.2.2, "Управление блоками локальной динамической области памяти". В следующих разделах приведено описание функций управления глобальной динамической памятью. Выделение блока в глобальной динамической области памяти. Функция GlobalAlloc выделяет блок памяти указанного размера в глобальной динамической области памяти. Windows управляет блоками памяти в соответствии с той же классификацией, что и в локальной памяти, т.е. блоки могут быть фиксированные, перемещаемые и сбрасываемые. Таким образом, могут быть заданы соответствующие флаги: - GMEM_FIXED - GMEM_MOVEABLE - GMEM_MOVEABLE и LMEM_DISCARDABLE Для управления глобальной памятью используется тот же механизм, что и для управления локальной памятью. Таким образом, вы можете указать при вызове GlobalAlloc атрибуты GMEM_NODISCARD и GMEM_NOCOMPACT. Смотрите описание LMEM_NOCOMPACT и LMEM_NODISCARD в разделе "Выделение памяти в локальной динамической области памяти". . Windows 3.0/pg/3#3 = 36 = GlobalAlloc возвращает дескриптор выделенного в глобальной динамической области памяти блока. Если для удовлетворения запроса недостаточно памяти, GlobalAlloc возвращает NULL. Поскольку не гарантируется удовлетворение запроса, то вы должны проверять возвращаемое функцией GlobalAlloc значение. Большинству описанных в следующих разделах функций требуется данный дескриптор. Выделение блоков глобальной памяти из DLL при работе под конфигурацией памяти с EMS. По умолчанию Windows удовлетворяет запрос DLL на выделение памяти в зависимости от того, каким образом была загружена библиотека. Если библиотека была загружена явно с помощью вызова функции LoadModule, то Windows выделяет глобальные блоки в памяти, находящейся ниже границы отображения. Для библиотек, загруженных неявно оператором IMPORTS в файле описания ресурсов прикладной программы (.DEF), Windows выделяет блок глобальной памяти выше границы отображения (если Windows работает в режиме с большим фреймом отображения). Если Windows работает в режиме с малым фреймом отображения, глобальные блоки памяти выделяются ниже границы отображения. Глобальные объекты памяти, выделенные ниже границы отображения, могут служить помехой некоторым типам библиотек, таким, например, как драйвер принтера, которым необходимы большие буферы для данных. Если место под них будет выделяться ниже границы отображения, то этот ограниченный системный ресурс будет использоваться неэффективно. Для решения этой проблемы вы можете компилировать вашу DLL с ключем -e компилятора ресурсов RC. Этот ключ переключает размещение глобальных объектов памяти с используемого по умолчанию размещения ниже границы отображения на размещение выше границы. Библиотеки, компилируемые с ключем -е, должны быть написаны с учетом того, что при переключении с контекста задачи их глобальные объекты будут отображены в расширенную память. Возможно библиотеке необходимо, чтобы некоторые глобальные объекты всегда находились ниже границы отображения, даже когда происходит переключение между прикладными программами. В этом случае библиотека может вызвать функцию GlobalAlloc c атрибутом GMEM_NOT_BANKED. Блокирование и разблокирование блоков в глобальной динамической области памяти. Вы можете получить дальний адрес блока в глобальной динамической области памяти с помощью функции GlobalLock. В реальном режиме эта функция блокирует блок памяти в определенном месте. Во всех режимах данная функция возвращает дальний указатель, для которого гарантируется, что до вызова GlobalUnlock он останется корректным. . Windows 3.0/pg/3#3 = 37 = В реальном режиме функция GlobalLock вынуждена блокировать объект в памяти, чтобы его указатель оставался корректным до вызова GlobalUnlock. Поскольку при этом происходит действительное блокирование объекта, то GlobalLock увеличивает счетчик блокировок объекта. Этот счетчик позволяет гарантировать, что объект не будет сброшен или очищен в то время, когда он используется. В защишенном режиме (стандартном или расширенном режиме процессора 80386) Windows фиксирует объект в памяти только в том случае, если он сбрасываемый. Указатель остается корректным, пока блок перемещается в линейной памяти. Поскольку Windows в действительности не блокирует объект в памяти, то в защищенном режиме GlobalLock не увеличивает счетчика блокировок для несбрасываемых объектов. GlobalUnlock уменьшает счетчик блокировок, только, если функция GlobalLock его увеличила. Однако, вы все равно при работе в защищенном режиме должны вызвать функцию GlobalUnlock после того, как указатель, возвращаемый функцией GlobalLock, вам больше не нужен. Кроме функций GlobalLock и GlobalUnloc на состояние счетчика блокировок влияют следующие функции: Увеличивают содержимое счетчика блокировок ------------------------------------------ GlobalFix GlobalWire LockSegment Уменьшают содержимое счетчика блокировок ------------------------------------------ GlobalUnfix GlobalUnWire UnlockSegment Описание этих функций смотрите в Справочном руководстве, том 1. Функция GlobalFlags возвращает состояние счетчика блокировок, установленное этими функциями. Раньше было сказано, что для локальных объектов, выделенных с атрибутом LMEM_FIXED, не нужно получать их адрес с помощью функции LocalLock. Однако это не относится к глобальным фиксированным объектам. Для получения адреса фиксированного объекта все равно необходимо использовать функцию GlobalLock. Следующий пример иллюстрирует получение адреса глобального объекта. HANDLE hGlobalObject; LPSTR lpGlobalObject; . Windows 3.0/pg/3#3 = 38 = if(hGlobalObject = GlobalAlloc(GMEM_MOVEABLE,1024)) { if(lpGlobalObject = GlobalLock(hGlobalObject)) { /* lpGlobalObject используется как дальний указа- тель на объект в глобальной динамической области памяти */ . . . GlobalUnlock(hGlobalObject); else { /* Реакция на невозможность блокировки */ } } else { /* Невозможно выделить место под 1024 байта */ } Если вы выделяете объект, размер которого равен или больше 64К, вы должны явно определить указатель, возвращаемый GlobalLock, как huge. Ниже приведен пример выделения блока размером 128К: HANDLE hGlobalObject; char huge * hpGlobalObject; if(hGlobalObject = GlobalAlloc(GMEM_MOVEABLE,0x20000L)) { if(hpGlobalObject = GlobalLock(hGlobalObject)) { /* hpGlobalObject используется как дальний указа- тель на объект в глобальной динамической области памяти */ . . . GlobalUnlock(hGlobalObject); else { /* Реакция на невозможность блокировки */ } } else { /* Невозможно выделить место под блок размером 128К */ } Изменение блока глобальной динамической области памяти. Вы можете изменить размер и атрибуты глобального блока при . Windows 3.0/pg/3#3 = 39 = сохранении его содержимого с помощью функции GlobalRealloc. Если вы укажете меньший размер, Windows обрежет блок. Если вы укажете больший размер и атрибут GMEM_ZEROINIT, то дополнительная часть будет заполнена нулями. С помощью атрибутов GMEM_DISCARD и GMEM_NOCOMPACT вы запретите Windows сбрасывать и перемещать блоки для удовлетворения запроса GlobalRealloc. Кроме этого, с помощью функции GlobalRealloc вы можете изменить атрибуты блока со сбрасываемого на несбрасываемый и наоборот. В отличие от LocalRealloc, вы можете изменить атрибут блока с GMEM_FIXED на GMEM_MOVEABLE или GMEM_DISCARDABLE. Однако вы не можете изменить атрибуты сбрасываемого или перемещаемого блока на GMEM_FIXED. Для изменения атрибутов блока глобальной памяти при вызове GlobalRealloc необходимо также указать атрибут GMEM_MODIFY. Смотрите пример в разделе 16.2.2, "Изменение блока локальной динамической области памяти". Вы должны проявлять осторожность при увеличении размера глобального блока, когда его размер пересекает границу, кратную 64К. Windows может вернуть новый дескриптор для измененного блока. Это, например, происходит при изменении размера блока с 50К на 70К или со 120К до 130К. В стандартном режиме это происходит при пересечении границы 65519 байт, т.е. на 17 байт меньше 64К. Поскольку при работе в стандартном режиме и в расширенном режиме процессора 80386 Windows использует технологию "селекторов в стык", то при пересечении размера блока границы кратной 64К, Windows возможно осуществит поиск большего набора зависимых селекторов. Если такой набор будет найден, Windows вернет первый селектор этого набора. Смотрите раздел 16.1.3, "Использование больших блоков памяти при работе в стандартной конфигурации памяти". Следующий пример показывает, как не надо увеличивать размер блока глобальной памяти. Этот способ подходит для работы в базовой конфигурации памяти и в конфигурации EMS 4.0., но является неверным при работе Windows в стандартной конфигурации и конфигурации памяти расширенного режима процессора 80386. /* НЕ СЛЕДУЙТЕ ДАННОМУ ПРИМЕРУ */ GlobalRealloc(hHugeObject, 0x20000L, GMEM_MOVEABLE); В предыдущем примере дескриптор hHudeObject может стать некорректным в зависимости от того, как Windows удовлетворила данный запрос. Ниже приведен корректный пример для всех конфигураций памяти Windows. /* СЛЕДУЙТЕ ДАННОМУ ПРИМЕРУ */ . Windows 3.0/pg/3#3 = 40 = if(hTempHugeObject = GlobalRealloc(hHugeObject, 0x20000L, GMEM_MOVEABLE); { hHugeObject = hTempHugeObject; } else { /* объект не может быть выделен */ } В этом примере используется временный дескриптор hTempHugeObject, который предотвращает занесение в hHugeObject NULL, возвращаемого в случае неудачного завершения GlobalRealloc. Очистка и сброс блоков глобальной памяти. Функции GlobalFree и GlobalDiscard идентичны функциям LocalFree и LocalDiscard за исключением того, что они работают с глобальными объектами, а не локальными. Обсуждение работы функций LocalFree и LocalDiscard вы найдете в разделе 16.2.2, в параграфе "Очищение и сброс блоков локальной памяти". Получение информации о блоках глобальной памяти. Функции GlobalSize и GlobalFlags предоставляют вам информацию о блоках глобальной памяти. GlobalSize возвращает размер блока. GlobalFlags определяет, сбрасываемый ли блок, и если сбрасываемый, то сброшен ли он. Она также определяет, был ли блок выделен с атрибутами GMEM_DDESHARE или GMEM_NOT_BANKED. Блокирование блоков глобальной памяти на длительный период. Когда вы вызываете GLobalLock для предотвращения перемещения перемещаемого объекта в глобальной динамической области памяти, вы уменьшаете эффективность управления Windows остальными объектами в памяти. Допускается блокирование объекта только на короткий промежуток времени. Для блокирования объекта на длительное время вместо функции GlobalLock нужно использовать функцию GlobalWire. Эта функция перемещает блокируемый объект в младшие адреса, которые зарезервированы под фиксированные объекты, и затем блокирует его. Блокирование объекта в младших адресах позволяет Windows эффективно исползовать память, но требует больше времени на перемещение объекта. Функция GlobalUnWire разблокирует объект. После разблокирования объекта он перемещается из области фиксированных объектов глобальной памяти. Уведомление прикладной программы при сбросе блока памяти. . Windows 3.0/pg/3#3 = 41 = Если вы хотите, чтобы Windows при сбросе блока глобальной памяти уведомляла вашу программу, вызовите функцию GlobalNotify. Эту функцию можно использовать, если, например, вы пишите собственную систему управления виртуальной памятью, которая использует подкачку с и на диск. При вызове функции вы определяете адрес функции многократного вызова, которая получает уведомление о сбросе блока. Изменения, когда блок памяти сброшен. При управлении глобальной динамической областью памяти Windows использует алгоритм LRU для определения того, какой блок нужно сбросить при необходимости освобождения памяти. Вы можете переместить блок памяти на последнее место в списке LRU с помощью функции GlobalLRUOldest. Это означает, что данный объект будет наиболее подходящей кандидатурой на сбрасывание. Если вам нужно, чтобы объект был наименее вероятной кандидатурой на сбрасывание, можно воспользоваться функцией GlobalLRUNewest. Эти функции используются, например, при удалении кода инициализации, когда он больше не нужен. Кроме этого, вы можете использовать эти функции при создании собственной системы управления виртуальной памятью, которая использует подкачку с диска. С помощью этих функций вы можете воздействовать на то, какие объекты будут сброшены в первую, а какие в последнюю очередь для минимизации обращения к диску. Очистка глобальной памяти в состоянии недостатка памяти. Глобальная память - это разделяемый ресурс. Характеристики всех прикладных программ зависят от их готовности разделять этот ресурс. При недостатке системной памяти ваша прикладная программа должна быть способна освободить выделенную ей глобальную память. Windows посылает сообщение WM_COMPACTING всем высокоуровневым окнам, когда она определяет, что в течении интервала времени от 30 до 60 секунд на реорганизацию памяти тратилось больше 15 процентов системного времени. Это сообщение говорит о недостатке памяти. При получении этого сообщения ваша прикладная программа должна освободить максимально возможную часть памяти, принимая во внимание текущую активность прикладной программы и число других прикладных программ, работающих под Windows. Чтобы определить число работающих под Windows прикладных программ, вы можете воспользоваться функцией GetNumTasks. 16.2.4 Использование дополнительных байт Окна и класса. Вы можете хранить дополнительные данные вашей прикладной программы вместе со структурой данных, описывающей атрибуты . Windows 3.0/pg/3#3 = 42 = класса или окна. Эти дополнительные данные называются дополнительные байты класса и дополнительные байты окна. Эти собственные данные размещаются в структуре данных, которую поддерживает Windows. При вызове функции RegisterClass поле cbWndExtra определяет число дополнительных байт, которые будут поддерживаться для всех окон, принадлежащих данному классу. Эти данные могут использоваться, когда у вас имеется два или больше окна, принадлежащих одному и тому же классу, и вы хотите связать с каждым окном уникальный набор данных. Без данной возможности вам пришлось бы поддерживать список структур данных для каждого окна. Затем, при необходимости доступа к данным определенного окна вам пришлось бы определять соответствующий элемент в списке. При использовании дополнительных байт окна вы можете напрямую осуществлять доступ к данным, используя дескриптор окна, и не заводить отдельного списка. Для записи данных в область дополнительных данных окна вы можете использовать функцию SetWindowWord и SetWindowLong. Обе эти функции получают смещение байта внутри области дополнительных данных, точки, в которую необходимо записать данные. Нулевое смещение определяет первый элемент типа WORD или LONG. Смещение 2 определяет второй элемент типа WORD в этой области. Смещение 4 определяет третий элемент типа WORD и второй типа LONG. Необходимо отметить, что функции SetWindowWord и SetWindowLong получают также такие константы, как GWW_STYLE и GWW_ENDPROC, определенные в WINDOWS.H. Эти константы содержат отрицательные смещения внутри структуры данных окна. Длина структуры данных (минус размер области дополнительных данных) таким образом добавляется к смещению, передаваемому при вызове SetWindowWord и SetWindowLong, для определения действительного смещения относительно начала структуры данных. Для чтения данных из области дополнительных данных окна используются функции GetWindowWord и GetWindowLong. Смещение указывается как и в функциях SetWindowWord и SetWindowLong. В конфигурации памяти EMS структуры данных окна выделяются в относительно ограниченной области памяти ниже границы отображения. Если вы хотите связать с окном большой объем дополнительных данных (больше 10 байт), то лучше в области дополнительных данных хранить глобальный дескриптор, а не сами данные. Это позволяет уменьшить область дополнительных данных до 2 байт, необходимых для хранения глобального дескриптора. Также, как вы можете связать дополнительные данные с окном, вы можете связать дополнительные данные и с классом. Для доступа к этим данным используются функции SetClassWord, SetClassLong, GetClassWord и GetClassLong. Возможны ситуации, . Windows 3.0/pg/3#3 = 43 = когда нужно присвоить данные, относящиеся ко всему классу как к одному целому, и которые общие для всех окон одного класса. 16.2.5 Управление ресурсами. Ресурсы - это данные, предназначенные только для чтения, хранящиеся в .EXE файле прикладной программы или файле .DLL библиотеки, и которые Windows загружает при необходимости. Они включают растровые карты, иконы, курсоры, панели диалога и шрифты. Вы можете их создать с помощью редакторов ресурсов SDKPAINT, DIALOG и FONTEDIT. Ресурсы подсоединяются к файлу .EXE или .DLL с помощью компилятора ресурсов (RC). Вы используете знание Windows форматов этих ресурсов, оперируя с ними с помощью специальных функций, таких как LoadIcon или CreateDialog. Windows загружает ресурсы в память в виде одного сегмента. Ресурс может быть описан как перемещаемый, фиксированный или сбрасываемый. При определении того, какой должен быть ресурс, т.е. сбрасываемый, фиксированный или перемещаемый, вы должны принимать во внимание те же причины, что и при определении атрибутов глобальных блоков памяти. Если вы объявляете ресурс с ключем PRELOAD, Windows загрузит его во время запуска прикладной программы. Иначе, Windows загрузит его только когда он понадобиться (ключ LOADONCALL). Эти ключи особенно важны при работе прикладной программы в конфигурации памяти EMS с небольшим фреймом отображения. Смотрите раздел 16.6.4, "Последовательность кодовых сегментов в .DEF файле", в котором дается дополнительная информация по поводу загрузки ресурсов при работе в конфигурации памяти EMS с малым фреймом отображения. Кроме ремурсов, формат которых распознает Windows, вы можете создать свои собственные ресурсы, распознаваемые только вашей программой. Данные в этом случае могут иметь любой формат, включая текст ASCII, двоичные данные или их смесь. Решая вопрос о предствлении данных как отдельный файл или как ресурсы, вы должны принимать во внимание следующее: - При компиляции ресурсов к вашей прикладной программе вы все данные помещаете в .EXE-файл. Ни вам ни вашему пользователю не придется беспокоиться об инсталлировании дополнительных файлов данных вместе с .EXE-файлом. - С другой стороны использование данных в виде ресурсов означает, что при изменении данных файл придется компилировать заново. Если вы не хотите для каждого пользователя компилировать программу с разными ресурсами, то удобнее будет использовать разные файлы данных, а не разные .EXE-файлы. . Windows 3.0/pg/3#3 = 44 = Шаги, необходимые для присоединения к EXE и DLL - файлам ресурсов, определяемых вами, описаны в "Tools". В следующих разделах описаны функции, используемые для доступа к определяемым вами ресурсам. Определение местоположения определяемых вами ресурсов. Функция FindResource определяет местоположение ресурса по его имени в описании ресурса. Функция возвращает дескриптор, который вы можете в дальнейшем использовать для загрузки ресурса с помощью функции LoadResource. Дескриптор ресурса, возвращаемый функцией FindResource, определяет информацию, которая описывает его тип, объявленный в файле описания ресурсов прикладной программы, местонахождение в EXE- или DLL-файле и размер ресурса. Например, предположим, вы хотите в качестве ресурса поддерживать текстовый файл в коде ASCII с именем MYTEXT.EXE. Имя ресурса "mytext", а возможное имя типа "TEXT". Описание такого ресурса в файле описания ресурсов прикладной программы будет выглядеть следующим образом: mytext TEXT mytext.txt В программе вы можете получить дескриптор ресурса с помощью функции FindResource: HANDLE hMyTextResLoc; . . . hMyTextResLoc = FindResource(hInstance, "TEXT","mytext"); Загрузка собственных ресурсов. Функция FindResource не загружает ресурс из файла в память. Она только определяет его местоположение в файле и возвращает дескриптор, который определяет информацию о его разположении. Для загрузки ресурса в память вы должны вызвать LoadResource, как показано ниже: HANDLE hMyTextResLoc; HANDLE hMyTextRes; . . . hMyTextResLoc = FindResource(hInstance, "TEXT","mytext"); if(!hMyTextRes = LoadResource(hInstance,hMyTextResLoc)) { /* обработка события, когда для загрузки ресурса недостаточно памяти */ } . Windows 3.0/pg/3#3 = 45 = Функция LoadResource самостоятельно вызывает GlobalAlloc для выделения памяти для ресурсов и затем копирует их из файла в память. Блокирование и разблокирование собственных ресурсов. Для доступа к данным ресурсов, находящимся в блоке глобальной памяти, вы должны вызвать функцию LockResource для блокирования памяти и получения дальнего указателя на данные. Это эквивалентно использованию GlobalLock для получения адреса блока памяти, выделенного с помощью функции GlobalLock. Ниже приводится продолжение предыдущего примера: LPSTR lpstrMyText; . . . lpstrMyText = LockResource(hMyTextRes); После получения дальнего адреса ресурса вы можете читать из него данные так же, как из блока памяти, заблокированного функцией GlobalLock. В отличие от GlobalLock, функция LockResource в случае сброса блока памяти с ресурсом самостоятельно вызывает функцию LoadResource для его повторной загрузки. Когда доступ к ресурсам вам больше не нужен вы должны вызвать функцию UnlockResource. Эта функция эквивалентна GlobalUnlock. Если вы объявили ресурс как сбрасываемый или перемещаемый, это предоставляет Windows возможность перемещать или сбрасывать блок памяти, содержащий ресурс, при необходимости удовлетворения других запросов на выделение памяти. Очистка собственных ресурсов. Функция FreeResource аналогична GlobalFRee. Она сбрасывает блок памяти, используемый для хранения данных ресурса, и дескриптор, используемый для доступа к данному ресурсу. Если возникнет необходимость снова загрузить ресурс, вы должны это сделать с помощью функции LoadResource, используя дескриптор его местоположения, который вы получили в начале с помощью функции FindResource. . Windows 3.0/pg/3#3 = 46 = 16.3 Использование моделей памяти. Прикладные программы Windows, так же как и прикладные программы DOS, могут иметь один или несколько сегментов данных и один или несколько сегментов кода. Модель памяти, которую вы указываете при компиляции исходных модулей, определяет, будет ли компилятор использовать дальние или ближние адреса. Если вы указываете модель памяти, в которой программа имеет один сегмент кода или данных, то компилятор будет генерировать ближние адреса для доступа соответственно к коду или данным. Если вы используете модель памяти с несколькими сегментами кода или данных, то компилятор будет соответственно генерировать дальние адреса для доступа к коду или данным. Рисунок 16.8 иллюстрирует влияние модели памяти на то, каким образом прикладная программа адресует код и данные. Число кодовых сегментов Один Несколько ---------------T--------------¬ Число ¦ Малая ¦ Средняя ¦ сегментов Один ¦ модель ¦ модель ¦ данных ¦ памяти ¦ памяти ¦ +--------------+--------------+ ¦ Компактная ¦ Большая ¦ Несколько ¦ модель ¦ модель ¦ ¦ памяти ¦ памяти ¦ L--------------+--------------- Рисунок 16.8 Модели памяти компилятора Microsoft. Две модели памяти: большая и huge, которые генерируют дальние адреса при обращении как к данным так и к коду. В большой модели памяти дальние адреса могут увеличиваться только в пределах 64 Кбайтового сегмента. В модели памяти huge адреса могут увеличиваться и пересекать границы сегментов, т.к. для них производится увеличение не только смещения, но и адреса сегмента. Также, если модуль компилируется в большой модели памяти, то в реальном режиме его сегмент данных всегда фиксирован, и во всех режимах Windows может загрузить только один экземпляр этого модуля. В общем, лучше всего для прикладных программ Windows использовать малую модель памяти. При работе Windows в базовой конфигурации памяти и конфигурации EMS, что приводит к тому, что дальние сегменты данных программ, откомпилированных в компактной, большой и hude моделях памяти, становятся фиксированными, что ухудшает характеристики системы управления памяти Windows. Дальние сегменты данных должны быть фиксированы, поскольку Windows не содержит механизма переназначения дальних ссылок на перемещаемые в памяти сегменты. Однако Windows содержит механизм переназначения ссылок на автоматический сегмент данных прикладной программы и . Windows 3.0/pg/3#3 = 47 = на кодовый сегмент, когда они перемещаются. Если вы используете Microsoft C Compiler, то компилируйте прикладные программы с ключем -AS для малой модели памяти и -AM для средней модели памяти. Windows позволяет вам использовать "смесь" моделей памяти. В смешанной модели памяти вы компилируете модули, используя ключ -AS и для тех модулей, которые вы хотите поместить в один кодовый сегмент вы указываете одно и то же имя сегмента, а для тех модулей, которые должны находиться в других сегментах, указываете отличные имена сегментов. Для присвоения сегменту имени используется ключ -NT. Функции, вызываемые из разных сегментов, должны быть объявлены в модуле, который осуществляет их вызов как дальние (far), как показано ниже: WORD FAR PASCAL FuncInAnotherCodeSeg(WORD,LONG); WORD wReturn; . . . wReturn = FuncInAnotherCodeSeg(0,0L); Преимущество смешанной модели заключается в том, что необходимо использовать дальние вызовы только между сегментами. Функции, объявленные как far, требуют больше места и их вызов осуществляется медленнее. В другой форме смешанной модели вы можете компилировать с использованием ключа -AM, который заставляет компилятор генерировать по умолчанию дальние вызовы. Затем, те функции, вызов которых осуществляется из этого же сегмента, описываются как ближние (near). Недостатком этого метода является то, что функции исполняющей системы С также будут дальними. 16.4 Использование данных типа huge. В программах на С вы можете объявить данные как huge. В этом случае компилятор С будет корректно выполнять арифметические действия с указателями таким образом, чтобы они могли пересекать границы сегментов. Вы можете посылать указатели типа huge функциям библиотеки Windows или собственным функциям, которые должны получать дальние указатели, но только если функция не выполняет внутреннего увеличения указателя, для указания объекта, который перекрывает границу сегментов. Например, нижеприведенный пример допустим, поскольку 16 является коэффициентом 64К (65536). char huge Record[10000][16]; int i; TextOut(hDC,x,y,(LPSTR)Record[i],16); . Windows 3.0/pg/3#3 = 48 = Следующий пример нарушает это ограничение, т.к. указатель, посылаемый функции TextOut, указывает на объект, который в конце концов пересечет границу 64К: char huge Record[10000][15]; int i; TextOut(hDC,x,y,(LPSTR)Record[i],15); /* не делайте так */ Поскольку 15 не является делителем 64К, то в конце концов указатель пересечет границу сегмента. 16.5. Чeго следует избегать при работе с данными В предыдущих разделах приведена основная информация о том, как работает система управления памятью Windows. Даны советы по поводу того, каким образом выделять память и как эффективно использовать различные методы для выделения памяти. В данном разделе мы сосредоточим внимание на основных ошибках программирования в среде Windows, которые вы должны избегать в своих программах. Если вы понимаете, как работает система управления памятью Windows, то следующие советы будут прозрачны. Не предполагайте, что уровень привилегий, на котором работает ваша программа, останется неизменным. Будущие версии Windows будут изменять уровень привилегий, на котором работают прикладные программы. Не используйте дальние указатели на статические данные в малой и средней модели памяти. Предположим, модуль содержит следующие объявления: static LPSTR lpstrDlgName = "MyDlg"; /* НЕ СЛЕДУЙТЕ ДАННОМУ ПРИМЕРУ */ . . . hDlg = CreateDialog(hInstance, lpstrDlgName, hWndParent, lpDlgFunction); Указатель LPSTR (char FAR *) устанавливается при загрузке программы в память и может стать некорректным, если Windows переместит автоматический сегмент данных, который содержит строку "MyDlg" (конечно этого не произойдет, если автоматический сегмент данных будет фиксированным). . Windows 3.0/pg/3#3 = 49 = Предыдущий пример можно исправить, если объявить ближний указатель на строку, PSTR (char NEAR *), и привести его к типу LPSTR в вызове функции CreateDialog, как показано в следующем примере: static PSTR pstrDlgName = "MyDlg"; /* СЛЕДУЙТЕ ДАННОМУ ПРИМЕРУ */ . . . hDlg = CreateDialog(hInstance, (LPSTR)pstrDlgName, hWndParent, lpDlgFunction); Явное приведение к типу дальнего указателя в момент вызова функции приведет к тому, что содержимое регистра DS будет помещено в стек в момент вызова функции, а не в момент загрузки программы. Не посылайте данные другой прикладной программе через глобальный дескриптор. Для разделения данных с другой прикладной программой не используйте глобальный дескриптор, т.к. вы должны подразумевать, что ваша и другие программы имеют разбитое на части адресное пространство. Например, если ваша прикладная программа работает при конфигурации памяти EMS с большим фреймом отрбражения. Адрес , возвращаемый при разрешении дескриптора, определяет блок в области отображения. Если ваша прикладная программа посылает затем этот дескриптор другой прикладной программе, то при попытке получить адрес блока памяти (с помощью функции GlobalLock), в результате вы получите адрес в одномегобайтовом физическом адресном пространстве. Однако этот дескриптор указывает на данные, отображенные в область отображения EMS для другой прикладной программы, а не данные вашей прикладной программы. Рассмотрим следующий неверный пример: WORD wMyMsg; HANDLE hGlobalObject; . . . wMyMsg = RegisterWindowMessage((LPSTR)"MyMessage"); hGlobalHandle = GlobalAlloc(GMEM_FIXED,100H); . . . PostMessage(-1,wMyMsg,hGlobalObject,0L); /* НЕ СЛЕДУЙТЕ ДАННОМУ ПРИМЕРУ */ . Windows 3.0/pg/3#3 = 50 = В данном примере всем окнам (и других прикладных программ) передается специально зарегистрированное сообщение. Если другая программа зарегистрировала сообщение с таким же именем "MyMessage", то она также сможет распознать данное сообщение в одной из функций обработки сообщений окна. Если затем эта программа попытается получить адрес объекта, определяемого дескриптором hGlobalObject, то при работе при конфигурации памяти EMS с большим фреймом отображения, адрес, возвращаемый функцией GlobalLock, будет неверным. Даже те данные, которые выделены с атрибутом GMEM_FIXED, все равно в данной конфигурации отображаются в расширенную память. Кроме того, в будущих версиях Windows при работе в стандартной конфигурации памяти и конфигурации памяти расширенного режима процессора 80386 адресное пространство прикладной программы будет разделенным на части. Windows поддерживает следующие методы обмена данными между прикладными программами - это системный буфер и протокол динамического обмена данными (DDE). Если вы посылаете через DDE глобальный дескриптор, то он должен иметь атрибут GMEM_DDESHARE. При разделении памяти вы всегда должны пользоваться DDE. Вы можете пользоваться протоколом DDE для передачи глобального дескриптора потому, что для таких дескрипторов Windows выполняет дополнительную обработку. Когда вторая прикладныя программа пытается получить адрес такого блока памяти, Windows загружает его из расширенной памяти первой программы во временный глобальный блок в области отображения второй программы. Возвращаемый функцией GlobalLock адрес таким образом указывает на этот временный глобальный блок. Прикладная программа, которая в настоящий момент отображена на физическую память, может только читать эти данные. Когда программа вызывает функцию GlobalUnlock, Windows удаляет этот временный блок из глобальной динамической области памяти второй программы. Не подразумевайте зависимости между дескриптором и дальним указателем в разных режимах. При использовании глобальных блоков памяти для получения адреса блока необходимо всегда пользоваться функцией GlobalLock, независимо от режима, в котором работает Windows. Не загружайте в сегментные регистры значения, отличающиеся от тех, что загружены Windows или DOS. При работе Windows в стандартной конфигурации памяти и в конфигурации расширенного режима процессора 80386 сегментные регистры рассматриваются как селекторы, а не как адреса параграфов. Таким образом, не надо пытаться считать таблицу прерываний, установив в 0 регистр DS или ES. Используйте только . Windows 3.0/pg/3#3 = 51 = функции DOS для захвата прерываний. Не выполняйте самостоятельно сегментную арифметику. Не пытайтесь увеличить сегментный адрес дальнего указателя. Эта техника не может быть использована, когда Windows работает в стандартной конфигурации памяти или в конфигурации расширенного режима процессора 80386. Смотрите параграф "Блокирование и разблокирование блоков глобальной памяти" в разделе 16.2.3. Не сравнивайте адреса сегментов. Не сравнивайте значения селекторов для определения того, какой сегмент находится в младших адресах. Эта техника не может быть использована, когда Windows работает в стандартной конфигурации памяти или в конфигурации расширенного режима процессора 80386. Не читайте и не пишите за концом объекта памяти. Ни при каких обстоятельствах не пытайтесь писать или читать данные за концом объекта памяти. Хотя в конфигурациях памяти это не отлавливается, но при работе Windows в стандартной конфигурации памяти или в конфигурации расширенного режима процессора 80386 это приводит к возникновению неустранимой ошибки (GP). 16.6 Управление памятью для кода программы. Вы должны планировать, как Windows будет управлять кодовыми сегментами, которые составляют выполняемую часть вашей прикладной программы или библиотеки. Это планирование должно включать следующее: - Должны ли кодовые сегменты быть фиксированными, перемещаемыми или удаляемыми. - Должна ли ваша программа или библиотека содержать один или несколько кодовых сегментов. - Как поддерживать баланс между размерами кодовых сегментов и дальними вызовами между ними. - Порядок, в котором Windows должна загружать кодовые сегменты. В данном разделе приводится информация о том, как Windows управляет кодом прикладных программ и библиотек, а также даются советы о том, как писать программы с учетом данной информации. 16.6.1 Использование атрибутов кодовых сегментов. . Windows 3.0/pg/3#3 = 52 = Для управления кодовыми сегментами Windows использует те же способы, что и для управления сегментами данных. Вы можете и часто должны разделить вашу прикладную программу на отдельные кодовые сегменты. Вы можете объявить, что определенный кодовый сепгмент должен быть фиксированным, перемещаемым или сбрасываемым, так же как вы это делаете для автоматического сегмента данных или для глобальных объектов. В файле описания модуля прикладной программы (.DEF) с помощью оператора CODE можно указать, что по умолчанию кодовые сегменты будут фиксированными, перемещаемыми или сбрасываемыми. Например, в следующем примере объявляется, что для кодовых сегментов по умолчанию будет применяться атрибут MOVEABLE: CODE MOVEABLE; Вы можете определить другие атрибуты для конкретного кодового сегмента, используя метод, описанный в разделе 16.6.2, "Использование нескольких кодовых сегментов". Если вы объявите кодовый сегмент как сбрасываемый, то Windows может сбросить его, если понадобится память для удовлетворения запроса не ее выделение. Поскольку кодовый сегмент неизменяемый, то при сбросе сегмента не нужно опасаться потери данных. При вызове кодового сегмента, отсутствующего в памяти, Windows загружает его из .EXE-файла. Однако, если сбрасываемого сегмента нет в памяти, то для его вызова требуется дополнительное время, необходимое Windows для его загрузки в память. С другой стороны, это довольно слабый недостаток, т.к. для сброса сегментов Windows использует алгоритм LRU, и таким образом часто используемые сегменты не будут сброшены. 16.6.2 Использование нескольких кодовых сегментов. Большинство прикладных программ Windows должно компилироваться в смешанной модели памяти. Код должен быть разделен на относительно небольшие (примерно 4К) сегменты. Это позволяет Windows легко перемещать их в памяти. Информацию о смешанной модели вы найдете в разделе 16.3, "Использование моделей памяти". Когда вы компилируете модуль на языке С, кодовому сегменту по умолчанию присваивается имя _TEXT. Вы можете присвоить кодовому сегменту другое имя с помощью ключа -NT в команде cl. Вы делите код на различные сегменты, присваивая различные имена сегментов разным модулям. Следующая команда создает кодовый сегмент с именем CODESEG1. cl -u -c -AS -Gsw -Oas -Zpe -NT CODESEG1 module1.c Для кодового сегмента можно указать атрибуты, отличные от устанавливаемых по умолчанию оператором CODE в файле описания . Windows 3.0/pg/3#3 = 53 = модуля прикладной программы. Например, следующий файл описания модуля объявляет все кодовые сегменты перемещаемыми, за исключением сегмента с именем CODESEG1, который объявляется сбрасываемым: CODE LOADONCALL MOVEABLE SEGMENTS CODESEG1 MOVEABLE DISCARDABLE 16.6.3 Балансирование кодовых сегментов. Хотя мы говорили о том, что кодовые сегменты должны быть маленькими, необходимо предварительно оценить, во что выльются дальние вызовы между сегментами по сравнению с ближними внутри сегментов. Для прикладной программы Windows дальний вызов обходится намного дороже, чем в DOS. Каждый вызов требует от Windows дополнительной обработки, т.к. его необходимо направить в сегмент, который мог быть перемещен или сброшен. Задача балансировки кодовых сегментов в прикладной программе заключается в минимизации дальних вызовов между сегментами, размер которых не превышает значительно 4К. Функции, часто вызывающие друг друга, должны помещаться в один кодовый сегмент с учетом размера сегмента. 16.6.4 Порядок кодовых сегментов в файле определения модуля прикладной программы (.DEF). В конфигурации памяти EMS с малым фреймом отображения сегменты и ресурсы загружаются выше границы отображения в соответствии с последовательностью их загрузки в память Windows. Если кодовый сегмент загружен выше границы отображения, он не может быть сброшен. Чтобы максимально использовать преимущества расширенной памяти, вы должны описать в файле определения модуля как PRELOAD и MOVEABLE сегменты, к которым происходит частое обращение. Эти сегменты должны быть указаны в списке в начале, т.к. они должны быть загружены первыми. Если вы этого не сделаете, то выше границы отображения могут попасть менее важные сегменты. Например, код инициализации, который необходим только в момент загрузки программы, будет помещен в расширенную память на все время работы программы. Это означает, что другие кодовые сегменты, обращение к которым происходит часто, могут оказаться ниже границы отображения и будут сброшены при выделении дополнительной памяти. Для определения наиболее часто исплользуемых сегментов можно использовать Profiler. Смотрите "Tools". Обявление сегмента как MOVEABLE или DISCARDABLE не влияет, если сегмент будет загружен выше границы отображения в . Windows 3.0/pg/3#3 = 54 = конфигурации памяти EMS с малым фреймом отображения. Вы не должны объявлять кодовый сегмент как FIXED, но это не означает, что вы можете это делать для других конфигураций памяти, например, для EMS с большим фреймом отображения. Ресурсы прикладной программы так же могут располагаться выше границы отображения. Вы можете влиять на это так же, как вы это делаете в случае с кодовыми сегментами. Для этого необходимо объявить ресурс в файле описания ресурсов с параметром PRELOAD следующим образом: mycursor CURSOR PRELOAD point.cur Заметим, что кодовые сегменты должны быть загружены перед ресурсами. 16.7 Заключение. Windows управляет памятью, пытаясь обеспечить ее наиболее эффективное использование. Имеется четыре основных конфигурации памяти, которые более или менее соответствуют режимам работы Windows. В каждой конфигурации Windows осуществляет управление памятью поразному. Прикладные программы должны следовать некоторым правилам, чтобы работать под Windows, работающей в стандартном режиме или в расширенном режиме процессора 80386. Раздел Руководство --------------------------------------------------------------- Использование языка Руководство программиста: глава 14, "Язык С и языка ассемблера С и язык ассемблера". в прикладных прог- раммах Windows Функции управления Справочное руководство, том 1: Глава 4, памятью "Список функций". Операторы файла Справочное руководство, том 2: Глава 10, определения модуля "Операторы файла определения модуля".. . Windows 3.0/pg/3#3 = 55 = Глава 17. Параметры принтера. ---------------------------------------------------------------- Когда пользователь осуществляет печать из вашей программы, то полученный результат будет зависеть не только от данных, которые выводятся на принтер, но и от текущих параметров принтера. Эти параметры могут включать такую информацию, как размер страницы, ориентацию вывода или тип бумаги. Простейший путь для вывода на принтер использует текущие установоленные параметры (показан в главе 12, "Печать") без их проверки или изменения. Такой подход работает до тех пор, пока текущие параметры принтера удовлетворяют нуждам прикладной программы. Однако, если это не так, то вывод на принтер вашей прикладной программы будет отличаться от идеального. Например, если ваша прикладная программа печатает электронную таблицу, которая требует "ландшафтную" ориентацию вывода, на принтере, на котором установлена "портретная" ориентация, то вполне возможно, что выводимые данные вылезут за правую границу бумаги. Microsoft Windows 3.0 позволяет изменить параметры принтера в соответствии с нуждами вашей прикладной программы (например, ориентацию вывода на "ландшафтную" или тип бумаги). После определения новых параметров прикладная программа может печатать, используя новые параметры. Поскольку параметры на разных принтерах различаются, прикладная программа для изменения их должна взаимодействовать с драйвером принтера. Большинство драйверов принтеров Windows содержат специальные функции для упрощения управления параметрами принтера. В данной главе описано, как использовать эти функции для манипулирования параметрами принтеров. Приводится информация по следующим темам: - Как Windows управляет параметрами принтеров. - Использование функций драйверов устройств. - Получение характеристик драйвера принтера. - Манипулирование параметрами принтеров. - Копирование параметров с одного принтера на другой. - Измененение параметров принтера пользователем. - Работа с драйверами, написанными для предыдущих версий Windows. . Windows 3.0/pg/3#3 = 56 = 17.1 Как Windows управляет параметрами принтеров. При выводе на печать прикладная программа использует контекст устройства принтера, созданный с помощью функции CreateDC. При создании контекста устройства принтера прикладная программа определяет для него драйвер, имя, порт и, необязательно, параметры принтера для данного драйвера. Эти параметры зависят от устройства; каждый набор является специфичным для конкретного принтера и драйвера. Поскольку точные параметры могут отличаться для каждого принтера, прикладная программа должна передавать каждому принтеру только распознаваемые им параметры. Когда прикладная программа при подготовке к печати вызывает CreateDC для создания контекста устройства для принтера, Windows создает контекст устройства, используя первый найденный набор параметров. Параметры ищутся в следующем порядке: 1. Windows пытается в начале использовать параметры (если они есть), посылаемые функции CreateDC в параметре lpInitData. 2. Если при вызове функции CreateDС прикладная программа не передает параметров принтера, Windows ищет последние параметры, последними сохраненные в памяти драйвером принтера с помощью функции SetEnvironment. 3. Если драйвер принтера еще не сохранял такие данные с помощью SetEnvironment, Windows ищет параметры принтера в файле WIN.INI. 4. Если WIN.INI не содержит полного набора параметров, драйвер принтера заполняет пробелы собственными параметрами, используемыми по умолчанию. Наибольшее управление параметрами принтера вы можете обеспечить, передавая их при вызове функции CreateDC. Если вы определите параметры принтера с помощью CreateDC, то Windows будет использовать их вместо всех параметров из файла WIN.INI или драйвера принтера. 17.1.1. Параметры принтера и структура DEVMODE. Обычно, параметры принтера используются в форме структуры DEVMODE. Например, если вы посылаете параметры принтера при вызове функции CreateDC, вы в действительности посылаете указатель на структуру DEVMODE. (Исключением является файл WIN.INI, в котором параметры принтера содержатся в виде текстовых строк.) Обычно, прикладные программы не создают самостоятельно структуру DEVMODE, вместо этого они получают указатель на нее от драйвера принтера и при необходимости модифицируют ее содержимое. Этот метод гарантирует, что структура будет полной и правильной. . Windows 3.0/pg/3#3 = 57 = Структура DEVMODE включает три типа информации: Информация Описание --------------------------------------------------------------- Информация заголовка Первые пять полей содержат информацию заголовка структуры. Эта информация включает имя принтера (например, "PCL/HP Laserjet"), версию, и информацию о раз- мере структуры DEVMODE. Вы обязаны ука- зать полную информацию заголовка. Аппаратно-независимая Большинство полей DEVMODE содержат ап- информация паратно-независимую информацию, такую как ориентация, размер бумаги и число копий. Хотя структура DEVMODE содержит полный набор аппаратно-независимых пара- метров, некоторые принтеры не поддержи- вают ни одного из них. Например, боль- шинство принтеров может печатать только на одной стороне бумаги, и драйверы таких принтеров должны игнорировать поле dmDuplex структуры DEVMODE, которое оп- ределяет двухстороннюю печать. Аппаратно-зависимая Поле dmDriverData структуры DEVMODE информация содержит аппаратно-зависимые данные, ко- торые определяются для каждого драйвера устройства. Обычно, прикладная программа не должна изменять эти данные, а просто передавать их драйверу. --------------------------------------------------------------- Чтобы при вызове CreateDC передать полную структуру DEVMODE, лучше всего вначале получить ее с помощью функции ExtDeviceMode (включается в драйверы принтеров Windows 3.0). Эта функции сообщает драйверу принтера, что необходимо создать структуру DEVMODE c текущими параметрами принтера. Поскольку драйвер создает структуру самостоятельно и заполняет ее аппаратно-зависимой информацией, вы можете рассматривать ее как законченную. Затем ваша прикладная программа может послать структуру DEVMODE функции CreateDC. Описание функции DEVMODE приведено в Справочном руководстве, том 2. Функция CreateDC описана в первом томе Справочного руководства. 17.1.2. Параметры принтера и среда принтера "Средой принтера" называют набор его параметров, находящийся в памяти. Может быть по одной среде принтера на каждый порт. Текущий драйвер принтера (если пользователь установил его для порта) отвечает за создание и поддержку среды . Windows 3.0/pg/3#3 = 58 = принтера для данного порта. Параметры среды для каждого принтера аналогичны используемым в файле WIN.INI, за исключением того, что в файле WIN.INI они рассматриваются как строки, в то время как в среде они содержатся в памяти в форме структуры DEVMODE. Поддержка информации в памяти увеличивает скорость создания контекста устройства для принтера. Когда прикладная программа создает контекст устройства для принтера без определения собственных параметров, Windows использует параметры из среды принтера. Поскольку среда принтера связана с портом принтера, изменение среды принтера повлечет за собой изменение параметров принтера для всех прикладных программ, которые не задают собственных параметров. При использовании драйверов принтера, написанных для Windows версии 3.0, прикладная программа может манипулировать параметрами принтера для удовлетворения собственных нужд. Ее изменения не повлияют на другие прикладные программы использующие тот же порт. (При использовании драйверов принтеров для более ранных версий Windows прикладная программа могла изменять параметры принтера, только изменяя файл WIN.INI или среду принтера. Это влияло на все прикладные программы, исползующие этот порт без установки собственных параметров.) 17.2 Использование функций драйверов устройств. Большинство драйверов принтеров включает специальные функции, которые позволяют прикладной программе манипулировать параметрами драйвера и порта принтера. - Старые драйверы принтеров содержат функцию DeviceMode. Эта функция отображала панель диалога в которой пользователь мог установить параметры принтера, такие как ориентация и размер бумаги. Изменения, внесенные пользователем, влияли на файл WIN.INI и на среду принтера. - Драйверы принтеров Windows версии 3.0 содержат функцию ExtDeviceMode, которая предоставляет прикладной программе много возможностей по манипулированию параметрами принтера без влияния на другие прикладные программы. Эта функция позволяет пользователю получить копию параметров принтера в структуре DEVMODE. Прикладная программа может затем использовать эту структуру для изменения параметров принтера, вместо того, чтобы создавать ее самостоятельно. (ExtDeviceMode также содержит все возможности, которые предоставляла функция DeviceMode в ранних версиях Windows). . Windows 3.0/pg/3#3 = 59 = - Драйверы Windows версии 3.0 также содержат функцию DeviceCapabilities. Эта функция позволяет прикладной программе определить какие поля структуры DEVMODE поддерживаются данным принтером. Функции драйвера устройства являются частью самого драйвера, а не обычными функциями Windows. Поэтому для вызова функций драйвера необходимо следовать следующей процедуре: 1. Загрузить драйвер в память с помощью функции LoadLibrary. 2. С помощью функции GetProcAddress получить адрес необходимой функции. (Если функция GetProcAddress возвращает нулевой указатель, это означает, что данный драйвер не поддерживает эту функцию.) 3. Для вызова функции драйвера используйте адрес, возвращаемый функцией GetProcAddress. 4. После завершения использования функций драйвера, с помощью функции Windows FreeLibrary выгрузите драйвер устройства из системы. Ниже приведен пример, как вызвать функцию ExtDeviceMode для драйвера PSCRIPT.DRV: FARPROC lpfnExtDeviceMode; FARPROC lpfnDeviceMode; HANDLE hDriver; hDriver = LoadLibrary("PSCRIPT.DRV); lpfnExtDeviceMode = GetProcAddress(hDriver,"ExtDeviceMode"); if(lpfnExtDeviceMode != NULL) { /* Если драйвер поддерживает функцию ExtDeviceMode, вызвать функцию ExtDeviceMode через адрес, содер- жащийся в lpfnExtDeviceMode. */ } else { /* Если драйвер не для версии 3.0 и не поддерживает новые функции, то вместо этого использовать функцию DeviceMode */ lpfnDeviceMode = GetProcAddress(hDriver,"DeviceMode"); if(lpfnExtDeviceMode != NULL) { /* Если драйвер поддерживает функцию DeviceMode, вызвать функцию DeviceMode через адрес, содер- жащийся в lpfnDeviceMode. */ . Windows 3.0/pg/3#3 = 60 = } } FreeLibrary(hDriver); /* после завершения выгрузить драйвер */ 17.3 Получение характеристик драйвера принтера. Функция драйвера DeviceCapabilities позволяет вам определить возможности конкретного принтера, включая поля структуры DEVMODE, которые поддерживает данный принтер. Например, если ваша программа зависит от "ландшафтной" ориентации, она может вызвать функцию DeviceCapabilities, чтобы определить, поддерживает ли данный принтер такую ориентацию. Функция DeviceCapabilities подробно описана в первом томе Справочного руководства. 17.4 Работа с параметрами принтера. Функция ExtDeviceMode позволяет одновременно выполнять несколько действий. Вы можете использовать ее для: - Получения структуры DEVMODE, содержащей текущие параметры принтера. - Изменения одного и более параметров принтера. - Запроса у пользователя параметров принтера. - Сброса среды принтера и информации в WIN.INI. Поскольку ExtDeviceMode выполняет столько различных функций вы возможно обнаружите, что программа часто вызывает функцию ExtDeviceMode на протяжении получения, изменения и поддержки параметров принтера. При вызове ExtDeviceMode вы указываете: - Дескриптор модуля необходимого вам драйвера принтера (его возвращают функции LoadLibrary и GetModuleHandle). - Имя драйвера принтера (Например, "PCL/HP Laserjet"). - Имя порта, к которому подключен принтер (например, "LPT2:"). - Операцию, которую необходимо выполнить. Вы определяете различные операции, указывая соответствующие значения в параметре wMode. Если вы хотите выполнить несколько операций одновременно, вы можете комбинировать два или более значений с помощью битовой операции OR ("|"). . Windows 3.0/pg/3#3 = 61 = - Буфер для ввода (если есть). Прикладная программа может в качестве буфера ввода указать полную или часть структуры DEVMODE. (В отличие от других функций, использующих структуру DEVMODE, ExtDeviceMode не требует, чтобы структура DEVMODE была полной.) - Выходной буфер (если есть). По запросу прикладной программы драйвер выводит в выходной буфер полную структуру DEVMODE. Примечание: Функция ExtDeviceMode требует в действительности восемь параметров. Выше приведены только те параметры, которые относятся к нашему обсуждению. Полное описание параметров функции ExtDeviceMode вы найдете в первом томе Справочного руководства. 17.4.1 Определение ввода и вывода функции ExtDeviceMode. Параметр wMode определяет, как функция ExtDeviceMode получает ввод и куда она производит вывод. Реакция драйвера зависит от указанного значения (значений). Если вы устанавливаете параметр wMode равным нулю, то функция ExtDeviceMode возвращает просто размер в байтах структуры DEVMODE. Обычно, таким образом выполняют первый вызов функции ExtDeviceMode, чтобы определить размер буфера для вывода. Вы можете присвоить параметру wMode одно или больше значений. Они приведены в таблице 17.1. Для каждого значения в таблице указано следующее: - Его имя. - Чем управляет: вводом или выводом. - Короткое описание каждого значения. Таблица 17.1 Значения параметра wMode. --------------------------------------------------------------- Значение Ввод/Вывод Описание --------------------------------------------------------------- DM_IN_BUF Ввод Сообщает драйверу, что необхо- димо изменить параметры прин- тера в соответствии с передан- ными во входном буфере в струк- туре DEVMODE. DM_IN_PROMPT Ввод Сообщает драйверу принтера, что . Windows 3.0/pg/3#3 = 62 = необходимо вывести панель диа- лога Print Setup и изменить текущие параметры в соотвествии с указанными пользователем. DM_OUT_BUF Вывод Вывести текущие параметры прин- тера в выходной буфер в форме структуры DEVMODE. DM_OUT_DEFAULT Вывод Вывести текущие параметры принтера в среду принтера и в файл WIN.INI. --------------------------------------------------------------- Вы можете использовать комбинации значений wMode, чтобы позволить управлять параметрами принтера как вашей прикладной программе, так и пользователю. Важное примечание: Для изменения параметров принтера вы должны указать по крайней мере одно входное значение и одно выходное. Например, для получения параметров от пользователя и установки их в среде принтера и в файле WIN.INI вы можете использовать комбинацию значений DM_IN_PROMPT и DM_OUT_DEFAULT. Если вы укажите только выходные значения (DM_OUT_BUF или DM_OUT_DEFAULT), драйвер выведет текущие параметры, игнорируя любые указанные вами входные. Если вы определите только входные параметры (DM_IN_PROMPT или DM_IN_BUF), вызов ExtDeviceMode приведет только к вводу, и любой вывод в данном случае не будет иметь никакого эффекта. 17.4.2 Получение копии параметров принтера. Часто удобно при работе с параметрами принтера иметь текущии параметры конкретного принтера. Это позволяет прикладной программе определить, соответствуют ли они ее нуждам. Для получения копии параметров драйвера принтера: 1. Определите, сколько места требует выходная структура DEVMODE. Для этого вызовите ExtDeviceMode с параметром wMode равным 0. Функция ExtDeviceMode возвращает размер в байтах структуры DEVMODE (которая может быть создана вызовом ExtDeviceMode с wMode равным DM_OUT_BUF). 2. Выделите буфер такого размера. 3. Вызовите ExtDeviceMode снова. Передаваемые ей параметры должны содержать следующую информацию: . Windows 3.0/pg/3#3 = 63 = Параметр Значение ------------------------------------------------------- lpDEVMODEoutput Указатель на выходной буфер, который вы выделили. wMode DM_OUT_BUF ------------------------------------------------------- Драйвер принтера затем помещает структуру DEVMODE с текущими параметрами принтера в указанный вами буфер. Поскольку выходной буфер содержит полную структуру DEVMODE, вы можете затем ее передавать функциям CreateDC и SetEnvironment, т.к. они обе требуют в качестве входной структуру DEVMODE. Примечание: Вызов функции ExtDeviceMode c параметром wMode равным DM_OUT BUF аналогичен вызову GetEnvironment, т.к. в обеих случаях возвращаются параметры, используемые по умолчанию. Отличие заключается в том, что функция ExtDeviceMode работает всегда, т.к. работает с драйвером, а GetEnvironment работает верно только в том случае, если драйвер до этого вызывал функцию SetEnvironment. 17.4.3 Изменение параметров принтера. Часто, при выводе информации на принтер прикладной программе требуется изменить текущие параметры принтера. Для изменения параметров принтера укажите в параметре wMode как ввод (DM_IN_BUF или DM_IN_PROMPT), так и вывод (DM_OUT_BUF или DM_OUT_DEFAULTS). Вы может указать несколько значений, до тех пор, пока имеется одно значение для ввода и одно для вывода. (Для изменения параметров без влияния на другие прикладные программы не используйте DM_OUT_DEFAULTS. Это значение приводит к изменению параметров, используемых по умолчанию, на указанные вами). Входные значения можно указать нескольким способами. Каждый из них требует своей комбинации значений wMode. Это следующие способы: - Передаете частичную структуру DEVMODE с новыми необходимыми параметрами. (При вызове ExtDeviceMode указываете значение DM_IN_BUF). - Выводите панель диалога Printer Setup, чтобы пользователь мог указать требуемые ему параметры. (При вызове ExtDeviceMode указываете значение DM_IN_PROMPT). - Передаете частичную структуру DEVMODE и, кроме этого, выводите панель диалога Printer Setup. Этот способ позволяет указать параметры принтера как пользователю, так и вашей программе. (При вызове ExtDeviceMode . Windows 3.0/pg/3#3 = 64 = указываете значение DM_IN_BUF и DM_IN_PROMPT). При изменении параметров принтера вы должны не только указать новые параметры, но и определить куда вы хотите их занести. Драйвер выводит полную и корректную структуру DEVMODE, которая отражает изменения, внесенные вашей программой и/или пользователем. Вы указываете драйверу, куда поместить выходную структуру. Это вы делаете с помощью параметра wMode. Вы можете направить вывод следующим образом: - Поместите модифицированную выходную структуру DEVMODE в выходной буфер. Затем прикладная программа может передать эту структуру функции CreateDC или другой функции Windows. (При вызове ExtDeviceMode укажите DM_OUT_BUF). - Выведите модифицированную структуру DEVMODE в память с помощью функции SetEnvironment. Когда драйвер принтера делает это, он сбрасывает в исходное состояние среду принтера и изменяет соответствующие элементы файла WIN.INI. Новые параметры влияют на все прикладные программы, использующие этот же порт принтера и не указывающие собственные параметры. (При вызове ExtDeviceMode укажите DM_OUT_DEFAULT). - Поместить модифицированную структуру в выходной буфер, сбросить в исходное состояние среду принтера и модифицировать файл WIN.INI. (При вызове ExtDeviceMode укажите DM_OUT_BUF и DM_OUT_DEFAULT). Оставшаяся часть данного раздела посвящена некоторым общим путям использования и комбинирования возможностей функции ExtDeviceMode. 17.4.4 Приспособление параметров принтера для использования с CreateDC. Для использования принтера ваша программа должна вначале с помощью функции CreateDC создать его контекст устройства. Эта функция имеет необязательный параметр, lpInitData, который определяет параметры принтера, используемые при создании его контекста. Простейший способ - это указать в качестве этого параметра NULL. Windows тогда создаст контекст устройства, используя текущие параметры для данного порта принтера. Чтобы использовать собственные параметры принтера, вы должны их передать при вызове функции CreateDC. Тогда Windows создаст контекст устройства, используя эти параметры. Для передачи параметров функции CreateDC, используется структура DEVMODE, которая содержит необходимые вам параметры. . Windows 3.0/pg/3#3 = 65 = При вызове функции CreateDC, вы указываете структуру DEVMODE, полученную прямо от драйвера принтера. Хотя можно просто отредактировать структуру DEVMODE, а затем передать ее CreateDC, лучше так не делать. CreateDC требует полную и корректную структуру DEVMODE. Таким образом, любые противоречия в структуре DEVMODE могут привести к созданию неверного контекста устройства. Чтобы убедиться, что структура DEVMODE корректна, вы можете послать ее драйверу в качестве входной. Драйвер возвращает полную, корректную структуру, которую вы спокойно передаете CreateDC. Для использования конкретных параметров принтера передайте частичную структуру DEVMODE, содержащую только необходимые вам параметры. Драйвер в этом случае изменит только те параметры, для которых вы указали новые значения. Это означает, что вы можете изменять отдельные параметры принтера - например для изменения ориентации с "портретной" на "ландшафтную" - без влияния на другие параметры. В качестве вывода ExtDeviceMode возвращает полную структуру DEVMODE. Для изменения параметров принтера выполните следующее: 1. Определите полную или частичную структуру DEVMODE, содержащую параметры, которые вы хотите изменить. Если вы определяете частичную структуру: - Убедитесь, что включили все пять полей заголовка (dmDeviceName, dmSpecVersion, dmDeviceVersion, smSize и dmDriverExtra). Если вы не посылаете аппаратно-зависимой информации, установите значения полей dmDriverVersion и dmDriverExtra в ноль. - Установите содержимое поля dmFields для определения того, какие поля аппаратно-независимой информации вы определяете. Например, для указания принтеру использовать "ландшафтную" ориентацию и размер страницы для писем вы должны определить следующую структуру DEVMODE: DEVMODE dm; lstrcpy(dm.dmDeviceName,szDeviceName); /* информация заголовка */ dm.dmVersion = DM_SPECVERSION; dm.dmDriverVersion = 0; dm.dmSize = sizeof(DEVMODE); dm.dmDriverExtra = 0; /* аппаратно-независимая информация */ dm.dmFields = DM_ORIENTATION | DM_PAPERSIZE; dm.dmOrientation = DMORIENT_LANDSCAPE; dm.dmPaperSize = DMPAPER_LETTER; . Windows 3.0/pg/3#3 = 66 = Первые пять полей определяют информацию заголовка. szDeviceName - это строка, содержащая имя устройства, такое как "PCL/HP Laserjet". Получение значений из файла WIN.INI описано в главе 12, "Печать". 2. Вызовите функцию ExtDeviceMode. Вы должны передать ей параметры, содержащие следующую информацию: Параметр Значение ------------------------------------------------------- lpDevModeInput Указатель на буфер, содержащий полную или частичную структуру DEVMODE. lpDevModeOutput Указатель на выходной буфер. wMode DM_IN_BUF|DM_OUT_BUF ------------------------------------------------------- Затем драйвер изменит параметры в соответствии с переданными во входной структуре DEVMODE и поместит в выходной буфер полную структуру DEVMODE. 3. Передайте полученную в результате структуру DEVMODE функции CreateDC для создания контекста устройства, использующего новые параметры. После изменения структуры DEVMODE драйвер копирует ее в выходной буфер. Полученная структура будет полной, и будет включать все изменения, которые вы определили в частичной структуре. Поскольку драйвер проверяет ваши параметры, вы можете спокойно передавать структуру DEVMODE функции CreateDC. 17.4.5 Изменение параметров принтера без влияния на другие прикладные программы. Ваша прикладная программа может изменять параметры принтера, не оказывая влияния на другие прикладные программы. Для этого нужно сделать следующее: 1. Вызовите ExtDeviceMode. Параметры должны включать следующую информацию: Параметр Значение ------------------------------------------------------- lpDevModeInput Указатель на буфер, содержащий полную или частичную структуру DEVMODE. lpDevModeOutput Указатель на выходной буфер. wMode DM_IN_BUF|DM_OUT_BUF . Windows 3.0/pg/3#3 = 67 = или DM_IN_PROMPT|DM_OUT_BUF или DM_IN_BUF|DM_IN_PROMPT|DM_OUT_BUF ------------------------------------------------------- Заметим, что вы можете указать и оба входных параметра (DM_IN_BUF|DM_IN_PROMPT). Такой вызов ExtDeviceMode приведет к тому, что ваша собственная копия параметров принтера будет сохранена в буфере вашей прикладной программы. Поскольку не указано значение DM_OUT_DEFAULT, то не происходит копирования новых параметров в среду принтера и в файл WIN.INI. Таким образом на другие прикладные программы влияние не оказывается. 2. Выходную структуру DEVMODE передайте функции CreateDC для создания контекста устройства в соответствии с указанными вами параметрами. Примечание: Вы можете сохранить выходную структуру DEVMODE в файле, чтобы в последующих сеансах работы вашей программы не вызывать функцию ExtDeviceMode, а считать ее из файла и сразу послать функции CreateDC. 17.4.6 Запрос у пользователя изменения параметров принтера. Прикладная программа может передать драйверу требование отобразить панель диалога Printer Setup. В этой панели диалога пользователь может указать новые параметры принтера. Драйвер изменит текущие параметры в соответствии с указанными пользователем. Выходная структура DEVMODE (если она указана) будет содержать сделанные пользователем изменения. Для запроса у пользователя параметров принтера сделайте следующее: 1. Вызовите ExtDeviceMode. Параметры должны включать следующую информацию: Параметр Значение ------------------------------------------------------- lpDevModeOutput Указатель на выходной буфер. wMode DM_IN_PROMPT|DM_OUT_BUF ------------------------------------------------------- Затем драйвер отображает панель диалога Printer Setup, которая позволяет пользователю указать новые параметры. Если пользователь нажимает на мягкую клавишу "OK" после выбора параметров принтера, функция ExtDeviceMode . Windows 3.0/pg/3#3 = 68 = возвратит значение IDOK и драйвер поместит структуру DEVMODE в выходной буфер. Эта выходная структура будет содержать изменения, сделанные пользователем. Если пользователь нажмет клавишу "Cancel", функция вернет значение IDCANCEL, и выходная структура не будет содержать изменений, сделанных пользователем. 2. Выходную структуру DEVMODE передайте функции CreateDC для создания контекста устройства в соответствии с указанными пользователем параметрами. Установка значений в панели диалога Printer Setup. Для начальной установки значений, которые должны отражаться в панели диалога Printer Setup, ваша прикладная программа должна просто передать драйверу входную структуру DEVMODE с начальными параметрами и указать драйверу, что необходимо отобразить панель диалога. В этом случае драйвер отображает панель диалога с начальными значениями, установленными в соответствии с параметрами во входной структуре DEVMODE. Затем пользователь может изменить некоторые или все параметры. После того, как пользователь нажмет OK драйвер вернет структуру DEVMODE, которая будет отражать сделанные им изменения. Кроме этого, те параметры, которые пользователь не изменил, будут также отражаться в выходной структуре. Для запроса у пользователя параметров с помощью панели диалога, отображающей начальные значения, сделайте следующее: 1. Создайте частичную или полную структуру DEVMODE и занесите в нее начальные значения параметров, которые вы хотите изменить. (Описание частичной структуры DEVMODE вы найдете в разделе 17.4.4, "Приспособление параметров принтера для использования с CreateDC".) 2. Вызовите ExtDeviceMode. Параметры должны включать следующую информацию: Параметр Значение ------------------------------------------------------- lpDevModeInput Указатель на буфер, содержащий полную или частичную структуру DEVMODE. lpDevModeOutput Указатель на выходной буфер. wMode DM_IN_BUF|DM_IN_PROMPT|DM_OUT_BUF ------------------------------------------------------- Вначале, драйвер изменяет собственные параметры в соответствии с переданными вами. Затем, он отображает панель диалога Printer Setup с новыми параметрами, и . Windows 3.0/pg/3#3 = 69 = пользователь все из них или только некоторые изменяет. Если пользователь нажимает на мягкую клавишу "OK" после выбора параметров принтера, функция ExtDeviceMode возвратит значение IDOK, и драйвер поместит структуру DEVMODE в выходной буфер. Эта выходная структура будет содержать изменения, сделанные пользователем. Если пользователь нажмет клавишу "Cancel", функция вернет значение IDCANCEL и выходная структура не будет содержать изменений, сделанных пользователем. 3. Выходную структуру DEVMODE передайте функции CreateDC для создания контекста устройства в соответствии с новыми параметрами. 17.5 Копирование параметров принтера между драйверами. Для копирования параметров принтера с одного драйвера на другой сделайте следующее: 1. Скопируйте структуру DEVMODE первого драйвера, как это описано в разделе 17.4.2, "Получение копии параметров принтера". 2. Удалите аппаратно-зависимую информацию из выходной структуры DEVMODE. Для этого приравняйте к нулю поля dmDriverVersion и dmDriverExtra. 3. Занесите в поле dmDeviceName имя второго устройства. 4. Вызовите функцию ExtDeviceMode второго принтера. Указываемые вами параметры должны включать следующую информацию: Параметр Значение ------------------------------------------------------- lpDevModeInput Указатель на буфер, содержащий полную или частичную структуру DEVMODE. lpDevModeOutput Указатель на выходной буфер. wMode DM_IN_BUF|DM_OUT_BUF ------------------------------------------------------- Затем, второй драйвер помещает корректную, полную структуру в выходной буфер. Выходная структура отражает аппаратно-независимые параметры, которые прикладная программа скопировала из первого драйвера, но содержит аппаратно-зависимую информацию для второго драйвера. . Windows 3.0/pg/3#3 = 70 = 17.6 Поддержка собственных параметров принтера. Windows версии 3.0 позволяет вам использовать параметры принтера, специфичные для вашей прикладной программы или даже для отдельного документа. Для этого сохраните структуру DEVMODE с параметрами, которые вы будете использовать по умолчанию. Вы можете сохранить их или в файле параметров прикладной программы или в файле с документом в качестве его части для параметров, специфичных для документа. 17.7 Работа со старыми драйверами принтеров. Драйверы принтеров, написанные для предыдущей версии Windows поддерживают только функцию DeviceMode, которая отображает панель диалога в которой пользователь может установить необходимые параметры, такие как ориентация и размер страницы. Сделанные изменения влияют на всю систему, а не только на данную прикладную программу. Как и остальные функции драйверов устройств, DeviceMode является функцией драйвера, а не GDI. (Вызов функций драйверов устройств описан в разделе 17.2, "Использование функций драйверов устройств".) При вызове функции драйвера DeviceMode, драйвер отображает панель диалога Printer Setup. Затем пользователь в ней может изменить параметры принтера и порта. Ниже показано, как для вызова функции DeviceMode использовать адрес процедуры lpfnDeviceMode: if(lpfnDeviceMode != NULL) /* Если драйвер поддерживает эту функцию */ { (*lpfnDeviceMode)( (HWND)hWnd, /* Дескриптор родительского окна */ (HANDLE)hDriver, /* Дескриптор модуля драйвера */ (LPSTR)"PSCRIPT", /* Имя принтера */ (LPSTR)"LPT1:"); /* Имя порта */ } 17.8 Заключение. В данной главе описано, как использовать функции драйверов принтеров для манипулирования параметрами принтера. Основная причина изменения параметров принтера заключается в том, что прикладная программа может послать свои собственные параметры функции CreateDC при подготовке к печати. Затем Windows устанавливает контекст принтера, используя параметры, передаваемые прикладной программой, вместо параметров по умолчанию. Кроме этого, функции драйвера позволяют вашей программе изменить параметры принтера, не оказывая влияния на другие прикладные программы, использующие тот же драйвер. Дополнительную информацию, относящуюся к параметрам принтера вы найдете: . Windows 3.0/pg/3#3 = 71 = Раздел Руководство --------------------------------------------------------------- Печать из среды Руководство программиста: глава 12, Windows "Печать". Функции Справочное руководство, том 1: Глава 2, ExtDeviceMode, "Функции интерфейса графических устройств", DeviceCapabilities, и глава 4, "Список функций". DeviceMode и CreateDC Структура DEVMODE Справочное руководство, том 2: глава 7, "Типы и структуры данных". Пример инициализации Прикладная программа MULTIPAD.EXE, вклю- принтера ченная на диск "SDK Sample Source Disk". Написание драйверов Microsoft Windows Device Development принтеров Windows Kit. . Windows 3.0/pg/3#3 = 72 = Глава 18. Шрифты. ---------------------------------------------------------------- Windows предлагает широкий спектр возможностей для вывода текста и, в частности, выбор используемого шрифта. Шрифт - это группа символов, которая имеет определенные сочетание высоты, ширины, начертания, набора символов и других атрибутов. Прикладная программа использует шрифты для отображения на экране или печати текста различного внешнего вида и размера. Например, программы текстовой обработки используют шрифты, чтобы обеспечить интерфейс типа "что видишь, то и получаешь". В данной главе приведена следующая информация: - Использование шрифтов в прикладной программе. - Создание ресурса шрифтов, которыми может пользоваться ваша и другие прикладные программы. Кроме этого, в данной главе описано создание примера программы ShowFont, которая иллюстрирует использование этих возможностей. 18.1 Запись текста. Можно вывести текст с помощью заданного шрифта, выбрав этот шрифт и применив функцию TextOut. Эта функция выводит символы строки, используя шрифт, выбранный в данный момент в контексте устройства. В приведенном ниже примере показано, как записать строку "Пример строки": hDC = GetDC(hWnd); TextOut(hDC, 10, 10, "Пример строки", 13); ReleaseDC(hWnd, hDC); В данном примере функция TextOut начинает строку с точки с координатами (10,10) и печатает все 13 символов строки. Шрифт по умолчанию для контекста устройства - это системный шрифт. Это шрифт с фиксированной шириной, представляющий символы в наборе символов ANSI. Название шрифта - "System". Windows использует системный шрифт в меню, заголовках окон и в другом тексте. 18.2 Использование цвета при записи текста. К выводимому тексту можно добавить цвет, установив цвета текста и фона контекста устройства. Цвет текста определяет цвет выводимых символов; цвет фона определяет цвет ячейки символа, за исключением самого символа. При выводе текста GDI полностью выводит ячейку символа (прямоугольник, окаймляющий символ). Ячейка символа обычно имеет ту же ширину и высоту, что и сам . Windows 3.0/pg/3#3 = 73 = символ. Можно установить цвета текста и фона с помощью функции SetTextColor и SetBkColor. В приведенном ниже примере устанавливается красный цвет текста и зеленый цвет фона: SetTextColor(hDC, RGB(255, 0, 0)); SetBkColor(hDC, RGB(0, 255, 0)); Когда контекст устройства создается впервые, цвет текста - черный, а цвет фона - белый. Эти цвета можно изменить в любой момент. Примечание: Если используется общий контекст отображения, полученный с помощью GetDC или BeginPaint, цвета теряются при каждом освобождении контекста, так что их необходимо устанавливать при каждом получении контекста изображения. Цвет фона применяется только в том случае, когда режим фона - непрозрачный. Режим фона определяет, влияет ли как-либо цвет фона в ячейке символа на то, что уже находится на экране дисплея. Если режим непрозрачный, цвет фона забивает все уже находящиеся на экране. Если режим - прозрачный, то сохраняется все, что находится на экране. Режим фона можно установить с помощью функции SetBkMode; получить текущий режим можно с помощью функции GetBkMode. Аналогично, можно получить цвет текущего текста и фона, используя функции GetTextColor и GetBkColor. 18.3 Использование заказных шрифтов. Пользователь не ограничен использованием только системного шрифта в своей прикладной программе. GDI предлагает ряд шрифтов на заказ. Для использования в программе шрифтов на заказ необходимо специфицировать тип шрифта в функции GetStockObject. Эта функция создает шрифт по запросу и возвращает дескриптор шрифта, который можно использовать для выбора в контексте устройства. GDI содержит следующие шрифты: Шрифт Описание --------------------------------------------------------------- ANSI_FIXED_FONT Определяет шрифт с фиксированным шагом, базирующийся на наборе символов в коде ANSI. Обычно используется шрифт Courier, если он доступен. ANSI_VAR_FONT Определяет шрифт с переменной шириной, базирующийся на наборе символов в коде ANSI. Обычно используется шрифт Helvetica, если он доступен. . Windows 3.0/pg/3#3 = 74 = DEVICEDEFAULT_FONT Определяет шрифт, наиболее подходящий для данного устройства. Этот шрифт зависит от того, как разметчик шрифта GDI интерпретирует запросы на шрифт, так что шрифт может значительно меняться от устройства к устройству. OEM_FIXED_FONT Определяет шрифт с фиксированным шагом, базирующийся на наборе символов в коде OEM. Этот набор символов меняется от системы к системе. Для персональных ЭВМ типа IBM PC и совместимых с ними шрифт OEM базируется на наборе символов IBM PC. SYSTEM_FONT Определяет системный шрифт. Это шрифт с фиксированным шагом, базирующийся на наборе символов в коде ANSI и используемый системой для индицирования заголовков окон, имен меню и текста в панелях диалога. Системный шрифт всегда доступен. Другие шрифты доступны только в том случае, если они инсталлированы. --------------------------------------------------------------- Для того, чтобы использовать шрифт на заказ, создайте его с помощью функции GetStockObject и затем выберите дескриптор шрифта в контексте устройства, используя функцию SelectObject. Все последующие вызовы функции TextOut будут использовать выбранный шрифт. В приведенном ниже примере показано, как использовать шрифт ANSI с переменной шириной: HFONT hFont; HFONT hOldFont; . . . hFont = GetStockObject(ANSI_VAR_FONT); if (hOldFont = SelectObject(hDC, hFont)) { TextOut(hDC, 10, 10, "Пример строки", 13); SelectObject(hDC, hOldFont); } Необходимо выбрать шрифт (как и другие объекты GDI) перед тем, как использовать его в операциях вывода. Функция SelectObject выбирает созданный пользователем шрифт и возвращает его дескриптор. Системный шрифт на заказ всегда доступен, даже если нет других шрифтов на заказ. В этом случае функция GetStockObject возвращает дескриптор системного шрифта. . Windows 3.0/pg/3#3 = 75 = 18.4 Создание логического шрифта. Логический шрифт - это перечень атрибутов шрифта таких, как высота, ширина, набор символов и начертание, которые использует GDI при выборе шрифта для вывода текста. Логический шрифт можно создать с помощью функции CreateFont. Эта функция возвращает дескриптор логического шрифта. Можно использовать этот дескриптор в функции SelectObject для выбора этого шрифта в контексте устройства. При выборе логического шрифта GDI подбирает физический шрифт, опираясь на ваш запрос. GDI пытается подобрать физический шрифт, который наиболее точно соответствует заданному логическому шрифту, но если это невозможно, то берет наиболее соответствующий ему. В приведенном ниже примере функция CreateFont создает логический шрифт. hFont = CreateFont( 10, /* lfHeight */ 8, /* lfWidth */ 0, /* lfEscapement */ 0, /* lfOrientation */ FN_NORMAL, /* lfWeight */ FALSE, /* lfItalic */ FALSE, /* lfUnderline */ FALSE, /* lfStrikeOut */ ANSI_CHARSET, /* lfCharSet */ OUT_DEFAULT_PRECIS, /* lfOutPrecision */ CLIP_DEFAULT_PRECIS, /* lfClipPrecision */ DEFAULT_QUALITY, /* lfQuality */ FIXED_PITCH | FF_MODERN, /* lfPitchAndFamily */ "System" /* lfFaceName */ ); Данный логический шрифт запрашивает шрифт с фиксированным шагом, в котором каждый символ имеет высоту 10 пикселей и ширину 8 пикселей. Размеры шрифта всегда задаются в пикселях. Запрашиваемые угол наклона строки и символа равны нулю; это означает, что базовая линия, вдоль которой индицируются символы - горизонтальна и ни один символ не будет повернут. FW_NORMAL - это запрашиваемая яркость. Другими значениями яркости могут быть FW_BOLD (жирный шрифт) и FW_LIGHT (яркий шрифт). Курсив, подчеркивание и перечеркивание символов не требуются. Запрашиваемый набор символов - ANSI, стандартный набор символов Windows. Запрашиваются точность вывода шрифта, точность вырезания и качество по умолчанию. Эти атрибуты воздействуют на способ отображения символов. Установка этих атрибутов в значения по умолчанию позволяет устройству отображения использовать все преимущества, предоставляемые своими собственными возможностями по индицированию символов. Запрашиваемое семейство шрифтов - FF_MODERN. Наименование шрифта - "System". Когда функция SelectObject получает логический шрифт, она проверяет пул имеющихся в наличии шрифтов, чтобы найти шрифт, . Windows 3.0/pg/3#3 = 76 = удовлетворяющий запрошенным атрибутам. Если функция находит в точности то, что необходимо, она возвращает дескриптор этого шрифта. Если она не находит точного соответствия, то выбирает наиболее соответствующий шрифт и возвращает его дескриптор. В некоторых случаях функция SelectObject может не найти точную копию искомого логического шрифта, но тем не менее может синтезировать запрашиваемый шрифт, используя наиболее соответствующий шрифт из существующих. Например, если доступен только системный шрифт, имеющий в высоту 5 пикселей, а логический шрифт выбран высотой в 10 пикселей, функция SelectObject может синтезировать запрашиваемый шрифт, удвоив высоту. В таких случаях функция SelectObject возвращает для вывода текста синтезированный шрифт. 18.5 Использование нескольких шрифтов в одной строке. Если разрабатывается программа, которая использует несколько шрифтов (например, текстовый процессор), вероятно, потребуется использовать более одного шрифта для одной строки. В этом случае необходимо выводить текст, используя отдельно каждый шрифт. Функция TextOut изменять шрифт не может. Главная трудность в использовании более одного шрифта для одной строки текста состоит в том, что необходимо сохранять то количество символов, которое выводится при каждом вызове функции TextOut, чтобы иметь соответствующую начальную позицию следующей части строки. Если используется шрифт с переменной шириной, то могут возникнуть трудности при сохранении длины выводимой строки. Однако, Windows предоставляет функцию GetTextExtent, которая вычисляет длину заданной строки, используя значения ширины символов текущего шрифта. Один из способов формирования строки текста, которая содержит несколько шрифтов, состоит в использовании функции GetTextExtent после каждого вызова TextOut и в добавлении длины выведенной части строки к текущей позиции. В приведенном ниже примере показано, как записать строку "Пример строки", используя курсив для слова "Пример" и жирный шрифт для остальных символов: X = 10; SelectObject(hDC, hItalicFont); TextOut(hDC, X, 10, "Пример ", 7)); X = X + LOWORD(GetTextExtent(hDC, "Пример ", 7)); SelectObject(hDC, hBoldFont); TextOut(hDC, 10, "строки", 6); В этом примере функция SelectObject устанавливает шрифт, который будет использоваться в последующей функции TextOut. Предполагается, что дескрипторы шрифта hBoldFont и hItalicFont были созданы ранее с помощью функции CreateFont. Каждая функция TextOut записывает часть строки, затем функция GetTextExtent . Windows 3.0/pg/3#3 = 77 = вычисляет длину этой части. Функция GetTextExtent возвращает значение в виде двойного слова, содержащего длину и высоту. Для получения длины необходимо использовать служебную функцию LOWORD. Эта длина добавляется к текущей позиции, определяя начальную позицию следующей части строки. Другой способ формироания строки с несколькими шрифтами - создание функции, которая объединяет все требуемые действия. Ниже приведена такая функция: WORD StringOut(hDC, X, Y, lpString, hFont) HDC hDC; short X; short Y; LPSTR lpString; HANDLE hFont; { HANDLE hPrevFont; hPrevFont = SelectObject(hDC, hFont); TextOut(hDC, X, Y, lpString, lstrlen(lpString)); SelectObject(hDC, hPrevFont); return (LOWORD(GetTextExtent(hDC, lpString, lstrlen(lpString))); } Эта функция записывает строку в заданном шрифте, затем восстанавливает шрифт в его предыдущее состояние и возвращает длину записанной строки. В приведенном ниже примере показано, как записать строку "Пример строки": X = 10; X = X + StringOut(hDC, X, 10, "Пример ", hItalicFont); StringOut(hDC, X, 10, "строки", hBoldFont); 18.6 Получение информации о выбранном шрифте. Можно получить информацию о выбранном шрифте из контекста устройства, используя функции GetTextMetrics и GetTextFace. Функция GetTextMetrics копирует структуру TEXTMETRIC в предоставляемый пользователем буфер. Структура содержит описание шрифта, включая средние размеры ячеек символов шрифта, число символов в шрифте и набор символов, на котором базируется шрифт. Можно использовать характеристики текста для определения расстояния между строками текста, значений, соответствующих определенным символам или начертание символа шрифта по умолчанию. Характеристики текста чаще всего используются для определения необходимого расстояния между строками текста, чтобы одна строка не наползала на другую. Например, чтобы вычислить соответствующее значение для расстояния в одну строку, необходимо сложить значения полей tmHeight и . Windows 3.0/pg/3#3 = 78 = tmExternalLeading структуры TEXTMETRIC. Поле tmHeight специфицирует высоту каждой ячейки символа, а поле tmExternalLeading - рекомендуемое расстояние между нижней частью одной ячейки символа и верхней частью следующей. В приведенном ниже примере показано, как вывести несколько строк с расстоянием между ними в одну строку: TEXTMETRICS TextMetric; int nLineSpace; int i; . . . GetTextMetrics(hDC, &TextMetric); nLineSpace = TextMetric.tmHeight + TextMetric.tmExternalLeading; Y = 0; for (i = 0; i < 4; i++) { TextOut(hDC, 0, Y, "Интервал в одну строку", 22); Y += nLineSpace; } Можно также использовать характеристики текста для проверки, что выбранный шрифт имеет запрашиваемые параметры такие, как яркость, набор символов, шаг и семейство. Это полезно в том случае, если не подготовлен контекст устройства; например, если он получен как часть сообщения дочернего окна или блока управления. Более подробная информация о полях структуры TEXTMETRICS дана во втором томе Справочного руководства. Функция GetTextFace копирует имя, идентифицирующее внешний вид выбранного шрифта, в предоставленный буфер. Наименование шрифта вместе с характеристиками текста позволяют полностью определить шрифт. В приведенном ниже примере наименование текущего шрифта копируется в символьный массив FaceName: char FaceName[32]; . . . GetTextFace(hDC, 32, FaceName); 18.7 Получение информации о логическом шрифте. Можно получить информацию о шрифте из дескриптора шрифта, используя функцию GetObject. Эта функция копирует информацию о логическом шрифте такую, как высота, ширина, яркость и набор символов, в предоставляемую структуру. Можно использовать эту информацию для того, чтобы решить, насколько данный шрифт отвечает требованиям пользователя. Функции GetObject и CreateFont часто используются после создания для проверки, насколько близок данный шрифт к требуемому. В приведенном ниже . Windows 3.0/pg/3#3 = 79 = примере функция GetObject возвращает информацию о вновь созданном шрифте и сравнивает значения набора символов и наименования шрифтов: HFONT hFont; LOGFONT LogFont; . . . hFont = CreateFont( 10, 10, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, OEM_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH | FF_MODERN, "Courier" ); GetObject(hFont, (LPLOGFONT) &LogFont); if (LogFont.lfCharSet != OEM_CHARSET) { . . . } if(strcmp(LogFont.lfFaceName, "Courier")) { . . . } Шрифт, который использует GDI при фактическом выборе шрифта с помощью функции SelectObject, может сильно меняться от системы к системе. Выбранный шрифт, который зависит от доступных на данный момент шрифтов, может соответствовать или не соответствовать запросам пользователя. Единственный способ гарантировать удовлетворение запроса - это: - Определить, какие шрифты фактически доступны и запра- шивать только их. - Добавить соответствующий ресурс шрифта к таблице сис- темных шрифтов перед тем, как делать запрос. - Изменить метод, который использует для выбора размет- чик шрифта. . Windows 3.0/pg/3#3 = 80 = 18.8 Перечисление шрифтов. Можно определить, какой шрифт доступен для данного устройства, используя функцию EnumFonts. Эта функция посылает информацию об имеющихся шрифтах функции многократного вызова, предоставляемой пользователем. Функция многократного вызова получает информацию о логическом шрифте и характеристиках текста. Из этой информации пользователь может определить, какие шрифты он может использовать, и создать для них соответствующие дескрипторы. Если создавать дескрипторы шрифтов с помощью этой предоставляемой информации, то имеется гарантия точного соответствия получаемого шрифта с желаемым. Функция EnumFonts обычно выдает информацию обо всех шрифтах, имеющих определенное наименование. Можно задать это имя при вызове функции EnumFonts. Если имя не задано, то эта функция выдает информацию о произвольно выбранных шрифтах, каждый из которых представляет доступное в данный момент начертание. Способ проверки всех доступных шрифтов состоит в получении списка доступных начертаний и перечислении для каждого шрифта соответствующих начертаний. В приведенном ниже примере показано, как использовать функцию EnumFonts для того, чтобы определить, какое количество шрифтов, имеющих начертание "Courier", доступно. Функция многократного вызова EnumFunc получает информацию о шрифте и для каждого шрифта создает дескриптор. FARPROC lpEnumFunc; . . . int FAR PASCAL EnumFunc() { } hDC = GetDC(hWnd); lpEnumFunc = MakeProcInstance(EnumFunc, hInst); EnumFonts(hDC, "Courier", lpEnumFunc, NULL); FreeProcInstance(lpEnumFunc); Для использования функции EnumFonts необходимо задать функцию многократного вызова. Как и каждая функция многократного вызова EnumFunc должна быть явно поименована в операторе EXPORTS файла определения модуля и объявлена с атрибутами FAR и PASCAL. Для каждого перечисляемого шрифта функция многократного вызова EnumFunc возвращает указатель на структуру логического шрифта, указатель на структуру характеристик текста, указатель на любые данные, которые могут быть переданы функции EnumFonts при вызове, и целое, определяющее тип шрифта. В приведенном ниже примере показана простая функция многократного вызова, которая создает перечень . Windows 3.0/pg/3#3 = 81 = всех размеров (в терминах высот) заданного набора растровых шрифтов: short SizeList[10]; short SizeCnt = 0; . . . int FAR PASCAL EnumFunc(lpLogFont, lpTextMetric, FontType, lpData) LPLOGFONT lpLogFont; LPTEXTMETRIC lpTextMetric; short FontType; LPSTR lpData; { if (FontType & RASTER_FONTTYPE) { SizeList[SizeCnt++] = lpLogFont->lfHeight; if (SizeCnt >= 10) return (0); } return (1); } В данном примере вначале проверяется, что шрифт является растровым. Если бит RASTER_FONTTYPE равен 1, то шрифт является растровым; в противном случае - это векторный шрифт. Следующий шаг - это сохранение значения поля lfHeight в массиве SizeList. Функция многократного вызова сохраняет первые 10 размеров, а затем возвращает нуль для прекращения перенумерации. Можно также использовать бит DEVICE_FONTTYPE параметра FontType для различения шрифтов, предоставляемых GDI, от шрифтов, предоставляемых устройством. Это полезно для моделирования GDI курсива, жирного шрифта, подчеркивания и зачеркивания. GDI может моделировать эти атрибуты для собственных шрифтов, а не для шрифтов, предоставляемых устройством. 18.9 Проверка текстовых возможностей устройства. Можно определить расширение возможностей устройств по выводу текста, используя функцию GetDeviceCaps и индекс TEXTCAPS. Этот индекс побуждает функцию возвратить флаг бита, идентифицирующий текстовые возможности устройства. Например, этот флаг может использоваться для определения, может ли данное устройство использовать векторные шрифты, поворачивать символы или моделировать атрибуты шрифта такие, например, как подчеркивание и курсив. GDI может моделировать векторные шрифты на устройствах, которые не поддерживают напрямую вывод линий. Большинство текстовых возможностей применимо к шрифтам, которые предоставляются устройством, в отличие от шрифтов, предоставляемых GDI. Обычно GDI может масштабировать . Windows 3.0/pg/3#3 = 82 = предоставляемые им шрифты и моделировать их атрибуты; однако, он не может делать этого для шрифтов, задаваемых устройством. С помощью функции GetDeviceCaps и индекса NUMFONTS можно определить, сколько имеется шрифтов устройства. Информацию о шрифтах устройства можно получить, используя функцию EnumFonts и проверяя бит DEVICE_FONTTYPE в параметре FontType каждый раз, когда вызывается функция многократного вызова EnumFonts. В приведенном ниже примере показано, как составить перечень шрифтов, предоставляемых устройствами. Функция GetDeviceCaps возвращает число шрифтов, предоставляемых устройством, а EnumFonts создает дескрипторы для каждого из них. HDC hDC; HANDLE hDevFonts; FARPROC lpEnumFunc; short NumFonts; . . . int FAR PASCAL EnumFunc(lpLogFont, lpTextMetric, FontType, Data) LPLOGFONT lpLogFont; LPTEXTMETRIC lpTextMetric; short FontType; LONG Data; { PSTR pDevFonts; short index; int code = 1; if (FontType & DEVICE_FONTTYPE) { pDevFonts = LocalLock(LOWORD(Data)); if (pDevFonts != NULL) { index = ++pDevFonts[0]; if (index < HIWORD(Data)) pDevFonts[index] = CreateFontIndirect(lpLogFont); else code = 0; } LocalUnlock(LOWORD(Data)); } return (code); } . . . hDC = GetDC(hWnd); NumFonts = GetDeviceCaps(hDC, NUMFONTS); hDevFonts = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(HANDLE) * (NumFonts + 1)); . Windows 3.0/pg/3#3 = 83 = lpEnumFunc = MakeProcInstance(EnumFunc, hInst); EnumFonts(hDC, NULL, lpEnumFunc, MAKELONG(hDevFonts, NumFonts)); FreeProcInstance(lpEnumFunc); 18.10 Добавление ресурса шрифта. GDI хранит системную таблицу шрифтов, содержащую все шрифты, которые могут использовать прикладные программы. GDI выбирает шрифт из таблицы, когда программа запрашивает шрифт с помощью функции CreateFont. Ресурс шрифта - это группа отдельных шрифтов, представляющих символы из заданного набора, имеющих различные сочетания высоты, ширины и шага. Например, системный ресурс шрифта - это совокупность шрифтов, представляющих символы различных размеров из набора символов ANSI. Аналогично, ресурс шрифта OEM - это совокупность шрифтов, представляющих символы различных размеров из набора символов OEM. Прикладная программа может иметь до 253 элементов в системной таблице шрифтов. Прикладные программы могут загружать ресурс шрифта и добавлять его к системной таблице ресурсов, используя функцию AddFontResource. После добавления ресурса шрифта отдельные шрифты ресурса становятся доступными для программы. Другими словами, функция CreateFont просматривает шрифты, когда пытается найти соответствующий физический шрифт для заданного логического шрифта. Заметим, что шрифты в системной таблице шрифтов не могут быть непосредственно доступны прикладной программе. К ним имеется доступ только через функции CreateFontIndirect или CreateFont, которые возвращают дескрипторы шрифтов, а не адреса памяти. Можно добавить ресурс шрифта к системной таблице шрифтов, используя функцию AddFontResource. Аналогично, для того, чтобы освободить место под другой ресурс шрифта, можно удалить ресурс шрифта из таблицы с помощью функции RemoveFontResource. Каждый раз, когда прикладная программа добавляет или удаляет ресурс шрифта, она должна информировать все другие программы об изменении, послав сообщение WM_FONTCHANGE. Можно использовать следующий вызов функции SendMessage для посылки сообщения всем окнам: SendMessage(-1, WM_FONTCHANGE, 0, 0L); Если пользователь устанавливает шрифты с помощью программы "Control Panel", можно найти список этих шрифтов, используя функцию GetProfileString для поиска раздела шрифтов файла win.ini. . Windows 3.0/pg/3#3 = 84 = 18.11 Установка выравнивания текста. Функция TextOut использует текущее выравнивание текста из контекста устройства для определения того, как разместить текст относительно данного положения. Например, выравнивание текста по умолчанию - вверх и влево, т. е. функция TextOut располагает в указанной позиции верхний левый угол ячейки первого символа строки. Другими словами, вызов функции, приведенный ниже, помещает верхний левый угол буквы "А" в точку с координатами (10, 10): TextOut(hDC, 10, 10, "AБВГДЕ", 6); Можно изменить выравнивание текста для контекста устройства, используя функцию SetTextAlign. Если считать, что функция TextOut заполняет прямоугольник строкой текста, то выравнивание текста определяет, в какую часть прямоугольника помещается специфицированная точка строки. Функция SetTextAlign распознает левую границу, центр и правую границу прямоугольника, а также его верх, низ и базовую линию. Для спецификации нескольких комбинаций выравнивания можно объединять любое расположение по вертикали с любым расположением по горизонтали. Например, приведенная ниже функция устанавливает выравнивание текста вправо и вниз: SetTextAlign(hDC, TA_RIGHT | TA_BOTTOM); TextOut(hDC, 10, 10, "АБВГДЕ", 6); В данном примере нижний правый угол буквы "Е" располагается в точке с координатами (10, 10). Используя функцию GetTextAlign всегда можно определить текущее выравнивание текста. 18.12 Создание ресурса шрифта. Можно задать собственные ресурсы шрифта для различных прикладных программ, создав файлы шрифта и добавив их в качестве ресурсов к файлу ресурса шрифта. Для создания файла ресурса шрифта необходимы следующие шаги: 1. Создать файлы шрифта. 2. Создать описание ресурса шрифта. 3. Создать шаблон программного модуля. 4. Создать файл определения модуля, который описывает шрифты и устройства, использующие их. 5. Оттранслировать и скомпоновать исходную программу. Файл ресурса шрифта является фактически пустой библиотекой . Windows 3.0/pg/3#3 = 85 = Windows - он не содержит программ или данных, а содержит только ресурс. Если имеются файлы шрифтов, то можно их добавить к пустой библиотеке, используя компилятор ресурсов. Можно также добавить другие ресурсы к библиотеке, такие как иконы, курсоры и меню. В следующих разделах это описано более подробно. 18.12.1 Создание файлов шрифтов. Для создания ресурса шрифта необходим один или несколько файлов шрифтов. Можно создать файлы шрифтов, используя FontEditor, описанный в "Tools". Пользователь свободен в определении числа, размера и типа файлов шрифтов в ресурсе шрифта. В большинстве случаев необходимо включить достаточное количество шрифтов, чтобы разумно удовлетворить большинству запросов на логические шрифты для устройств, на которых они будут использоваться. При планировании размеров шрифта необходимо помнить, что GDI может изменять масштаб независимых от устройств растровых шрифтов от 1 до 8 раз по вертикали и от 1 до 5 раз по горизонтали. GDI может также моделировать жирный шрифт, подчеркивание, перечеркивание и курсив. 18.12.2 Создание описания ресурса шрифта. Подсоедините ресурсы к файлу, добавив один или несколько операторов FONT к файлу описания ресурсов. Описание ресурса шрифта может напротив добавить файл .FNT к библиотеки Windows, драйверу устройства или к файлу, содержащему только ресурсы. Поскольку ресурс шрифта доступен любой прикладной программе, вы не должны добавлять его к модулям прикладной программы. Оператор имеет следующий вид: number FONT filename Для того, чтобы поместить каждый файл шрифта в ресурс, необходим один оператор. Параметр number должен быть уникальным, поскольку позже он используется для идентификации шрифта. Ниже приводится типичный файл описания ресурсов для ресурса шрифта: 1 FONT FntFil01.FNT 2 FONT FntFil02.FNT 3 FONT FntFil03.FNT 4 FONT FntFil04.FNT 5 FONT FntFil05.FNT 6 FONT FntFil06.FNT Шрифты могут быть вставлены в модули, которые содержат другие ресурсы, посредством добавления их к существующему . Windows 3.0/pg/3#3 = 86 = описанию ресурса. Это означает, что в файле описания ресурсов можно иметь описание икон, меню, курсора и панелей диалога, а также операторы FONT. 18.12.3 Создание шаблона программного модуля. Шаблон программного модуля задает объектный файл, из которого формируется файл ресурса шрифта. Шаблон программного модуля создается с помощью макрокоманд ассемблера и С. Исходный файл модуля должен выглядеть так: TITLE FONTRES - выполняемый файл специального вида для создания файла типа .FON .xlist include cmacros.inc .list sBegin CODE sEnd CODE end Ассемблируйте этот исходный файл с помощью команды masm. Она создаст объектный файл, который не содержит программы и данных, но который может быть скомпонован с пустой библиотекой Windows, куда можно добавить ресурсы шрифтов. 18.12.4 Создание файла определения модуля. Необходимо создать файл определения модуля для ресурса шрифта. Этот файл должен содержать оператор LIBRARY, определяющий имя ресурса, оператор DESCRIPTION, определяющий характеристики ресурса шрифта, и оператор DATA. Файл определения модуля для ресурса шрифта может выглядеть так: LIBRARY FontRes DESCRIPTION 'FONTRES 133,96,72: System, Terminal (Set #3)' STUB 'WINSTUB.EXE' DATA NONE Оператор DESCRIPTION задает зависящую от устройства информацию о шрифте, которая может быть использована для сопоставления шрифта с заданным дисплеем или принтером. Ниже приводятся три возможных формата оператора DESCRIPTION в ресурсе шрифта: DESCRIPTION 'FONTRES Aspect, LogPixelsX, LogPixelsY: Cmt' DESCRIPTION 'FONTRES CONTINUOUSSCALING: Cmt' DESCRIPTION 'FONTRES DEVICESPECIFIC DeviceTypeGroup: Cmt' Первый формат определяет шрифт, спроектированный для конкретного коэффициента сжатия, ширины и высоты логического . Windows 3.0/pg/3#3 = 87 = шрифта в пикселях, и может быть использован на любом устройстве, имеющем те же самые коэффициент сжатия и размеры логического шрифта. Параметр Aspect - это значение выражения (100*AspectY)/AspectX, округленное до целого. AspectX, AspectY, LogPixelsX и LogPixelsY - это те же самые значения, что заданы в структуре GDIINFO соответствующего устройства (значения доступны с помощью функции GetDeviceCaps). При желании можно задать более одного набора значений Aspect, LogPixelsX и LogPixelsY. Значение Cmt - это комментарий. Приведем в качестве примеров следующие операторы: DESCRIPTION 'FONTRES 133,96,72: System, Terminal (Set #3)' DESCRIPTION 'FONTRES 200,96,48; 133,96,72; 83,60,72; 167,120,72; Helv' Второй формат специфицирует непрерывный масштабируемый шрифт. Он обычно соответствует векторному шрифту, который может иметь любой размер и который не зависит от коэффициента сжатия или ширины логического шрифта в пикселях для устройства вывода. Приведем следующий пример: DESCRIPTION 'FONTRES CONTINUOUSSCALING: Modern, Roman, Script' Третий формат определяет шрифт, который специфичен для отдельного устройства или группы устройств. Параметр DeviceTypeGroup может быть DISPLAY или может быть перечнем имен типов устройств - тех же самых имен, которые следует специфицировать в качестве второго параметра в вызове функции CreateDC. Приведем такой пример: DESCRIPTION 'FONTRES DISPLAY: HP 7470 plotters' DESCRIPTION 'FONTRES DEVICESPECIFIC HP 7470A,HP 7475A: HP 7470 plotters' Примечание. Максимальная длина строки DESCRIPTION равна 127 символам. Поскольку Windows может синтезировать атрибуты, такие как толщина, подчеркивание, курсив, то вам нет необходимости создавать для них отдельные файлы .FNT. Однако, вы можете это делать. Windows может использовать другие шрифты, которые не соответствуют определяющему соотношению дисплея. Имеются специальные шрифты, предназначенные для таких устройств, как например, матричных принтеров. 18.12.5 Трансляция и компоновка файла ресурса шрифта. Ниже приводится файл make, содержащий команды, которые необходимы для трансляции и компоновки файла ресурса шрифта: fontres.obj: fontres.asm masm fontres; fontres.exe: fontres.def fontres.obj fontres.rc fontres.exe\ . Windows 3.0/pg/3#3 = 88 = FntFil01.FNT FntFil02.FNT FntFil03.FNT\ FntFil04.FNT FntFil05.FNT FntFil06.FNT link4 fontres.obj, fontres.exe, NUL, /NOD, fontres.def rc fontres.rc rename fontres.exe fontres.fon По соглашению все файлы ресурса шрифта имеют расширение файла .fon. Последняя строка файла make служит для переименования выполняемого файла в файл fontres.fon. 18.13 Пример прикладной программы ShowFont. Данный пример прикладной программы иллюстрирует, как использовать шрифты в прикладных программах среды Windows. Хотя программа ShowFont имеет ту же самую структуру, что и любая прикладная программа, описанная в данном руководстве, она содержит значительно больше операторов, обладающет большим их разнообразием, чем какая-либо другая прикладная программа. По этой причине полное описание дано в исходных файлах прикладных программ, расположенных на диске, содержащем примеры. Прикладная программа ShowFont не только иллюстрирует, как использовать шрифты. Она также показывает, как модифицировать многие из программ, ранее описанных в данном руководстве, для выполнения несколько отличных задач. Например, она показывает, как создать и использовать бестипные панели диалога, как использовать панели перечней с собственными строками (вместо текущего каталога) и как использовать метод прямого доступа для групповых панелей и клавиш установки в панели диалога. 18.14 Заключение. Шрифт - это набор символов, имеющих общие атрибуты, такие как ширина, высота, вид и т.п. Прикладная программа использует шрифты для отображения или печати текста. Вы можете определить свои собственные шрифты или использовать предоставляемые Windows. Дополнительную информацию о шрифтах вы найдете в: Раздел Руководство --------------------------------------------------------------- Использование "Tools": Глава 6, "Создание шрифтов: Font "Font Editor" Editor". Печать Руководство программиста: Глава 12, "Печать". Вывод текста Руководство программиста: Глава 3, "Вывод в окно". . Windows 3.0/pg/3#3 = 89 = Глава 19. Палитры цветов. ---------------------------------------------------------------- Палитры цветов Windows предоставляют вам интерфейс между прикладной программой и цветным устройством вывода, таким как дисплей. Этот интерфейс позволяет прикладной программе использовать преимущества устройства вывода без влияния на цвета других прикладных программ. Windows получает информацию о цветах через логическую палитру прикладной программы (это объект GDI, который представляет из себя список цветов, используемых в прикладной программе) и применяет ее к системной палитре (список цветов, доступных в системе и разделяемых всеми прикладными программами). Когда больше чем одна программа отображает цвета из логической палитры, вмешивается Windows, и определяя, какая прикладная программа имеет доступ к системной палитре, управляет с высоким качеством цветами на других прикладных программах. В данной главе приводится информация по следующим темам: - Создание и подготовка к использованию логической палитры в прикладной программе. - Использование цветов из палитры для вывода в области пользователя окна. - Изменение логической палитры и управление тем, когда Windows отобразит эти изменения. - Реакция на изменения, выполненные в системной палитре другими прикладными программами. Примеры, используемые в данной главе, взяты из исходного кода программы ShowDIB, которая содержится на диске "SDK Sample Source Code Disk". В этой программе демонстрируется, как отображать аппаратно-независимые растровые карты с цветами, управляемыми палитрой. 19.1 Что делает палитра цветов. Многие дисплеи способны отображать большой набор цветов. В действительности, однако, число отображаемых в некоторый момент времени цветов ограничено. Например, дисплей, который потенциально способен отображать 24000 различных цветов, может одновременно отображать только 256 из них из-за ограничений оборудования. При наличии таких ограничений дисплеи часто используют палитры. Когда прикладная программа требует не отображаемый в настоящий момент цвет, устройство отображения добавляет требуемый цвет к палитре. Однако, когда число требуемых цветов превышает максимум, следующие цвета будут заменять предыдущие, и действительные отображаемые цвета будут некорректными. Палитры цветов Windows выполняют роль буфера между . Windows 3.0/pg/3#3 = 90 = интенсивно использующей цвета прикладной программой и системой. Палитра цветов позволяет прикладной программе использовать столько цветов, сколько нужно, без влияния на цвета, отображаемые другой прикладной программой. Когда прикладная программа использующая палитру цветов, имеет захват ввода, Windows подразумевает, что программа использует все требуемые цвета и дополнительные цвета отображаются в соответствии с доступными цветами. Windows сравнивает цвета, трубуемые неактивным окном, и использует наиболее подходящий из доступных цветов. При этом исчезает возможность изменения изображений в неактивных окнах. 19.2 Как работают палитры цветов. Windows предоставляет аппаратно-независимый метод доступа к цветовым возможностям устройств отображения с помощью системной палитры устройства, если такая поддерживается устройством. Как мы сказали раньше, ваша прикладная программа обращается с системной с помощью одной или более логических палитр. Логическая палитра - это объект GDI, определяющий цвета, с помощью которых будет производиться вывод в контексте устройства. Каждый элемент в палитре содержит определенный цвет. Затем во время выполнения графических операций прикладная программа не определяет цвет явным значением RGB. Вместо этого вы явно или не явно используете палитру. При прямом использовании вы определяете цвет в палитре, указывая его индекс в палитре цветов. При непрямом методе вы указываете палитро-зависимое значение RGB так же, как и при явном указании значения RGB. Полностью эти методы описаны в разделах 19.4.1, "Явное определение цветов в палитре", и 19.4.2, "Неявное определение цветов в палитре". Когда окно требует от системы использования цвета из логической палитры окна (процесс называется "реализацией" логической палитры), Windows в начале сравнивает элементы логической палитры с текущими элементами системной палитры. Если точное совпадение данного элемента логической палитры невозможно, Windows заносит элемент логической палитры в неиспользуемый элемент системной палитры. После того, как будут задействованы все элементы системной палитры, Windows ищет максимальное совпадение элементов логической палитры с элементами системной палитры. Windows отдельно устанавливает 20 статических цветов в системной палитре, чтобы использовать их при таком сравнении. Windows всегда удовлетворяет в начале запросы цветов, это позволяет активному окну иметь лучшее отображение цветов. Для оставшихся окон Windows удовлетворяет запрос сначала окна, последнего окна имевшего захват ввода, затем следующего, и т.д. . Windows 3.0/pg/3#3 = 91 = Рисунок 19.1 иллюстрирует этот процесс. Рисунок 19.1 Системная палитра и логические палитры. 1. Системная палитра. 2. Логическая палитра 1 (Активное окно). 3. Логическая палитра 2. Представленный на рисунке 19.1 дисплей имеет системную палитру, способную отображать 12 цветов. Прикладная программа, создавшая логическую палитру 1, владеет активным окном и первая реализует логическую палитру. Логическая палитра 1 состоит из восьми цветов. Логической палитрой 2 владеет окно, которое реализует свою логическую палитру, когда оно неактивно. Логическая палитра 2 содержит девять цветов. Поскольку окно, которому принадлежит логическая палитра 1, активно, то все цвета логической палитры напрямую отображаются в системную палитру. Три цвета логической палитры 2: 1, 3 и 5 идентичны цветам в системной палитре. Когда вторая прикладная программа реализует логическую палитру, Windows просто отображает эти цвета на цвета системной палитры, чтобы сохранить место. Поскольку цвета 0, 2, 4 и 6 не содержаться в системной палитре, Windows отображает их в системную палитру. Цвета 7 и 8 в логической палитре 2 точно не совпадают с цветами системной палитры. Но, т.к. системная палитра уже заполнена, Windows не может отобразить эти цвета в системную палитру. Поэтому Windows ищет для них наиболее подходящие цвета в системной палитре. 19.3 Создание и использование логической палитры. Для того, чтобы использовать логическую палитру, прикладная программа должна выполнить следующие действия: 1. Создать структуру данных LOGPALETTE, описывающую логическую палитру. 2. Создать саму палитру. 3. Выбрать палитру в контексте устройства. 4. Реализовать палитру. В следующих разделах описано, как это сделать. 19.3.1 Создание структуры данных LOGPALETTE. Структура данных описывает используемую вами логическую палитру. Она содержит: - Номер версии Windows (для Windows 3.0 это 300Н). . Windows 3.0/pg/3#3 = 92 = - Число элементов в палитре. - Массив структур PALETTEENTRY, каждая из которых содержит однобайтовое значение для красного, зеленого и синего цвета, и байт флагов. Байт флагов может содержать следующие значения: - PC_EXPLICIT - PC_RESERVED Флаг PC_EXPLICIT сообщает Windows, что элемент палитры не содержит значений цвета. Вместо этого, младшее слово элемента является индексом элемента в системной палитре. Например, пример программы MyPal выводит текущее состояние системной палитры. MyPal не определяет цвета в элементах логической палитры. Вместо этого MyPal, пользуясь этим флагом использует для рисования цвета в системной палитре. Прикладная программа устанавливает флаг PC_RESERVED, когда он собирается оживлять элемент (т.е. динамически изменять с помощью функции AnimatePalette). С помощью этого флага вы предотвращаете попытки Windows отобразить цвета других логических палитр на этот цвет, когда он отображен на системную палитру. В программе ShowDIB структура LOGPALETTE создается следующим образом: #define PALETTESIZE 256 . . . pLogPal = (NPLOGPALETTE) LocalAlloc( LMEM_FIXED, (sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY)*(MAXPALETTE)))); ShowDIB инициализирует палитру на 256 цветов, однако вы можете иметь палитру любого размера. ShowDIB заполняет элементы палитры, открывая растровую карту (.BMP) и копируя значения цветов из структуры данных BITMAPINFO в соответствующие элементы палитры: HPALETTE CreateBIPalette (lpbi) LPBITMAPINFOHEADER lpbi; { LOGPALETTE *pPal; HPALETTE hpal = NULL; WORD nNumColors; BYTE red; BYTE green; . Windows 3.0/pg/3#3 = 93 = BYTE blue; int i; RGBQUAD FAR *pRgb; if (!lpbi) return NULL; if (lpbi->biSize != sizeof(BITMAPINFOHEADER)) return NULL; /* Получить указатель таблицы цветов и число цветов в ней */ pRgb = (RGBQUAD FAR *)((LPSTR)lpbi + (WORD)lpbi->biSize); nNumColors = DibNumColors(lpbi); if (nNumColors){ /* Выделить место под логическую палитру */ pPal = (LOGPALETTE*)LocalAlloc(LPTR, sizeof(LOGPALETTE) + nNumColors * sizeof(PALETTEENTRY)); if (!pPal) return NULL; pPal->palNumEntries = nNumColors; pPal->palVersion = PALVERSION; /* Заполнить палитру цветами из таблицы цветов DIB * и создать логическую палитру */ for (i = 0; i < nNumColors; i++){ pPal->palPalEntry[i].peRed = pRgb[i].rgbRed; pPal->palPalEntry[i].peGreen = pRgb[i].rgbGreen; pPal->palPalEntry[i].peBlue = pRgb[i].rgbBlue; pPal->palPalEntry[i].peFlags = (BYTE)0; } hpal = CreatePalette(pPal); LocalFree((HANDLE)pPal); } else if (lpbi->biBitCount == 24){ /* Нет таблицы цветов, поэтому установить их число * в максимум (256) */ nNumColors = MAXPALETTE; pPal = (LOGPALETTE*)LocalAlloc(LPTR, sizeof(LOGPALETTE) + nNumColors * sizeof(PALETTEENTRY)); if (!pPal) return NULL; pPal->palNumEntries = nNumColors; pPal->palVersion = PALVERSION; red = green = blue = 0; /* Сгенерировать 256 (= 8*8*4) комбинаций RGB для . Windows 3.0/pg/3#3 = 94 = * заполнения элементов */ for (i = 0; i < pPal->palNumEntries; i++){ pPal->palPalEntry[i].peRed = red; pPal->palPalEntry[i].peGreen = green; pPal->palPalEntry[i].peBlue = blue; pPal->palPalEntry[i].peFlags = (BYTE)0; if (!(red += 32)) if (!(green += 32)) blue += 64; } hpal = CreatePalette(pPal); LocalFree((HANDLE)pPal); } return hpal; } Вначале, для определения числа цветов в таблице ShowDIB вызывает функцию DibNumColors. Если таблица цветов имеется (т.е. если поле biClrUsed не равно 0 и biBitCount не равно 24), она копирует значения RGBQUAD из каждого поля bmiColors структуры BITMAPINFO в соответствующий элемент палитры. ShowDIB создает палитру из 256 элементов, содержащей полный набор цветов. При выводе растровых карт Windows отображает цвета растровой карты на цвета в этой палитре. 19.3.2 Создание логической палитры. После того, как прикладная программа создала структуру LOGPALETTE, необходимо создать логическую палитру с помощью функции CreatePalette: hPal = CreatePalette((LPSTR)pLogPal); Функция CreatePalette получает в качестве единственного параметра дальний указатель на структуру LOGPALETTE и возвращает дескриптор палитры (HPALETTE). 19.3.3 Выбор палитры в контексте устройства. Для того, чтобы использовать логискую палитру, ее как и любой другой объект GDI необходимо выбрать в контексте устройства. Обычно это делается с помощью функции SelectObject. Однако, т.к. SelectObject не распознает в качестве объекта палитру, то для выбора палитры вы должны выбрать ее с помощью функции SelectPalette: hDC = GetDC(hWnd); SelectPalette(hDC,hPal,0); Этим вы связываете палитру с контекстом устройства, и т.о., все ссылки на палитру (такие как индекс элемента палитры, . Windows 3.0/pg/3#3 = 95 = посылаемый функции GDI вместо цвета) будут направлены в данную палитру. Для удаления объекта логической палитры вы используете фукнцию DeleteObject. Поскольку палитра независима от конкретного контекста устройства, она может разделяться несколькими окнами. Однако, когда прикладная программа выбирает палитру в контексте устройства Windows не создает ее копию, и, следовательно, любые изменения палитры будут влиять на контекст устройства, в котором выбрана данная палитра. Также, если прикладная программа выбрала палитру более чем в одном контексте устройства, эти контексты должны относиться к одному и тому же устройству (такому как дисплей или принтер). Во всех других отношениях палитра аналогична любому другому объекту Windows. 19.3.4 Реализация палитры. После того, как прикладная программа выбрала палитру в контексте устройства, перед ее использованием ее необходимо реализовать: RealizePalette(hDC); При вызове этой функции Windows сравнивает цвета в системной палитре с цветами в логической, устанавливая соответствие между совпадающими. Если в системной палитре имеется место, Windows отображает в нее несовпадающие цвета. И если в системной палитре остались цвета, неотображенные в системную и не совпадающие с цветами системной палитре, Windows находит для них в системной палитре наиболее близкие. 19.4 Рисование с использованием цветов палитры. После того, как логическая палитра в прикладной программе была создана, выбрана в контексте устройства и реализована, ее можно использовать для управления цветами, используемыми функциями GDI для рисования внутри области пользователя устройства. Для функций, требующих указания цвета (например CreatePen или CreateSolidBrush), вы явно или неявно указываете какой цвет палитры использовать. 19.4.1 Прямое определение цветов таблицы. При использовании прямого метода указания цвета из палитры вы в функциях, требующих цвет, явно указываете индекс цвета в палитре вместо явного значения RGB. Макроопределение PALETTEINDEX получает целое число, представляющее индекс в вашей логической палитре, и возвращает значение COLORREF, которое вы используете как цвет, определенный для таких функций. Например, для заполнения области, ограниченной зеленым, сплошной красной кистью вы можете использовать . Windows 3.0/pg/3#3 = 96 = следующую последовательсность: pLogPal -> palPalEntry[5].pRed = 0xFF; pLogPal -> palPalEntry[5].pGreen = 0x00; pLogPal -> palPalEntry[5].pBlue = 0x00; pLogPal -> palPalEntry[5].pFlags = (BYTE) 0; pLogPal -> palPalEntry[6].pRed = 0x00; pLogPal -> palPalEntry[6].pGreen = 0xFF; pLogPal -> palPalEntry[6].pBlue = 0x00; pLogPal -> palPalEntry[6].pFlags = (BYTE) 0; . . . hPal = CreatePalette((LPSTR)pLogPal); hDC = GetDC(hWnd); SelectPalette(hDC,hPal,0); RealizePalette(hDC); lSolidBrushColor = PALETTEINDEX(5); lBoundaryColor = PALETTEINDEX(5); hSolidBrush = CreateSolidBrush(lSolidBrushColor); hOldSolidBrush = SelectObject(hDC,hSolodBrush); hPen = CreatePen(lBoundaryColor); hOldPen = SelectObject(hDC,hPen); Rectangle(hDC,x1,y1,x2,y2); В этом фрагменте вы сообщаете Windows, что необходимо нарисовать приямоугольник цветом номер 6 (зеленый) из палитры и заполнить его цветом из палитры с индексом 5 (красный). Необходимо заметить, что кисть, созданная с помощью CreateSolidBrush, независима от любого контекста устройства. В результате цвет, определяемый параметром lSolidBrushColor, содержится в шестом элементе выбранной в настоящий момент в контексте устройства палитры, а не в прикладной программе, использующей данный контекст. Выбор и освобождение другой палитры и выбор кисти снова изменят цвет кисти. Таким образом, при использовании логической палитры, вам нужно создать лишь кисти каждого используемого вами типа (например сплошные или вертикальная штриховка). Затем вы можете изменить цвет кисти, меняя палитру или только цвет в палитре, используемый нужной вам кистью. 19.4.2 Непрямое определение цветов палитры. Использование индексов логической палитры позволяет прикладной программе осуществить действительное управление отображаемыми цветами. Однако этот метод становится непрактичным при работе с устройствами, которые могут отображать 2^24 цветов без системной палитры. На устройствах, поддерживающих полные 24-битовые цвета, логическая палитра ограничивает число цветов, которые может отображать ваша программа. Непрямое задание цветов палитры позволяет обойти . Windows 3.0/pg/3#3 = 97 = данное ограничение. Цвет палитры указывается неявно при использовании зависимого от палитры значения RGB COLORREF вместо индекса. Значение RGB относительно палитры - это 32-разрядное значение, в котором в старшем байте установлен бит 1, в случае, если оставшиеся байты представляют собой значения красного, синего и зеленого цвета. Макрокоманда PALETTERGB получая три значения, представляющие из себя интенсивности красного, зеленого и синего цветов, возвращает зависимое от палитры значение COLORREF, которое вы можете использовать как и индекс в палитре для функций, требующих указания цвета. Определяя зависимые от палитры значения RGB, ваша прикладная программа может рисовать на устройстве вывода с помощью цветов палитры, не определяя того, что устройство поддерживает системную палитру. Ниже показано, как Windows интерпретирует зависимое от палитры значения RGB. Устройство поддерживает Как Windows использует зависимое от системную палитру палитры значение RGB --------------------------------------------------------------- Да Windows находит в логической палитре ближайший цвет и использует его так, как будто этот элемент в прикладной программе явно определен. Нет Windows использует значение RGB за- висимое от палитры, как если бы вы указали явное значение RGB. --------------------------------------------------------------- Предположим, в вашей прикладной программе делается следующее: pLogPal -> palPalEntry[5].pRed = 0xFF; pLogPal -> palPalEntry[5].pGreen = 0x00; pLogPal -> palPalEntry[5].pBlue = 0x00; CreatePalette((LPSTR)&pa); crRed = PALETTERGB(0xFF,0x00,0x00); Если устройство поддерживает системную палитру, то crRed будет эквивалентен следующему: crRed = PALETTEINDEX(5); Однако, если устройство отображения не поддерживает системную палитру, то crRed будет эквивалентно следующему: crRed = RGB(0xFF,0x00,0x00); Даже при использовании логической палитры прикладная программа может использовать для указания цвета явное значение . Windows 3.0/pg/3#3 = 98 = RGB. В этом случае Windows осуществляет вывод также, как для программ, не использующих палитры, выводя ближайший цвет из палитры, используемой по умолчанию. Если прикладная программа создает сплошную кисть с помощью явного задания значений RGB, Windows эмулирует данный цвет, выводя шаблон, использующий цвета палитры по умолчанию. 19.4.3 Использование палитры при выводе растровых карт. Как показано в разделе 19.3.1, "Создание структуры данных LOGPALETTE", аппаратно-независимая растровая карта может напрямую использовать текущую логическую палитру для заполнения таблицы цветов ее индексами вместо явных значений RGB. Затем, при создании растровой карты с помощью CreatDIBitmap при получении бит из растровой карты с помощью GetDIBits, при установке бит растровой карты с помощью SetDIBits, или при выводе растровой карты непосредственно на устройство отображения с помощью SetDIBitsToDevice, прикладная программа посылает этим функциям в качестве параметра флаг, определяющий что таблица цветов содержит индексы в палитре. В следующем фрагменте из программы ShowDIB происходит установка битов в созданной в памяти растровой карте: SetDIBits (hMemDC,hBitmap,0, pBitmapInfo->bmciHeader.bcHeight, pBuf, (LPBITMAPINFO)pBitmapInfo, ((pBitmapInfo->bmciHeader.bcBitCount == 24) ? DIB_RGB_COLORS : DIB_PAL_COLORS)); В зависимости от того, использует ли DIB 24-разрядные цвета, ShowDIB устанавливает параметр wUsage в DIB_RGB_COLORS (для 24-разрядных) или в DIB_PAL_COLORS (для всех остальных). DIB_RGB_COLORS сообщает Windows, что при установке цветов в растровой карте необходимо использовать цвета из таблицы цветов структуры BITMAPINFO. Если параметр wUsage равен DIB_PAL_COLORS, то Windows интерпретирует таблицу цветов как 16-разрядные индексы в логической палитре и в соответствии с ними устанавливает биты в растровой карте, находящейся в памяти. Если вместо индексов таблица цветов в структуре BITMAPINFO содержит явные значения RGB, Windows использует ближайшие к ним цвета в текущей выбранной логической палитре, так же как и в случае с зависимыми от палитры RGB. Примечание: Если контексты устройства источника и устройства назначения имеют выбранными и реализованными различные палитры, то функция BitBlt не осуществляет корректного перемещения растровой карты из или на контекст устройства памяти. В этом случае вы должны вызвать функцию GetDIBits с параметром wUsage равным DIB_RGB_COLORS, чтобы получить биты растровой карты устройства источника в формате . Windows 3.0/pg/3#3 = 99 = аппаратно-независимой растровой карты. Затем, с помощью функции SetDIBits вы можете установить биты растровой карты на устройстве назначения. При этом Windows точно отобразит цвета одной растровой карты на другую. Функция BitBlt может корректно перемещать растровые карты между двумя контекстами устройства дисплея, даже если они имеют различные выбранные и реализованный палитры. Функция ScretchBlt корректно перемещает растровые карты между различными контекстами устройств, независимо от того, что на них реализованы и выбраны разные палитры. 19.5 Изменение логической палитры. Вы можете изменить один или несколько элементов логической палитры с помощью функции SetPaletteEntry. Эта функция получает следующие параметры: - Дескриптор изменяемой палитры и целое, определяющее парвый изменяемый элемент. - Целое, определяющее число изменяемых элементов. - Массив структур PALETTEENTRY, содержащий интенсивности красного, синего и зеленого цвета и флаг для каждого элемента. Windows не отображает на устройстве вывода сделанных изменений до тех пор, пока прикладная программа не вызовет функцию RealizePalette для любого контекста устройства, в котором выбрана данная палитра. Поскольку при этом производится изменение системной палитры, то цвета, отображаемые в области пользователя окна могут измениться. В разделе 19.6, "Реакция на изменение системной палитры", описано, как реагировать на изменение системной палитры. Второй метод модификации логической палитры состоит в ее оживлении. Во многих случаях оживление логической палитры применяется, когда требуется быстрое и частое изменение логической палитры и когда необходимо, чтобы данные изменения немедленно отображались. Для оживления палитры прикладная программа вначале должна в соответствующих элементах установить флаг PC_RESERVED. Этот флаг выполняет две функции: - Разрешает оживление соответствующего элемента. - Предотвращает отображение цветов другой палитры на соответствующий элемент в системной палитре. В следующем примере показано, как ShowDIB устанавливает флаг PC_RESERVED для всех элементов в существующей логической . Windows 3.0/pg/3#3 = 100 = палитре: /* создать палитру для оживления */ for (i = 0; i < pLogPal->palNumEntries; i++) { pLogPal->palPalEntry[i].peFlags = (BYTE)PC_RESERVED; } SetPaletteEntries(hpalCurrent, 0, pLogPal->palNumEntries, (LPSTR)&(pLogPal->palPalEntry[0])); Функция AnimatePalette получает те же параметры, что и SetPaletteEntries. Однако, в отличие от нее AnimatePalette изменяет только элементы палитры с флагом PC_RESERVED. При вызове AnimatePalette прикладной программой Windows немедленно отображает измененные элементы в системную палитру, но не переотображает цвета, отображаемые в контекстах устройств, используя палитру для которой прикладная программа вызвала AnimatePalette. Другими словами, если перед вызовом прикладной программой функции AnimatePalette пиксель отображался цветом пятнадцатого элемента системной палитры, то после вызова функции он будет отображаться цветом этого элемента, даже если элемент содержит новый цвет. Для демонстрации оживления ShowDIB устанавливает системный таймер и затем вызывает AnimatePalette для сдвига каждого элемента палитры при получении сообщения WM_TIMER: case WM_TIMER: /* Сигнал для оживления палитры */ hDC = GetDC(hWnd); hOldPal = SelectPalette(hDC, hpalCurrent, 0); { PALETTEENTRY peTemp; /* Сдвинуть все элементы логической палитры * влево на один элемент и прокрутить первый * элемент на место последнего */ peTemp = pLogPal->palPalEntry[0]; for (i = 0; i < (pLogPal->palNumEntries - 1); i++) pLogPal->palPalEntry[i] = pLogPal->palPalEntry[i+1]; pLogPal->palPalEntry[i] = peTemp; } /* Заменить элементы логической палитры на новые*/ AnimatePalette(hpalCurrent, 0, pLogPal->palNumEntries, pLogPal->palPalEntry); SelectPalette(hDC, hOldPal, 0); ReleaseDC(hWnd, hDC); . Windows 3.0/pg/3#3 = 101 = /* Уменьшить счетчик оживлений и при достижении 0 * завершить выполнение программы */ if (!(--nAnimating)) PostMessage(hWnd,WM_COMMAND,IDM_ANIMATE0,0L); break; Оживление всей логической палитры может ухудшить цвета, отображаемые в окнах других прикладных программ, если активное использует оживленную палитру, особенно, если оживляемая палитра достаточно велика, чтобы накрыть системную палитру. По этой причине мы не советуем оживлять в палитре больше элементов, чем необходимо. 19.6 Реакция на изменения системной палитры. Когда прикладная программа реализует логическую палитру для определенного контекста устройства, Windows отображает цвета логической палитры на системную, если в системной палитре нет таких цветов и есть свободные элементы. Поскольку изменилась системная палитра, то многие или все цвета, отображаемые в области пользователя всех окон, использующих палитру также изменятся. Для того, чтобы прикладная программа могла отреагировать на такие изменения, Windows посылает накладываемым и перекрывающимся окнам два сообщения: - WM_QUERYNEWPALETTE - WM_PALETTECHANGED 19.6.1 Обработка сообщения WM_QUERYNEWPALETTE. Windows посылает сообщение WM_QUERYNEWPALETTE окну, которое становится активным. Когда окно получает это сообщение, прикладная программа, которой принадлежит данное окно, должна реализовать логическую палитру, пометить для перерисовки область пользователя окна и вернуть TRUE, чтобы сообщить Windows, что системная палитра была изменена. ShowDIB реагирует на сообщение WM_QUERYNEWPALETTE следующим образом: case WM_QUERYNEWPALETTE: /* Если реализация палитры привела к ее измене- * нию, то необходима полная перерисовка. */ if (bLegitDraw) { hDC = GetDC (hWnd); hOldPal = SelectPalette (hDC, hpalCurrent, 0); i = RealizePalette(hDC); SelectPalette (hDC, hOldPal, 0); . Windows 3.0/pg/3#3 = 102 = ReleaseDC (hWnd, hDC); if (i) { InvalidateRect (hWnd, (LPRECT) (NULL), 1); UpdateCount = 0; return 1; } else return FALSE; } else return FALSE; break; 19.6.2 Реакция на сообщение WM_PALETTECHANGED. Windows посылает сообщение WM_PALETTECHANGED всем перекрываемым и накладываемым окнам, когда активное окно изменяет системную палитру, что происходит при реализации его логической палитры. Если ваше окно реагирует на это сообщение, реализуя свою собственную палитру, вы должны в начале определить, что дескриптор не определяет ваше собственное окно, иначе это приведет к зацикливанию. Когда неактивное окно получает это сообщение, оно имеет три возможности: - Не делать ничего. В этом случает цвета в области пользователя могут измениться до тех пор, пока его содержимое не будет перерисовано. Вы должны использовать эту возможность в случае, если вам не важно качество цветов, отображаемых вашей прикладной программой, когда ее окна неактивны, или если ваша прикладная программа не использует палитру. - Оно может реализовать свою собственную палитру и перерисовать содержимое области пользователя. Эта возможность позволяет добиться максимально возможного соответствия цветов, поскольку Windows модифицирует цвета в области пользователя в соответствии с логической палитрой окна. Однако расплатой за это служит время, необходимое для перерисовки области пользователя. Вы можете использовать эту возможность, если для вашей прикладной программы критичны цвета, используемые в неактивных окнах, или если изображение в области пользователя можно перерисовать быстро. - Окно может реализовать его логическую палитру и прямо модифицировать цвета в области пользователя. Эта возможность представляет собой компромис между между характеристиками и качесвом цвета. Окно прямо модифицирует цвета, реализуя палитру и вызывая функцию UpdateColors. При вызове UpdateColors Windows быстро модифицирует цвета, сравнивая цвета пикселей в области . Windows 3.0/pg/3#3 = 103 = пользователя с цветами в системной палитре. Поскольку отображение, базирующееся на цветах пикселей, производится до реализации логической палитры, то каждый вызов UpdateColors уменьшает качество цветов. Следовательно, если качество цвета важно для вашей прикладной программы, когда ее окна не активны, то прикладная программа должна ограничить число вызовов UpdateColors перед перерисовкой области пользователя. Ниже демонстрируется, как в программе ShowDIB можно модифицировать область пользователя при получении сообщения WM_PALETTECHANGED: case WM_PALETTECHANGED: /* Если SHOWDIB не отвечает на изменение палитры или реализация палитры приводит к изменению палитры, выполнить перерисовку. */ if (wParam != hWnd){ if (bLegitDraw){ hDC = GetDC (hWnd); hOldPal = SelectPalette (hDC, hpalCurrent, 0); i = RealizePalette (hDC); if (i){ if (bUpdateColors){ UpdateColors (hDC); UpdateCount++; } else InvalidateRect (hWnd, (LPRECT) (NULL), 1); } SelectPalette (hDC, hOldPal, 0); ReleaseDC (hWnd, hDC); } } break; Когда ShowDIB получает сообщение WM_PALETTECHANGED, она в начале сравнивает содержащийся в wParam дескриптор окна с собственным дескриптором. Если это собственный дескриптор, то реакции на реализацию собственной палитры не требуется. Затем, после выбора и реализации собственной логической палитры она определяет установлен ли флаг, который устанавливается, если пользователь выбрал в меню Options элемент Update Colors. Если это так, то она вызывает функцию UpdateColors для прямой модификации области пользователя и устанавливает флаг, говорящий, что происходит прямая модификация области пользователя. В противном случае помечается для перерисовки область пользователя. . Windows 3.0/pg/3#3 = 104 = 19.7 Заключение. При использовании палитры цветов ваша прикладная программа может отображать столько цветов, сколько доступно на данном устройстве. При выполнении графических операций вы вместо явного указания цвета можете создать палитру, из которой и будут выбираться цвета для вывода. Цвет в палитре можно указать явно с помощью его индекса в палитре или неявно при использовании значения RGB, зависимого от палитры. Когда ваше окно получает захват ввода, Windows гарантирует, что будут отображаться цвета из палитры (вплоть до максимально возможного числа цветов) и оставшиеся цвета будут настолько совпадать с доступными, насколько это возможно. Даже если ваше окно неактивно, Windows все равно обеспечивает отображение цветов окна насколько возможно близко к тем, что определяются системной палитрой. Дополнительную информацию по вопросам, относящимся к палитрам цветов Windows, вы найдете в: Раздел Руководство --------------------------------------------------------------- Отображение цветных Руководство программиста: глава 11, "Раст- растровых карт ровые карты." Функции GDI и функ- Справочное руководство том 1: глава 2, ции работы с палит- "Функции интерфейса графических устройств", рами глава 4, "Список функций". Типы данных и струк- Справочное руководство том 2: глава 7, туры, используемые "Типы данных и структуры". логической палитрой . Windows 3.0/pg/3#3 = 105 = Глава 20. Динамически подключаемые библиотеки. ---------------------------------------------------------------- Microsoft Windows содержит специальные библиотеки, называемые динамически подключаемыми библиотеками (DLL), которые обеспечивают разделение кода и ресурсов между прикладными программами. Windows использует DLL для кода и ресурсов, используемых всеми прикладными программами. Кроме этого, вы можете создавать свои собственные DLL для разделения кода и данных между вашими прикладными программами. В данной главе описано следующее: - Что такое DLL. - Когда использовать DLL. - Создание DLL. Кроме этого, приводится описание создания простой библиотеки SELECT.DLL, которая иллюстрирует концепции, описанные в данной главе. 20.1 Что такое DLL. DLL - это выполняемый модуль, содержащий функции, с которыми могут динамически связываться прикладные программы Windows и которые могут быть вызваны для выполнения определенных действий. DLL существует для обеспечения удобства прикладных программ. В среде Windows DLL играют важную роль. Windows использует DLL для обеспечения доступа прикладных программ к функциям и ресурсам Windows. DLL аналогична библиотекам исполняющей системы (например, библиотеке исполняющей системы С) за исключением того, что она подсоединяется к прикладной программе не на этапе компоновки, а на этапе выполнения. Этот метод компоновки библиотеки и прикладной программы во время выполнения прикладной программы называется динамической компоновкой. Компоновка библиотеки и прикладной программы называется статической компоновкой. Чтобы понять, что такое DLL, можно сравнить их со статическими библиотеками. Примером статической библиотеки может служить MLIBCEW.LIB, содержащая функции исполняющей системы С для средней модели памяти. MLIBCEW.LIB содержит такие функции, как strcpy и strlenr. Вы используете эти функции, не включая их исходный код в свою программу. При компиляции вашей программы компоновщик сам включает необходимую информацию в вашу программу. Когда прикладная программа использует функции статической библиотеки, компоновщик копирует их из библиотеки в .EXE-файл. Единственным преимуществом статических библиотек является то, что они предоставляют прикладной программе стандартный . Windows 3.0/pg/3#3 = 106 = набор процедур, и вам не нужно включать исходные коды этих подпрограмм в свою программу. Однако статические библиотеки не эффективны в многозадачной среде, такой как Windows. Если одновременно работают две прикладные программы и обе используют одну и ту же функцию статической библиотеки, то обе они содержат копию этой функции. Это неэффективное использование памяти. Более эффективно для обоих программ разделять одну копию этой функции, однако статические библиотеки не предоставляют этой возможности. С другой стороны DLL позволяет нескольким прикладным программам разделять одну копию некоторой функции. Все стандартные функции Windows, такие как GetMessage, CreateWindow и TextOut, содержатся в трех DLL: KERNEL.EXE, USER.EXE и GDI.EXE. Если две одновременно работающие прикладные программы Windows используют некоторую функцию Windows, то они разделяют одну копию этой функции. Кроме того, что прикладные программы могут разделять код функций из DLL, они позволяют разделять другие ресурсы, такие как оборудование и данные. Например шрифты Windows это данные, которые прикладные программы разделяют через DLL. Аналогично, драйверы Windows - это DLL, которые позволяют прикладным программам разделять доступ к оборудованию. Все библиотеки Windows являются библиотеками динамической компоновки. Например, файлы GDI.EXE, KERNEL.EXE и USER.EXE, которые составляют основную часть Windows, являются библиотеками динамической компоновки. Можно создать свою собственную библиотеку динамической компоновки. . Windows 3.0/pg/3#3 = 107 = 20.1.1 Импортируемые библиотеки и DLL. Таким образом, мы описали два вида библиотек: статические и динамические. Имеется третий вид библиотек, который особенно важен при работе с DLL. Это импортируемые библиотеки. Импортируемые библиотеки содержат информацию, которая помогает Windows определить местоположение кода в DLL. Во время компоновки компоновщик использует статические и импортируемые библиотеки для разрешения ссылок на внешние переменные. Когда прикладная программа использует процедуры из статических библиотек, компоновщик копирует код этих процедур в файл .EXE прикладной программы. Однако, когда прикладная программа использует процедуры из DLL, компоновщик не копирует код или данные. Вместо этого он копирует информацию из импортируемой библиотеки, которая определяет, где в DLL нужно искать необходимые функции в DLL во время выполнения. Во время выполнения прикладной программы эта информация о размещении создает динамическую связь между прикладной программой и DLL. В таблице 20.1 суммируется использование каждого типа библиотек. . Windows 3.0/pg/3#3 = 108 = Таблица 20.1 Использование трех типов библиотек. --------------------------------------------------------------- Тип Подсоединяется во время Пример ----------------------- ----------------------- Библиотеки компоновки выполнения Библиотеки Функции --------------------------------------------------------------- Статическая Да Нет MLIBCEW.LIB strcpy Импортируемая Да Нет LIBW.LIB TextOut Динамическая Нет Да GDI.EXE TextOut --------------------------------------------------------------- В данной таблице показано, что если прикладная программа вызывает функцию исполняющей системы С strcpy, то компоновщих присоединяет код этой функции из статической библиотеки MLIBCEW.LIB, содержащей функции исполняющей системы, к выполняемому файлу прикладной программы .EXE. Но когда прикладная программа вызывает функцию GDI TextOut, компоновщик копирует информацию о ее размещении из импортируемой библиотеки LIBW.LIB в .EXE-файл. Он не копирует сам код функции TextOut. Во время выполнения, когда прикладная программа осуществляет вызов TextOut, Windows, используя информацию о размещении, находящуюся в .EXE-файле ищет функцию TextOut в GDI.EXE. Другими словами, импортируемая библиотека осуществляет связь между модулями прикладной программы и модулями DLL. 20.1.2 Модули DLL и прикладной программы. Модуль - это основная структурная единица в Windows. Имеется два типа модулей: модули DLL и модули прикладной программы. С модулями прикладной программы вы уже знакомы. EXE файл любой прикладной программы Windows рассматривается как модуль прикладной программы. Примером модуля DLL может служить любой системный файл Windows с расширением .DLL, .DRV или FON. (Некоторые системные модули Windows имеют вместо расширения .DLL расширение .EXE). Прикладная программа и библиотечные модули имеют тот же формат файла. (Фактически этот формат разделяет файлы и DLL OS/2). Этот формат файла, который иногда называют "Новый формат заголовка EXE файла", позволяет осуществить динамическое связывание. Вы можете использовать служебную программу EXEHDR для чтения информации из заголовка файла модуля. Программа EXEHDR предоставляет информацию о функциях, которые модуль импортирует или экспортирует. EXEHDR поставляется вместе с Microsoft C Optimizing Compiler (смотрите документацию по C Compiler). Модуль экспортирует функции, чтобы сделать их доступными другим модулям. Таким образом DLL экспортирует функции, чтобы они могли использоваться другими DLL и прикладными программами. Например, динамически подключаемая библиотека Windows GDI.EXE . Windows 3.0/pg/3#3 = 109 = экспортирует все функции интерфейса графических устройств. Однако, в отличие от DLL, экспортируемые функции прикладной программы не могут использоваться другими модулями. Модуль импортирует функцию, содержащуюся в другом модуле, если возникает необходимость в ее использовании. Импортирование функции создает динамическую связь с кодом данной функции. Функцию можно импортировать двумя способами: - Компонуя модуль с импортируемой библиотекой, которая содержит информацию об этой функции. - Описав отдельные функции в разделе IMPORTS в файле описания модуля прикладной программы. Хотя и модули прикладной программы, и модули DLL могут экспортировать и импортировать функции, между ними существует одно существенное отличие - модули DLL не являются задачами. 20.1.3 DLL и задачи. Одним из основных отличий между модулем прикладной программы и динамически подсоединяемым модулем является их отношение к понятию "задача". Задача - это основной элемент, используемый Windows. Модуль прикладной программы считается выполняемым модулем задачи. После загрузки модуля прикладной программы осуществляется его вызов через точку входа, функцию WinMain, которая обычно содержит цикл обработки сообщений. Модуль прикладной программы создает окна и начинает взаимодействовать с пользователем через Windows. Пока пользователь взаимодействует с прикладной программой, сообщения посылаются модулю прикладной программы, а он возвращает управление Windows. DLL является выполняемым модулем, не являющимся задачей. Как и модуль прикладной программы динамически подсоединяемый модуль может содержать точку входа. При загрузке как и в прикладной программе вызывается его точка входа, но обычно в этом случае производится только минимальная инициализация. В отличие от модуля прикладной программы DLL не взаимодействует с пользователем через цикл обработки сообщений. Вместо этого он ждет обращений прикладных программ за содержащимися в нем функциями. Модули прикладных программ являются активными элементами Windows. Они получаю сообщения, генерируемые системой или пользователем, и в случае необходимости вызывают библиотечные модули для выполнения специальных функций. Библиотечные модули существуют для обеспечения функций, необходимых прикладным программам. Примечание: Некоторые DLL не полностью пассивны. Например, . Windows 3.0/pg/3#3 = 110 = некоторые DLL являются драйверами устройств, управляемыми через прерывания, как, например, клавиатура, мышь или коммуникационный порт. Однако взаимодействие с такими библиотеками полностью управляется, для исключения нарушения работы Windows. Написание DLL, играющих такую активную роль, описано в разделе 20.2.4, "Драйверы устройств". 20.1.4 DLL и стеки. Модуль DLL в отличие от модуля прикладной программы не имеет стека. Вместо этого для хранения параметров и локальных переменных она использует стек, вызывающей ее программы. Это может создать проблемы, когда DLL вызывает функцию, которая подразумевает, что в регистрах DS и SS хранится тот же адрес. Чаще всего эта проблема возникает в DLL, использующих малую и среднюю модели памяти, так как по умолчанию в этих моделях используются ближние указатели. Многие функции исполняющей системы С подразумевают, что содержимое DS равно SS. Необходимо соблюдать осторожность, при вызове таких функций из DLL. Кроме этого DLL может встретить трудности при вызове пользовательских функций. Рассмотрим, например, DLL, которая содержит функцию, объявляющую переменную внутри тела функции. Адрес этой функции берется относительно стека задачи, вызвавшей функцию DLL. Если эта функция передает такую переменную другой функции через ближний указатель, вторая функция подразумевает, что адрес берется относительно сегмента данных DLL, а не сегмента стека программы, вызвавшей DLL. Ниже показано, как функция DLL может передавать данные через стек, а не через сегмент данных: void DLLFunction(WORD wMyWord) WORD wMyWord; { char szMyString[10]; . . . AnotherFunction(szMyString); } Если AnotherFunction объявлена как получающая ближний указатель на массив символов (char NEAR *), то она интерпретирует адрес как смещение в сегменте данных, а не в сегменте стека задачи, вызвавшей DLL. Чтобы быть уверенным в том, что ваша DLL не передает переменные из стека функциям, которые получают ближние указатели, компилируйте DLL с ключем компилятора -Aw. Это приводит к генерации предупреждающего сообщения всякий раз, . Windows 3.0/pg/3#3 = 111 = когда DLL вызывает функцию, подразумевающую, что в DS и SS находятся одинаковые значения. При получении такого сообщения вы можете удалить вызов функции из DLL или переписать исходный модуль DLL таким образом, чтобы не происходила передача стековых переменных через сегмент данных. 20.1.5 Как Windows ищет DLL. Windows определяет местоположение DLL, просматривая директории тем же способом, как и при поиске модуля прикладной программы. Для того, чтобы Windows нашла DLL, необходимо, чтобы она находилась в одной из следующих директорий: 1. Текущей директории. 2. Директории Windows (содержащей WIN.COM). Функция GetWindowsDirectory получает маршрут этой директории. 3. Системную директорию Windows (директорию, содержащую системные файлы Windows, такие как KERNEL.EXE). Функция GetSystemDirectory возвращает маршрут этой директории. 4. Директории, приведенные в переменной среды PATH. 5. Директории из списка директорий, отображаемых на сеть. Windows просматривает директории в указанном порядке. Неявно загружаемые библиотеки должны иметь расширение DLL. В данном разделе описано, что делают DLL в контексте среды Windows. В следующем разделе описано, что могут делать DLL для вашей прикладной программы. 20.2 Когда использовать собственные DLL. Хотя DLL являются центром архитектуры Windows, для прикладных программ они не являются необходимым компонентом. Ваша прикладная программа, не использующая DLL, просто увеличивает затраты Windows на управление памятью. Если вы разделите вашу прикладную программу на отдельные кодовые сегменты, Windows выполнит некоторый род динамической связи между сегментами, чтобы обеспечить наиболее эффективное управление памятью. Дополнительную информацию по поводу использования нескольких кодовых сегментов вы найдете в главе 16, "Еще об управлении памятью". Однако, кроме всего прочего, DLL используются для: - Разделения кода и ресурсов между прикладными программами. - Упрощения модификации программ для различных рынков. . Windows 3.0/pg/3#3 = 112 = - Фильтрации сообщений во всей системе. - Создания драйверов устройств. - Обеспечения для редактора диалога (DIALOG) поддержки ваших собственных блоков управления. - Обеспечения разработки сложных прикладных программ. В данном разделе мы обсудим различные критерии, которые помогут нам решить, нужно ли использовать DLL. 20.2.1 Разделение между прикладными программами. DLL можно использовать для разделения объектов между прикладными программами. Некоторые типы объектов, включая код и ресурсы, могут свободно разделяться через DLL. Разделение других типов объектов, включающих данные и дескрипторы файлов, намного более ограничено. Это происходит от того, что дескрипторы файлов и данные находятся в адресном пространстве прикладной программы. Попытка разделения дескриптора файла или данных (за исключением DDE, системного буфера и сегмента данных DLL) может привести к непредсказуемым результатам и возможно будет несовместимо со следующими версиями Windows. В данном разделе описано, как можно использовать DLL так, чтобы прикладные программы могли разделять код и ресурсы. Разделение кода. Если вы разрабатываете семейство прикладных программ, то, возможно, вы захотите использовать одну или больше DLL. DLL позволяет уменьшить необходимую память, когда ее процедуры используют две или более прикладных программ, работающих одновременно. DLL позволяет нескольким прикладным программам разделять процедуры, которые при использовании статических библиотек были бы одинаковы. Предположим, что вы создаете две графические прикладные программы, одна из которых векторная (рисования), а другая растровая. Общее требование к обоим прикладным программам - это возможность импортировать изображения, созданные другой программой. Вы можете создать DLL для поддержки каждого поддерживаемого "внешнего" формата файла, который может быть преобразован во внутренний формат. Затем ваши программы могут преобразовывать данные в собственный формат. Таким образом, самим прикладным программам необходимо иметь только процедуры преобразования внутреннего формата в свой собственный формат. При добавлении конвертера для нового формата файла вам нужно лишь создать новую DLL, обеспечивающую это преобразование, и переслать ее вашим пользователям вместо того, чтобы перекомпилировать сами модули прикладных программ. . Windows 3.0/pg/3#3 = 113 = Разделение ресурсов. Ресурсы - это объекты данных, предназначенные только для чтения, которые подключаются к выполняемому файлу компилятором ресурсов (RC). Ресурсы можно добавить как к .EXE файлу, так и к файлу DLL. Windows поддерживает восемь встроенных типов ресурсов: - Таблицы ускорителей. - Растровые карты. - Курсоры. - Шаблоны панелей диалога. - Шрифты. - Иконы. - Шаблоны меню. - Таблицы строк. Кроме стандартных ресурсов Windows вы можете создать собственные ресурсы и подключить их к выполняемому файлу. Дополнительную информацию по поводу ресурсов вы найдете в главе 16, "Еще об управлении памятью". Ресурсы в DLL могут разделяться между прикладными программами, это позволяет сэкономить место при использовании в разных программах одних и тех же ресурсов. Ресурсы, расположенные в DLL, могут свободно использоваться в любой прикладной программе. Однако важно, чтобы каждая программа явно запрашивала каждый нужный ей ресурс. Например, если программа использует ресурс меню с именем MainMenu, который расположен в библиотеке MENULIB.DLL, то программа будет содержать следующие строки: HANDLE hLibrary; HMENU hMenu; hLibrary = LoadLibrary("MENULIB.DLL"); hMenu = LoadMenu(hLibrary,"MainMenu"); 20.2.2 Модификация программ для различных рынков. Вы можете использовать DLL для модификации программ под различные рынки. Для каждого рынка вы создаете DLL, которая содержит код, данные и ресурсы, специфичные для данного рынка. . Windows 3.0/pg/3#3 = 114 = Вы не перерабатываете полностью модули прикладной программы для каждого рынка. Вместо этого вы создаете основную программу, использующую специфичную для рынка информацию, содержащуюся в DLL. DLL часто используются для модификации прикладных программ под международные рынки. DLL может содержать информацию, зависимую от языка и культуры, программы, распространяемой в различных странах. Например, прикладнная программа может состоять из основного модуля APPFILE.EXE и трех библиотек, содержащих зависимую от используемого языка информацию: ENGLISH.DLL, FRENCH.DLL и GERMAN.DLL. После установки продукта можно выбрать нужную библиотеку, которая содержит шаблоны панелей диалога, меню, строки и другую информацию для данного языка. При использовании ресурсов библиотеки для ее идентификации вы используете дескриптор экзнмпляра библиотеки. Дескриптор экземпляра библиотеки возвращается функцией LoadLibrary: HANDLE hLibrary; hLibrary = LoadLibrary("FRENCH.DLL"); Значение hLibrary можно использовать во всех случаях, когда при использовании ресурсов необходим дескриптор экземпляра. Например, если библиотека FRENCH.DLL содержит шаблон меню "MainMenu", то программа загружает меню и осуществляет к нему доступ следующим образом: HMENU hMenu; hMenu = LoadMenu(hLibrary,"MainMenu"); 20.2.3 Ловушки окон. Windows позволяет прикладным программам использовать "ловушки" для фильтрации сообщений во всей системе. Ловушка в Windows - это функция, которая получает и обрабатывает события до того, как они посылаются циклу обработки сообщений прикладной программы. Примером такой функции может служить функция, обрабатывающая нажатие пользователем на клавиши. Имеется семь типов ловушек, которые более подробно описаны в первом томе Справочного руководства. Системные ловушки Windows должны быть реализованы с использованием DLL и должны располагаться в фиксированных кодовых сегментах. Это делается потому, что как и всякий системный ресурс код, связанный с ловушкой, должнен быть доступен в любой момент. В некоторых конфигурациях с EMS код, за исключением кода в фиксированных сегментах, располагается в . Windows 3.0/pg/3#3 = 115 = памяти EMS, принадлежащей прикладной программе. Такое размещение называется "выше границы отображения". Доступность такого кода ограничена прикладной программой, владеющей данной областью памяти EMS. Более того, вызов этого кода не осуществляется из контекста загрузившей ловушку прикладной программы. В защищенном режиме Windows рассматривает фиксированный код библиотеки как особый случай для того, чтобы можно было осуществить его вызов. Единственная ловушка Windows, которая не требует размещения в DLL, имеет тип WH_MSGFILTER, которая специфична для прикладной программы. Кроме этого, при работе в защищенном режиме (стандартном или расширенном), подразумевается, что системные ловушки располагаются в фиксированном кодовом сегменте DLL. 20.2.4 Драйверы устройств. Стандартные драйверы устройств Windows реализованы в виде DLL. Ниже приведен список имен, используемых по умолчанию для большинства стандартных драйверов: Драйвер Назначение ---------------------------------------------------------- COMM.DRV Драйвер последовательного порта. DISPLAY.DRV Драйвер видео дисплея. KEYBOARD.DRV Драйвер клавиатуры. MOUSE.DRV Драйвер мыши. SOUND.DRV Звуковой вывод. SYSTEM.DRV Таймер. ---------------------------------------------------------- Файл SYSTEM.INI определяет, какие драйверы инсталлируются при загрузке Windows. Драйверы для нестандартных устройств также должны быть реализованы в виде собственных DLL. После этого различные прикладные программы могут осуществлять доступ к устройству, драйвер сам выполняет необходимую синхронизацию для предотвращения конфликтов доступа к устройству. Поскольку в любой момент, а не только в то время, когда прикладная программа использует устройтсво, могут произойти прерывания, код обработки прерывания для устройства должен располагаться в фиксированном кодовом сегменте. В конфигурациях памяти EMS с большим фреймом отображения только для фиксированного кодового сегмента DLL гарантируется, что он будет доступен в любой время. В защищенном режиме также требуется, чтобы такой код располагался в кодовом сегменте DLL. Дополнительную информацию по поводу конфигураций памяти вы . Windows 3.0/pg/3#3 = 116 = найдете в главе 16, "Еще об управлении памятью". Код обработки прерывания в драйвере устройства не вызывается напрямую прикладной программой. Кроме этого, такие драйверы не должны вызывать код прикладных программ с помощью функции SendMessage, поскольку нет механизма синхронизации таких сообщений с процессом обработки нормальных сообщений. Такие вызовы могут привести к неопределенным состояниям, потере данных и непредсказуемым результатам. Вместо этого, драйвер устройства должен ожидать, пока его не запросит прикладная программа клиент, так же как драйвер коммуникационной программы должен ожидать, пока его не запросит прикладная программа. Вместо этого драйвер может для посылки сообщений прикладным программам использовать функцию PostMessage. 20.2.5 Собственные блоки управления. Если вы разрабатываете собственные блоки управления, вы можете поместить код этих блоков в DLL. Как описано в "Tools", Dialog Editor (DIALOG) может затем осуществлять доступ к DLL для отображения ваших блоков управления во время редактирования панелей диалога. Для того, чтобы библиотека вашего блока управления могла быть использована Dialog Editor, вы должны определить и экспортировать функции, описанные в данном разделе. Пример программы Rainbow иллюстрирует, как писать DLL для собственных блоков управления. В описаниях следующих функций "Class" используется как шаблон для имени класса вашего блока управления. Имя блока управления - это тоже самое имя, которое указывает пользователь в Dialog Editor для идентификации блока управления. Имя блока управления обычно совпадает с именем DLL, хотя это и не обязательно. Описания структур, таких как CTLINFO, и констант, определяющих интерфейс с Dialog Editor вашего блока управления, содержатся в файле CUSTCNTL.H. В данном разделе описано шесть функций, которые должна экспортировать DLL для блока управления. Функции должны экспортироваться со следующими значениями: Экспортируемая функция Значение --------------------------------------------------------------- WEP Любое число, за исключением 2 - 6. ClassInit или LibMain Не требуется. ClassInfo 2 . Windows 3.0/pg/3#3 = 117 = ClassStyle 3 ClassFlags 4 ClassWndFn 5 ClassDlgFn 6 --------------------------------------------------------------- Например, функции, экспортируемые в примере Rainbow, объявлены в файле RAINBOW.DEF следующим образом: EXPORTS WEP @1 RESIDENTNAME RAINBOWINFO @2 RAINBOWSTYLE @3 RAINBOWFLAGS @4 RAINBOWWNDFN @5 RAINBOWDLGFN @6 Дополнительную информацию о функции LibMain вы найдете в разделе 20.3.1, "Инициализация DLL". Дополнительная информация о функции WEP содержится в разделе 20.3.1, "Завершение DLL". Функция ClassInit. Синтаксис HANDLE FAR PASCAL ClassInit(hInstance, wDataSegment, wHeapSize, lpszCmdLine); Функция ClassInit отвечает за всю инициализацию, необходимую для использования динамически подключаемой библиотеки вашего блока управления. Точка входа на ассемблере обычно вызывает эту функцию. Кроме сохранения дескриптора экземпляра в глобальной статической переменной эта функция должна зарегистрировать класс окна блока управления и инициализировать локальную динамическую область памяти с помощью функции LocalInit, если этого не делает точка входа на ассемблере. Если вы компонуете DLL блока управления с LIBENTRY.OBJ вместо того, чтобы писать на ассемблере собственную точку входа, эта функция должна иметь имя LibMain. Дополнительную информацию о функции LibMain вы найдете в разделе 20.3.1, "Инициализация DLL". Параметр Тип/Описание --------------------------------------------------------------- hInstance HANDLE Идентифицирует экземпляр библиотеки. wDataSegment WORD Определяет сегмент данных библиотеки. . Windows 3.0/pg/3#3 = 118 = wHeapSize WORD Определяет размер локальной динамичес- кой области памяти библиотеки. lpszCmdLine LPSTR Определяет аргументы командной строки. --------------------------------------------------------------- Возвращаемое значение. Возвращаемое значение - это дескриптор экземпляра библиотеки зарегистрированного класса блока управления в случае, если инициализация прошла успешно. Если инициализация завершилась неудачно, то возвращается NULL. Функция ClassInfo. Синтаксис HANDLE FAR PASCAL ClassInfo() Функция ClassInfo снабжает вызывающий процесс информацией о библиотеке блока управления. На основе этой информации вызывающий процесс может создать экземпляры блока управления, используя один из поддерживаемых типов. Например, Dialog Editor запрашивает у этой функции типы блоков управления, которые поддерживает библиотека. Функция не имеет параметров. Возвращаемое значение определяет структуру данных CTLINFO. Эта информация становиться принадлежащей вызвавшему процессу. После того, как структура больше не нужна, вызвавший процесс должен явно освободить память с помощью функции GlobalFree. В случае, если для выделения места под структуру не хватает памяти, функция возвращает NULL. Структура CTLINFO определяет имя класса и номер версии. Кроме этого она содержит массив структур CTLTYPE, который представляет собой список используемых комбинаций типов блока управления (вариантов) с коротким описанием и информацией о размере. Ниже приведено описание этих структур и зависимых значений. /* основные типы и определение размеров */ #define CTLTYPES 12 #define CTLDESCR 22 #define CTLCLASS 20 #define CTLTITLE 94 /* структура описания блока управления */ typedef struct { . Windows 3.0/pg/3#3 = 119 = WORD wType; WORD wWidth; WORD wHeight; DWORD dwStyle; char szDescr[CTLDESCR]; } CTLTYPE; typedef struct { WORD wVersion; WORD wCtlTypes; char szClass[CTLCLASS]; char szTitle[CTLTITLE]; char szReserved[10]; CTLTYPE Type[CTLTYPES]; } CTLINFO; typedef CTLINFO * PCTLINFO; typedef CTLINFO FAR * LPCTLINFO; Структура CTLTYPE содержит следующие поля: Поле Описание --------------------------------------------------------------- wType Зарезервировано для последующих реализаций. Это поле должно быть равно 0. wWidth Определяет для Dialog Editor ширину блока управления, которая предлагается при его создании. Если самый значимый бит равен нулю, то младший байт содержит величину по умолчанию в координатах компилятора ресурсов. Если этот байт равен 1, то оставшиеся биты определяют ширину блока управления в пикселях. wWidth Определяет для Dialog Editor высоту блока управления, которая предлагается при его создании. Если самый значимый бит равен нулю, то младший байт содержит величину по умолчанию в координатах компилятора ресурсов. Если этот байт равен 1, то оставшиеся биты определяют высоту блока управления в пикселях. dwStyle Определяет исходные биты типа, используемые для определения данного типа. Это значение включает и флаги, определяемые блоком управления в старшем слове, и флаги, определяемые Windows в младшем слове. szDescr Определяет имя, используемое другими средст- вами разработки при ссылки на данный вариант блока управления. Dialog Editor не использует эту информацию. -------------------------------------------------------------- . Windows 3.0/pg/3#3 = 120 = Структура CTLINFO содержит следующие поля: Поле Описание --------------------------------------------------------------- wVersion Определяет номер версии блока управления. Хо- тя вы можете начать нумерацию с единицы, в большинстве реализаций две младшие цифры используются для представления младших цифр версии. wCtlTypes Определяет число типов блоков управления, под- держиваемых этим классом. Это значение должно быть всегда больше 0 и меньше CTLTYPES. szClass Определяет строку с нулевым окончанием, кото- рая содержит имя класса блока управления, поддерживаемого DLL. szTitle Определяет строку с нулевым окончанием, кото- рая содержит различную информацию о защите авторских прав или об авторе, относящуюся к блоку управления. Type[] Определяет массив структур CTLTYPE, содержа- щих информацию о каждом поддерживаемом данным классом типе блоков управления. --------------------------------------------------------------- Функция ClassStyle. Синтаксис BOOL FAR PASCAL ClassStyle(hWnd, hCtlStyle, lpfnStrToId, lpfnIdToStr); Dialog Editor вызывает функцию ClassStyle для отображения блока диалога, предназначенного для редактирования типа выбранного блока управления. При вызове этой функции она должна отобразить модальную панель диалога, чтобы пользователь мог отредактировать параметры CTLSTYLE. Пользовательский интерфейс должен совпадать с интерфейсом предопределенных блоков управления, поддерживаемых Dialog Editor. Параметр Тип/Описание --------------------------------------------------------------- hWnd HWND Определяет родительское окно панели диалога. hCtlStyle HANDLE Определяет структуру данных CTLSTYLE. . Windows 3.0/pg/3#3 = 121 = lpfnStrToId LPFNSTRTOID Указывает на функцию, предостав- ляемую Dialog Editor, которая преобразует строку в числовой идентификатор. Смотрите раздел "Комментарии" ниже. lpfnIdToStr LPFNIDTOSTR Указывает на функцию, предостав- ляемую Dialog Editor, которая преобразует числовой идентификатор в строку. Смотрите раздел "Комментарии" ниже. --------------------------------------------------------------- Возвращаемое значение. В случае, если структура CTLTYPE была изменена, возвращается TRUE. Если пользователь отменил изменение или произошла ошибка, возвращается FALSE. Комментарии. Структура CTLSTYLE определяет атрибуты выбранного блока управления, включая текущие флаги типа, местоположение, размеры и связанный с данным блоком управления текст. Ниже показано определение структуры CTLSTYLE: /* структура типа блока управления */ typedef struct { WORD wX; WORD wY; WORD wCx; WORD wCy; WORD wId; DWORD dwStyle; char szClass[CTLCLASS]; char szTitle[CTLTITLE]; } CTLSTYLE; typedef CTLSTYLE * PCTLSTYLE; typedef CTLSTYLE FAR * LPCTLSTYLE; Структура CTLSTYLE содержит следующие поля: Поле Описание --------------------------------------------------------------- wX Определяет х-координату блока управления в координатах экрана относительно области пользователя родительского окна. wY Определяет y-координату блока управления в координатах экрана относительно области пользователя родительского окна. wCx Определяет ширину блока управления в коорди- натах экрана. . Windows 3.0/pg/3#3 = 122 = wCy Определяет высоту блока управления в коорди- натах экрана. dwStyle Определяет текущий тип блока управления. Это значение включает и флаги, определяемые блоком управления в старшем слове, и флаги, определяемые Windows в младшем слове. Вы можете позволить пользователю изменить эти флаги на любое значение, которое поддерживается вашим блоком управления. szClass Определяет строку с нулевым окончанием, кото- рая содержит имя класса текущего блока управления. Вы не можете позволить пользователю редактировать это поле, оно приводится только для информации. szTitle Определяет строку с нулевым окончанием, кото- рая содержит текст, связанный с блоком управления. Обычно этот текст отображается внутри блока управления или используется для сохранения другой информации, требуемой блоку управления. --------------------------------------------------------------- Dialog Editor отслеживает определяемые пользователем имена идентификаторов блоков управления и соответствующее им имя символических констант, содержащихся во включаемом файле, который подключается в момент трансляции. Функция определения типа блока управления получает эту информацию через функции lpfnStrToId и lpfnIdToStr. Параметры lpfnStrToId и lpfnIdToStr определяют две точки входа в функции Dialog Editor. Для вызова этих функций вы должны указать их прототипы следующим образом: /* Функции преобразования идентификатора в строку */ typedef WORD (FAR PASCAL * LPFNIDTOSTR)(WORD,LPSTR,WORD); typedef DWPRD (FAR PASCAL *LPFNSTRTOID)(LPSTR); Точка входа lpfnIdToStr в Dialog Editor позволяет вам преобразовать числовое значение идентификатора, содержащегося в структуре CTLSTYLE, в текстовую строку, содержащую символическое имя константы, определенное во включаемом файле. Затем эта строка может быть отображена в панели диалога вместо числового значения. Первый параметр - это идентификатор блока управления. Второй параметр - это дальний указатель на буфер, в который заносится строка, а третий параметр определяет максимальную длину буфера. Функция lpfnIdToStr возвращает число скопированных в буфер символов. Если функция возвращает ноль, это означает, что вызов закончился неудачно. . Windows 3.0/pg/3#3 = 123 = Функция lpfnStrToId выполняет обратное действие, преобразуя строку в числовой идентификатор. Функция получает строку, содержащую имя символической константы, и возвращает соответствующее значение идентификатора. Если младшее слово возвращаемого значения не равно 0, то старшее слово содержит значение идентификатора, которое можно использовать для модификации содержимого поля wID структуры CTLSTYLE. Если младшее слово возвращаемого значения равно 0, то константное значение для данного имени не определено и функция ClassStyle должны выдать сообщение об ошибке. Обычно при вызове функции ClassStyle она вызывает lpfnIdToStr, передавая ей содержимое поля CTLSTYLE.wID. Если функция lpfnIdToStr возвращает ненулевое значение, то ClassStyle отображает полученную строку в радактируемом блоке управления, чтобы пользователь мог изменить ее. Иначе, она отображает числовое значение идентификатора. Если пользователь выполнил изменения в блоке редактирования, то classStyle вызывает функцию lpfnStrToId для проверки, что строка определяет символическую константу, и если это так, заменяет содержимое поля CTLSTYLE.wID на содержимое старшего слова возвращаемого значения. Функция ClassDlgFn. Синтаксис BOOL FAR PASCAL ClassDlgFn(hDlg,wMessage,wParam,lParam); Функция ClassDlgFn - это процедура обработки сообщений панели диалога. Панель диалога вызывается при вызове функции ClassStyle. Функция ClassDlgFn должна позволять пользователю редактировать выбранные части структуры CTLSTYLE, переданной функции ClassStyle. Параметр Тип/Описание --------------------------------------------------------------- hDlg HWND Идентифицирует окно, получающее сооб- щение. wMessage WORD Определяет сообщение. wParam WORD Определяет параметр сообщения размером 16 бит. lParam LONG Определяет параметр сообщения размером 32 бита. --------------------------------------------------------------- Возвращаемое значение. Возвращаемое значение равно TRUE, если функция обработала сообщение, или FALSE в противном случае. . Windows 3.0/pg/3#3 = 124 = Функция ClassFlags. Синтаксис WORD FAR PASCAL ClassFlags(dwFlags,lpStyle,wMaxString); Функция ClassFlags преобразует флаги типа класса блока управления в соответствующие текстовые строки, предназначенные для вывода, в файл описания ресурсов. Эта функция не интерпретирует флаги, содержащиеся в старшем слове, поскольку они управляются Dialog Editor. Заметим, что вы должны использовать те же определения типа блока управления, которые приведены во включаемом файле вашего блока управления. Параметр Тип/Описание --------------------------------------------------------------- dwFlags DWORD Определяет текущие флаги блока управ- ления. lpStyle LPSTR Указывает на буфер, в который помеща- ется строка определения типа. wMaxString WORD Определяет максимальную длину строки. --------------------------------------------------------------- Возвращаемое значение. Возвращаемое значение это число символов, скопированных в буфер, определяемый параметром lpStyle. Если произошла ошибка, возвращается 0. Функция ClassWndFn. Синтаксис LONG FAR PASCAL ClassWndFn(hWnd,wMessage,wParam,lParam); ClassWndFn - это функция окна, отвечающая за обработку сообщений, посылаемых данному блоку управления. Параметр Тип/Описание --------------------------------------------------------------- hDlg HWND Идентифицирует окно, получающее сооб- щение. wMessage WORD Определяет сообщение. wParam WORD Определяет параметр сообщения размером 16 бит. lParam LONG Определяет параметр сообщения размером 32 бита. --------------------------------------------------------------- . Windows 3.0/pg/3#3 = 125 = Возвращаемое значение. Возвращаемое значение определяет результат обработки сообщения и зависит от конкретного сообщения. 20.2.6 Управление проектами. Если вы разрабатываете большую или сложную прикладную программу, вы можете использовать DLL для облегчения разработки. Деление прикладной программы на полностью определенные подсистемы является логичным путем разделения работы между разными группами разработчиков. Каждая подсистема может быть затем реализована в виде DLL. Одной из сложных задач в таких проектах является определение интерфейса для каждой подсистемы. Поскольку функции DLL могут свободно вызывать функции из другой DLL, таким образом Windows не создает препятствий при определении подсистем. Кроме этого, Windows управляет перемещением и сбрасыванием кодовых сегментов для минимизации проблем, часто возникающих перед разработчиками программ для DOS из-за ограничений памяти. Для использования этих преимуществ кодовые сегменты должны быть определены в файле определения модуля прикладной программы (.DEF), как MOVEABLE или MOVEABLE и DISCARDABLE. Одним из удобств использования нескольких DLL является то, что, поскольку каждая DLL имеет свой собственный сегмент данных, то взаимовлияние данных разных подсистем будет минимальным. Этот тип инкапсуляции используется при разработке больших прикладных программ. Другой тип инкапсуляции может создать проблемы в большом проекте, который требует одновременной работы нескольких прикладных программ. Используя тот факт, что каждая прикладная программа рассматривается, как имеющая собственное адресное пространство, она может передать глобальный блок памяти только с помощью Протокола Динамического Обмена Данными (DDE). Смотрите главу 22, "Динамический обмен данными". 20.3 Создание DLL. В данном разделе приводится пример кода, который вы можете использовать как шаблон при создании собственных DLL. Для создания DLL вы должны создать по крайней мере три файла: - Файл с исходным кодом на С. . Windows 3.0/pg/3#3 = 126 = - Файл определения модуля (.DEF). - Make-файл. После создания этих файлов вы запускаете MAKE для их трансляции и компоновки. Оставшая часть этого раздела посвящена описанию того, как создать эти файлы. 20.3.1 Создание файла с исходным кодом на С. В данном разделе приводится исходный код, который вы можете использовать как шаблон при создании DLL. Как и другие программы на С, DLL может содержать несколько функций. Все функции, которые вызывает другая прикладная программа или DLL, должны быть объявлены как FAR и приведены в разделе EXPORTS файла определения модуля библиотеки (.DEF). Файл определения модуля библиотеки обсуждается далее в разделе 20.3.2, "Создание файла определения модуля". /* MINDLL.C -- пример кода DLL, который демонстрирует минимально необходимый для DLL код */ #include WINDOWS.H int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine) { . . /* Инициализация DLL */ . . if(cbHeapSize != 0) /* если сегмент данных DLL перемещаемый */ UnlockData(0); return(1); /* инициализация прошла успешно */ } VOID FAR PASCAL MinRoutine (int iParam1, LPSTR lpszParam2) { char cLocalVariable; /* локальная переменная в стеке */ . . /* код функции MinRoutine */ . . } VOID FAR PASCAL WEP (int nParameter) . Windows 3.0/pg/3#3 = 127 = { if(nParameter == WEP_SYSTEMEXIT) { /* выполняется завершение работы системы. выполнить соответствующие действия */ return(1); } else { if(nParameter == WEP_FREE_DLL) { /* счетчик обращений к DLL равен 0. Все прикладные программы, загрузившие DLL, уже ее освободили. */ return(1); } else { /* неизвестное значение. Игнорировать */ return(1); } } } Исходный код DLL использует WINDOWS.H так же, как и остальные прикладные программы. WINDOWS.H содержит определения данных и типов, определения точек входа API и другую информацию, используемую при программировании. Объявление PASCAL определяет соглашение по вызову и очистке стека. Это не требуется для DLL, однако его использование позволяет создать чуть меньший и более быстрый код. Соглашение по вызову языка Pascal не может использоваться с функциями, получающими переменное число параметров, или для вызова функций исполняющей системы С. В таких случаях требуется объявление CDECL. Функции MinRoutine передаются два параметра, однако функции DLL могут иметь столько параметров, сколько нужно. Единственным ограничением является то, что функциям DLL можно передавать только дальние указатели. Инициализация DLL. Вы должны включить в DLL функцию автоматической инициализации. Эта функция выполняется только один раз при загрузке библиотеки. Когда другие прикладные программы, использующие данную библиотеку, загружают ее, Windows не вызывает функцию инициализации, а просто увеличивает счетчик ссылок на DLL. Windows держит библиотеку в памяти, пока счетчик ссылок на нее не равен 0. Если он стал равен 0, то библиотека удаляется из памяти. Когда прикладная программа опять пытается загрузить . Windows 3.0/pg/3#3 = 128 = библиотеку, заново вызывается функция инициализации. Ниже приведен список задач, которые должна выполнить функция инициализации: - Зарегистрировать классы окон для функций окон, содержащихся в DLL. - Инициализировать локальную динамическую область памяти DLL. - Установить исходные значения для глобальных переменных DLL. Процедура инициализации библиотеки требуется для выделения места под локальную динамическую область памяти. Локальная динамическая область памяти должна быть создана перед любым вызовом в DLL функций работы с локальной динамической областью памяти, таких как LocalAlloc. Хотя для прикладных программ Windows инициализирует локальную динамическую область памяти автоматически, DLL должна сделать это самостоятельно с помощью функции LocalInit. Кроме этого, вы должны включить в процедуру инициализации следующее объявление: extrn __acrtuses:abs Этим вы определяете, что DLL будет скомпонована с стартовым кодом из библиотек исполняющей системы С для Windows DLL (xDLLCy.LIB). При загрузке информация функции инициализации передается в регистрах. Поскольку регистры в С недоступны, то для доступа к этим значения необходимо написать функцию на ассемблере. Расположение и значение информации о динамической области памяти приведено ниже: Регистр Значение --------------------------------------------------------------- DI Дескриптор экземпляра DLL. DS Сегмент данных DLL, если есть. CX Размер динамической области памяти, опреде- ляемый в файле определения модуля библиотеки. ES:SI Командная строка (в поле lpCmdLine параметра lpParameterBlock функции LoadModule). --------------------------------------------------------------- На дисках с SDK содержится файл LIBENTRY.ASM, который . Windows 3.0/pg/3#3 = 129 = можно использовать для создания функции инициализации DLL. (Вы можете найти этот файл в директории SELECT на диске Sample Source Code Disk). Функция LibEntry определена в этом файле следующим образом: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; LIBENTRY.ASM ; ; Точка входа в динамическую библиотеку Windows. ; ; Этот модуль генерирует кодовый сегмент с именем INIT_TEXT. ; Он инициализирует локальную динамическую облать памяти, если ; она есть и затем вызывает функцию C LibMain(), которая должна ; выглядеть следующим образом: ; ; BOOL FAR PASCAL LibMain(HANDLE hInstance, ; WORD wDataSeg, ; WORD cbHeap, ; LPSTR lpszCmdLine); ; ; Результат вызова LibMain возвращается Windows. ; Функция C должна вернуть значение TRUE, если инициализация за- ; вершена успешно, или FALSE при возникновении ошибки. ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; include cmacros.inc externFP |
::Главная ->Литература ->Руководство по программированию в Windows | |
(c) 2000 by AlmigoR
|