C++ Builder
Russian LinkExchange Banner Network
Реклама - двигатель сами понимаете чего...
Delphi  |  JBuilder
::Главная ->Литература ->Руководство по программированию в 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                ; Вызываемая процедура С
       externFP              ; Функция иниализации динамической
                                        ; области памяти Windows
       extrn  __acrtused:abs            ; подключить стартовый код DLL

       public LibEntry                  ; точка входа в DLL

       INIT_TEXT segment byte pablic 'CODE'
             assume CS,INIT_TEXT


       LibEntry proc far

               push di                  ; декриптор экземпляра библиотеки
               push    ds               ; сегмент данных библиотеки
               push    cx               ; размер динамической области
               push    es               ; сегмент командной строки
               push    si               ; смещение командной строки

               ; если нужно инициализировать динамическую область
               jcxz    callc            ; переход если не нужно

               ; Вызвать функцию Windows LocalInit
               ; LocalInit((LPSTR)start, WORD cbHeap);
.
       Windows 3.0/pg/3#3                                       = 130 =


               push    ds               ; сегмент динамической области
               xor     ax,ax
               push    ax               ; смещение начала динамической
                                        ; области памяти
               push    cx               ; смещение конца динамической
                                        ; области памяти

               call    LocalInit        ; попытка инициализации
               or      ax,ax            ; все в порядке?
               jz      exit             ; выход по ошибке

               ; Вызов   функции   C   для    выполнения    специальной
               ; инициализации.

       callc:
               call    LibMain          ; Вызов процедуры C (рез. в AX)

       exit:
               ret                      ; вернуть результат

       LibEntry endp

       INIT_TEXT ends

       end LibEntry

             На дисках  SDK также имеется отассемблированная копия этой
        функции: LIBENTRY.OBJ.  (Вы можете найти этот файл в директории
        SELECT на  диске  Sample  Source  Code Disk).  Функция LibEntry
        позволяет вызвать функцию инициализации на С. Для использования
        фунции LibEntry  в  таком  виде  добавьте ее к командной строке
        компоновщика:

             LINK MINDLL.OBJ LIBENTRY.OBJ, MINDLL.DLL, MINDLL.MAP/map,
                  MDLLCEW.LIB LIBW.LIB/NOE/NOD,MINDLL.DEF

             LibEntry осуществляет   дальний   вызов  с  использованием
        соглашения по вызову языка  PASCAL  функции  LibMain.  Если  вы
        компонуете DLL  с  LIBENTRY.OBJ,  ваша  библиотека должна иметь
        функцию LibMain.

             Ниже дан пример функции LibMain:

             int FAR PASCAL LibMain(HANDLE hInstance,
                                    WORD   wDataSeg,
                                    WORD   cbHeapSize,
                                    LPSTR  lpszCmdLine)
             {
                .
                .
                /*  Инициализация DLL */
                .
.
       Windows 3.0/pg/3#3                                       = 131 =

                .
             if(cbHeapSize != 0)    /* если сегмент данных DLL
                                       перемещаемый */
                 UnlockData(0);

             return(1);             /* инициализация прошла успешно */
             }

             LibMain получает  четыре параметра:  hInstance,  wDataSeg,
        cbHeapSize, lpszCmdLine. Первый параметр hInstance - дескриптор
        экземпляра  DLL.  Параметр wDataSeg это содержимое регистра DS,
        определяющего сегмент данных.  Параметр  cbHeapSize  определяет
        размер   динамической   области  данных,  получаемый  из  файла
        определения  модуля  библиотеки.   LibEntry   использует   этот
        параметр   для  инициализации  локальной  динамической  области
        данных.  Параметр lpszCmdLine содержит информацию из  командной
        строки и редко используется в DLL.

             Если вы   не   хотите,   чтобы  сегмент  данных  оставался
        заблокированным,  то  вызовите  функцию  UnlockData,  поскольку
        LocalInit не разблокирует сегмент.

             Если инициализация  DLL  завершилась  успешно,  DLL должна
        вернуть значение 1.  Нулевое возвращаемое  значение  определяет
        неудачное завершение   инициализации,   и  DLL  выгружается  из
        системной памяти.

             Примечание: Если вы  пишите  всю  DLL  на  ассемблере,  вы
        должны самостоятельно зарезервировать и инициализировать нулями
        первые 16 байт сегмента данных DLL.  Однако,  если  модуль  DLL
        содержит любой код на С, компилятор С зарезервирует эту область
        автоматически.

                                Завершение DLL.

             Windows DLL должна включать  функцию  завершения.  Функция
        завершения, иногда   называемая  процедурой  выхода,  выполняет
        очистку DLL перед ее выгрузкой.

             Для DLL  содержащей  функции  окна  (зарегистрированные  с
        помощью функции    RegisterClass),    не    требуется   удалять
        регистрацию класса (с помощью  UnRegisterClass),  т.к.  Windows
        делает это автоматически при завершении DLL.

             Ниже приведен  пример простой функции завершения.  Функция
        завершения должна  быть   зарегистрирована   так,   как   здесь
        показано. Ей  передается  один  аругмент (nParameter),  который
        определяет, завершается  ли  работа  Windows   (nParameter   ==
        WEP_SYSTEMEXIT)  или  только  DLL (nParameter == WEP_FREE_DLL).
        Она всегда возвращает 1, обозначающую успешное завершение.

             VOID FAR PASCAL WEP (int nParameter)
             {
.
       Windows 3.0/pg/3#3                                       = 132 =

             if(nParameter == WEP_SYSTEMEXIT)
                {
                 /* выполняется завершение работы системы.
                    выполнить соответствующие действия */
                return(1);
                }
             else
                {
                if(nParameter == WEP_FREE_DLL)
                   {
                    /* счетчик обращений к DLL равен 0. Все прикладные
                       программы, загрузившие DLL, уже ее освободили. */
                    return(1);
                   }
                   else
                   {
                        /* неизвестное значение. Игнорировать */
                      return(1);
                   }
                }
             }

             Функция завершения должна называться  WEP  и  должна  быть
        описана  в разделе EXPORTS файла определения модуля библиотеки.
        Мы настоятельно рекомендуем из-за ее влияния на  характеристики
        системы,   чтобы  с  ней  использовалось  оригинальное  входное
        значение и ключевое слово RESIDENTNAME,  чтобы уменьшить  время
        поиска функции.  Ключевое слово RESIDENTNAME заставляет Windows
        держать  информацию  об  экспортируемой  функции  постоянно   в
        памяти,   и  мы  не  рекомендуем  использовать  его  с  другими
        функциями.

             Дополнительную информацию вы найдете в следующем разделе.
                          20.3.2  Создание файла определения модуля.           

             В данном разделе  приведен  файл  определения  модуля  для
        минимальной DLL.  Этот  файл  содержит  входную  информацию для
        компоновщика (LINK), которая определяет различные атрибуты DLL.
        Заметим, что в данном файле нет оператора STACKSIZE,  поскольку
        DLL использует стек вызывающей ее  программы.  Полное  описание
        файла определения  модуля вы найдете во втором томе Справочного
        руководства.

             LIBRARY MinDLL

             DESCRIPTION "MinDLL - Минимальный требуемый для DLL код"

             EXETYPE WINDOWS

             STUB 'WINSTUB.EXE'

             CODE MOVEABLE DISCARDABLE
.
       Windows 3.0/pg/3#3                                       = 133 =


             DATA MOVEABLE SINGLE

             HEAPSIZE 0

             EXPORTS
                MinRoutine  @1
                WEP         @2  RESIDENTNAME

             Ключевое слово   LIBRARY   определяет,   что   модуль    -
        библиотека. Имя библиотеки,  в данном случае MinDLL, следует за
        ключевым словом  и  должно  быть  тем  же,  что  и  имя   файла
        библиотеки.

             Оператор EXETYPE  WINDOWS  требуется  для  всех прикладных
        программ и DLL Windows.

             Оператор DESCRIPTION  содержит  строку   длиной   до   128
        символов. Обычно  его  используют  для  описания  модуля  и для
        отметки об авторских правах. Этот оператор необязателен.

             Оператор STUB определяет программу для  DOS  2.х,  которая
        копируется в  тело  модуля  библиотеки  (.DLL).  Эта  программа
        предназначена для  выдачи  информации   пользователю,   который
        пытается запустить модули Windows из командной строки DOS. Если
        оператор STUB не указан, компоновщик добавит его автоматически.

             Оператор CODE  используется  для  определения   атрибутов,
        используемых по умолчанию,  для блока памяти кодового сегмента.
        Перемещаемый и  сбрасываемый  кодовый   сегмент   предоставляет
        наибольшую свободу системе управления памятью Windows,  который
        обеспечивает доступность    кодового    сегмента    при     его
        необходимости. Оператор   SEGMENTS,   не  включенный  в  данный
        пример, позволяет   определить   атрибуты   отдельных   кодовых
        сегментов.

             Оператор DATA обязателен.  Он определяет атрибуты сегмента
        данных библиотеки.  Ключевое слово MOVEABLE  позволяет  системе
        управления памятью Windows перемещать при необходимости сегмент
        данных библиотеки.  Для  библиотеки  требуется  ключевое  слово
        SINGLE, т.к.   библиотека  всегда  имеет  только  один  сегмент
        данных, независимо от  того,  сколько  прикладных  программ  ею
        пользуются.

             Оператор HEAPSIZE  используется  для определения исходного
        (или минимального)  размера  локальной   динамической   области
        памяти. Если библиотека использует выделение памяти в локальной
        динамической области памяти,  то в момент запуска она должна ее
        инициализировать. Размер  локальной динамической области памяти
        посылается процедуре LibEntry,  которая в свою очередь вызывает
        функцию LicalInit   для  инициализации  локальной  динамической
        области памяти данного размера.  Дополнительную  информацию  по
        этому поводу вы найдете в разделе 20.3.1,  "Инициализация DLL".
.
       Windows 3.0/pg/3#3                                       = 134 =

        В нашем  примере  локальная  динамическая  область  памяти   не
        используется и поэтому ее размер равен 0.

             Оператор EXPORTS,     определяет     процедуры     которые
        используются в качестве точек входа другими DLL или прикладными
        программами.    Эта   информация   используется   Windows   для
        определения сегмента данных,  который должен  использоваться  с
        каждой  функцией DLL.  Каждая процедура должна иметь уникальное
        значение,  которое в нашем примере  указывается  после  символа
        '@'.   Это  значение  используется  для  уменьшения  размера  и
        увеличения быстродействия системы динамического связывания.
                                    20.3.3.  Создание Make-файла.              

             Служебная программа  MAKE  используется   для   управления
        созданием выполняемых файлов и минимизации, требуемой для этого
        обработки.  Для  создания  DLL  используется  четыре  служебные
        программы:

             - Компилятор С (CL).

             - Компоновщик (LINK).

             - Служебная программа для создания импортируемых библиотек
               (IMPLIB).

             - Компилятор ресурсов (RC).

             Make-файл для  создания  нашего   примера   DLL   выглядит
        следующим образом:

             MINDLL.OBJ: MINDLL.C
                 CL  -ASw  -c -Gsw -Os -W2 MINDLL.C

             MINDLL.DLL: MINDLL.OBJ
                 LINK MINDLL.OBJ LIBENTRY.OBJ, MINDLL.DLL, MINDLL.MAP/map,
                  MDLLCEW.LIB LIBW.LIB/NOE/NOD,MINDLL.DEF
                 MAPSYM MINDLL.MAP
                 IMPLIB MINDLL.LIB MINDLL.DEF
                 RC MINDLL.DLL

             Дополнительная информация  о  программе  MAKE содержится в
        документации по Microsoft C Compiller.

                             Ключи компилятора С.

             Компилятор С  использует  пять  наборов  ключей,   которые
        коротко описаны  ниже.  Полное описание ключей компилятора С вы
        найдете в документации по Microsoft C Compiller  версии  4.0  и
        более   поздних.   Ниже   приведены   ключи,  используемые  для
        компиляции нашего примера DLL:

                 CL  -ASw  -c -Gsw -Os -W2 MINDLL.C
.
       Windows 3.0/pg/3#3                                       = 135 =


             Ключ -ASw определяет адресацию,  используемую компилятором
        по умолчанию.  Параметр  S  определяет  малую модель памяти,  в
        которой используются ближние  указатели  на  данные  и  ближние
        указатели на код.  Параметр w сообщает компилятору, что стек не
        является частью сегмента данных по умолчанию, или, если сказать
        по другому,   что   SS   !=   DS.   Это  заставляет  компилятор
        генерировать сообщение   об   ошибке,   когда   он    обнаружит
        недопустимое создание   ближнего  указателя  на  автоматические
        данные.

             Ключ -c определяет, что компилятор должен выполнить только
        компиляцию. Он необходим, если исходный текст вашей DLL состоит
        из нескольких модулей.

             Ключ -Gsw состоит из двух  частей.  Параметр  s  отключает
        нормальную проверку стека компилятора С.  Это необходимо,  т.к.
        проверка стека несовместима с Windows.  Параметр  w  заставляет
        подключить к   каждой   FAR-процедуре  код  пролога  и  эпилога
        Windows.  Это необходимо по двум причинам:  он используется для
        определения корректного  сегмента  данных  и  для  того,  чтобы
        система управления памятью могла перемещать  сегмент  во  время
        работы системы.

             Ключ -Os  сообщает компилятору,  что вместо оптимизации по
        скорости необходима   оптимизация   по   размеру.   Этот   ключ
        необязателен, но мы рекомендуем его использовать.

             Ключ -W2   устанавливает   для  предупреждений  уровень  2
        (высшим уровнем  предупреждений   является   3).   Его   хорошо
        использовать в  процессе  отладки,  т.к.  при  этом  компилятор
        производит различные  проверки  типов   данных   и   прототипов
        функций. Этот   ключ   необязателен,   но  мы  рекомендуем  его
        использовать.

                        Командная строка компоновщика.

             Командная строка  компоновщика  имеет   пять   аргументов,
        разделяемых запятыми:

             LINK MINDLL.OBJ LIBENTRY.OBJ, MINDLL.DLL, MINDLL.MAP/map,
                  MDLLCEW.LIB LIBW.LIB/NOE/NOD,MINDLL.DEF

             Первый аргумент содержит список объектных  файлов  (.OBJ),
        используемых для создания DLL.  Если вы используете стандартную
        процедуру инициализации   DLL,    включите    объектный    файл
        LIBENTRY.OBJ.

             Второй аргумент     определяет     имя     результирующего
        выполняемого файла.  Компоновщик  использует  для  динамических
        библиотек расширение .DLL. Неявно загружаемые библиотеки должны
        иметь расширение   .DLL.    Неявно    загружаемые    библиотеки
        импортируются в  файле определения модуля прикладной программы,
.
       Windows 3.0/pg/3#3                                       = 136 =

        в то время как явно загружаемые загружаются с  помощью  функции
        LoadLibrary.  Смотрите раздел 20.4, "Доступ к DLL из прикладных
        программ."

             Третий аргумент - это имя MAP-файла, который создается при
        использовании  ключа  /map.  Этот  файл  содержит  информацию о
        глобальных переменных и функциях.  Он используется  в  качестве
        входного для программы MAPSYM, которая описана ниже.

             Четвертый аргумент   содержит   список   импортируемых   и
        статических библиотек,  требуемых для создания  DLL.  В  данном
        примере используется  две  библиотеки:  MDLLCEW.LIB и LIBW.LIB.
        MDLLCEW.LIB - это библиотека  исполняющей  системы  С,  которая
        содержит   стартовый  код  DLL,  а  также  процедуры  библиотек
        исполняющей системы  С  и  процедуры  поддержки  математических
        операций.   LIBW.LIB   содержит  импортируемую  информацию  для
        процедур  библиотеки  KERNEL.EXE.  Четвертый   аргумент   также
        включает  два  ключа,  /NOD  и /NOE.  Ключ /NOD отключает поиск
        библиотек  по  умолчанию  на   основе   модели   памяти.   Если
        используются  функции библиотеки исполняющей системы С,  то они
        должны  быть  явно  указаны  в   данном   списке.   Ключ   /NOE
        используется для отключения поиска дополнительных библиотек,  и
        тем самым исключается возможность выдачи сообщений  об  ошибках
        при наличии в нескольких библиотеках одинаковых символов.

             Пятый аргумент  определяет  имя  файла  определения модуля
        библиотеки, создание  которого  описано   в   разделе   20.3.2,
        "Создание файла определения модуля".

                                    MAPSYM.

             Служебная программа  MAPSYM  получает MAP-файл,  созданный
        компоновщиком и создает символьный  файл  с  расширением  .SYM.
        Этот файл затем используется символическим отладчиком (SYMDEB),
        а также в отладочной версии Windows для создании информации  об
        использовании стека при возникновении неустранимой ошибки.

                                    IMPLIB.

             Служебная программа     IMPLIB    создает    импортируемую
        библиотеку с  расширением  .LIB  на  основе  файла  определения
        модуля DLL.  Импортируемая  библиотеки  должна присоединяться в
        командной строке   компоновщика   к   прикладным    программам,
        использующим эту библиотеку.  Это позволяет корректно разрешить
        ссылки на функции динамической библиотеки.

             Подробно IMPLIB описана в "Tools".

                             Компилятор ресурсов.

             Все DLL должны быть откомпилированы компилятором  ресурсов
        для того, чтобы они были совместимы с Windows 3.0.

.
       Windows 3.0/pg/3#3                                       = 137 =

             Вы можете   откомпилировать   DLL  с  помощью  компилятора
        ресурсов с указанием  ключа  -p.  Этим  вы  делаете  библиотеку
        приватной,  т.е.  доступной  для  вызова  из  одной  прикладной
        программы,   для   других   прикладных   программ   она   будет
        недоступной.  В  конфигурации  памяти  EMS  с  большим  фреймом
        отображения Windows  помещает  сегменты  данных  и  кода  таких
        библиотек  выше  границы  отображения.  В конфигурации памяти с
        малым фреймом отображения  Windows  помещает  ее  ниже  границы
        отображения,  даже  если  библиотека приватная.  Дополнительную
        информацию о конфигурациях памяти Windows вы  найдете  в  главе
        16, "Еще об управлении памятью".

             Следующая таблица  суммирует  отличия  между  приватными и
        неприватными библиотеками для двух конфигураций памяти EMS.

.
       Windows 3.0/pg/3#3                                       = 138 =


                           Приватная библиотека  Неприватная библиотека
                           --------------------------------------------
        Объект памяти      Малый      Большой    Малый      Большой
        библиотеки         фрейм      фрейм      фрейм      фрейм
        ---------------------------------------------------------------
        Сегмент данных     ниже       ниже       ниже       выше

        Фиксированный сег-
        мент кода          ниже       ниже       ниже       выше

        Ресурсы            ниже       выше       ниже       выше

        Сбрасываемый сег-
        мент кода          ниже       выше       ниже       выше
        ---------------------------------------------------------------
                                                                               
                20.4  Доступ к DLL из прикладных программ.

             В данном  разделе  описаны  три  шага,   необходимых   для
        осуществления доступа к DLL из прикладной  программы:

             - Создание прототипов библиотечных функций.

             - Вызов библиотечных функций.

             - Импортирование библиотечных функций.
                      20.4.1  Создание прототипа библиотечных функций.         

             Оператор прототипа  должен  использоваться для определения
        всех функций  библиотеки   во   всех   прикладных   программах,
        использующих эту библиотеку.  Для нашего примера прототип будет
        выглядеть следующим образом:

             VOID FAR PASCAL MinRoutine(int,LPSTR);

             Прототип определяет   для    компилятора    параметры    и
        возвращаемое значение  функции.  Затем компилятор может создать
        соответствующий код  для  вызова  библиотечной  функции.  Кроме
        этого, при  испоьзовании  ключа  -W2  компилятор  может  выдать
        предупреждающее сообщение в случае,  если использование функции
        и   ее   прототип   отличаются.   Мы  настоятельно  рекомендуем
        использовать прототипы и для функций прикладной программы, т.к.
        это позволяет избежать ошибок такого рода. Например, компилятор
        выдаст предупреждающее сообщение в  случае,  если  определенная
        выше функция MinRoutine будет вызвана следующим образом:

             MinRoutine (5);

                          Вызов библиотечных функций.

             Вызов функций   DLL   не   отличается  от  вызова  функций
.
       Windows 3.0/pg/3#3                                       = 139 =

        статической библиотеки  или  от  вызова  других  функций  самой
        прикладной  программы.  После  указания  прототипа библиотечной
        функции вы можете вызывать ее обычным образом.
                         20.4.2  Импортирование библиотечных функций.          

             Прикладная программа  может   импортировать   библиотечные
        функции тремя способами:

             - Неявно импортировать во время компоновки.

             - Явно импортировать во время компоновки.

             - Импортировать динамически во время выполнения.

             Во всех   случаях  информация  о  динамическом  связывании
        содержащаяся в библиотеке,  идентифицирует имя библиотеки и имя
        функции   или   ее   входное  значение.  Поскольку  чаще  всего
        используется  неявное  импортирование,   то   этот   способ   и
        обсуждается первым.

                  Неявное импортирование во время компоновки.

             Неявное импортирование  выполняется,  когда  вы  приводите
        список импортируемых библиотек в командной строке  компоновщика
        для прикладной программы.  Импортируемые библиотеки создаются с
        помощью программы  IMPLIB,  как  описано  в   разделе   20.3.3,
        "Создание Make-файла".

             SDK содержит   набор   импортируемых   библиотек,  которые
        позволяют осуществить  связь   с   динамическими   библиотеками
        Windows. В  таблице  20.2  приводится  список  этих файлов и их
        назначение.

.
       Windows 3.0/pg/3#3                                       = 140 =


        Таблица 20.2  Импортируемые библиотеки Windows SDK.
        ---------------------------------------------------------------
        Имя файла         Назначение
        ---------------------------------------------------------------
        LIBW.LIB          Импортируемая информация для USER.EXE,
                          GDI.EXE, и KERNEL.EXE.

        SDLLCEW.LIB       Стартовый код для Windows DLL, функций испол-
                          няющей системы С и библиотеки эмулятора для
                          DLL в малой модели памяти.

        MDLLCEW.LIB       Стартовый код для Windows DLL, функций испол-
                          няющей системы С и библиотеки эмулятора для
                          DLL в средней модели памяти.

        CDLLCEW.LIB       Стартовый код для Windows DLL, функций испол-
                          няющей системы С и библиотеки эмулятора для
                          DLL в компактной модели памяти.

        LDLLCEW.LIB       Стартовый код для Windows DLL, функций испол-
                          няющей системы С и библиотеки эмулятора для
                          DLL в большой модели памяти.

        SDLLCAW.LIB       Стартовый код для Windows DLL, функций испол-
                          няющей системы С и альтернативной математической
                          библиотеки для DLL в малой модели памяти.

        MDLLCAW.LIB       Стартовый код для Windows DLL, функций испол-
                          няющей системы С и альтернативной математической
                          библиотеки для DLL в средней модели памяти.

        CDLLCAW.LIB       Стартовый код для Windows DLL, функций испол-
                          няющей системы С и альтернативной математической
                          библиотеки для DLL в компактной модели памяти.

        LDLLCAW.LIB       Стартовый код для Windows DLL, функций испол-
                          няющей системы С и альтернативной математической
                          библиотеки для DLL в большой модели памяти.

        SLIBCEW.LIB       Стартовый код для прикладных программ Windows,
                          функций исполняющей системы  С  и  библиотеки
                          эмулятора для  малой модели памяти.

        MLIBCEW.LIB       Стартовый код для прикладных программ Windows,
                          функций исполняющей системы  С  и  библиотеки
                          эмулятора для  средней модели памяти.

        CLIBCEW.LIB       Стартовый код для прикладных программ Windows,
                          функций исполняющей системы  С  и  библиотеки
                          эмулятора для  компактной модели памяти.

        LLIBCEW.LIB       Стартовый код для прикладных программ Windows,
.
       Windows 3.0/pg/3#3                                       = 141 =

                          функций исполняющей системы  С  и  библиотеки
                          эмулятора для большой модели памяти.

        SLIBCAW.LIB       Стартовый код для прикладных программ Windows,
                          функций исполняющей системы С и альтернативной
                          математической библиотеки   эмулятора для малой
                          модели памяти.

        MLIBCAW.LIB       Стартовый код для прикладных программ Windows,
                          функций исполняющей системы С и альтернативной
                          математической библиотеки   эмулятора    для
                          средней модели памяти.

        CLIBCAW.LIB       Стартовый код для прикладных программ Windows,
                          функций исполняющей системы С и альтернативной
                          математической библиотеки  эмулятора для
                          компактной модели памяти.

        LLIBCAW.LIB       Стартовый код для прикладных программ Windows,
                          функций исполняющей системы С и альтернативной
                          математической библиотеки  эмулятора для
                          большой модели памяти.

        WIN87EM.LIB       Импортируемая информация для DLL Windows для
                          библиотеки эмулятора вычислений с плавающей
                          точкой.
        ---------------------------------------------------------------

                   Явное импортирование во время компоновки.

             Как и   неявное   импортирование,   явное   импортирование
        выполняется  во  время  компоновки.  Он  выполняется  когда все
        импортируемые  функции  приводятся  в  разделе  IMPORTS   файла
        определения  модуля  прикладной программы.  В следующем примере
        имеются три части: имя функции (MinRoutine), имя DLL, (MinDLL),
        и входное значение (1).

             IMPORTS
                MinRoutine = MinDLL.1

             Для уменьшения  размера  и  улучшения   характеристик   мы
        рекомендуем  разработчикам  всегда  определять исходные входные
        значения для всех экспортируемых функций DLL.  Однако,  если вы
        не  указываете  исходное  входное  значение для функции,  то ее
        можно импортировать следующим образом:

             IMPORTS
                MinDLL.MinRoutine

                   Динамический импорт во время выполнения.

             При импортировании    в   период   выполнения   прикладная
        программа должна в начале явно загрузить библиотеку и запросить
.
       Windows 3.0/pg/3#3                                       = 142 =

        адрес нужной  ей  функции.  После  этого  можно выполнять вызов
        функции. В следующем примере показана динамическая компоновка с
        функцией CreateInfo из библиотеки INFO.DLL.

             HANDLE hLibrary;
             FARPROC lpFunc;

             hLibrary = LoadLibrary("INFO.DLL");
             if(hLibrary >= 32)
                {
                lpFunc = GetProcAdress(hLibrary,"CreateInfo");
                if(lpFunc != (FARPROC)NULL)
                   (*lpFunc)((LPSTR)Buffer,512);
                FreeLibrary(hLibrary);
                }

             В этом  примере  функция  LoadLibrary  загружает требуемую
        библиотеку Windows и возвращает дескриптор  модуля  библиотеки.
        Функция GetProcAdress получает адрес функции по ее имени.  Этот
        адрес можно использовать  для  вызова  функции.  Ниже  приведен
        оператор непрямого вызова функции, в котором ей передеаются два
        параметра: Buffer и 512:

                   (*lpFunc)((LPSTR)Buffer,512);

             В конце функция FreeLibrary уменьшает на  единицу  счетчик
        ссылок на  библиотеку.  Когда счетчик становится равным 0 (т.е.
        библиотеку больше не использует ни одна прикладная  программа),
        Windows удаляет ее из памяти.

             Однако, можно получить более высокие характеристики,  если
        функция CreateInfo имела бы связанное с  ней  исходное  входное
        значение в файле определения модуля библиотеки.  Ниже показано,
        как это можно сделать:

             EXPORTS
                CreateInfo @27

             Этот оператор  определяет  для функции CreateInfo исходное
        входное значение  равное  27.  В  этом  случает  вызов  функции
        GetProcAdress можно изменить следующим образом:

             GetProcAdress(hLibrary,MAKEINTRESOURCE(27));
                         20.5  Правила владения для объектов Windows.          

             Объекты памяти  Windows  могут  находится  как в локальной
        памяти, так  и  в  глобальной  памяти.   К   объектам   Windows
        относятся:

             - Растровые карты.

             - Метафайлы.
.
       Windows 3.0/pg/3#3                                       = 143 =


             - Сегменты кода прикладной программы.

             - Ресурсы (за исключением шрифтов).

             Windows рассматривают объекты памяти следующим образом:

             - Прикладная  программа  владеет  памятью,  если  она   ее
               выделила.

             - Когда  DLL  выделяет  глобальный  объект,  этим объектом
               владеет прикладная программа, которая вызвала DLL.

             - При  завершении  прикладной  программы  или  DLL Windows
               освобождает систему от всех объектов или  классов  окон,
               которыми владеют данная прикладная программа или DLL.

             - Разделение данных может производиться  с  использованием
               системного буфера  или  протокола  динамического  обмена
               данными (DDE),  хотя вы можете также разделять данные  с
               помошью  сегмента данных DLL.  При использовании DDE или
               системного буфера Windows копирует данные в  собственное
               адресное пространство получающей прикладной программы.

             - Объекты   GDI   (кисти,  перья,  контексты  устройств  и
               области) не являются обычными  объектами  Windows  и  не
               освобождаются   при   завершении  прикладной  программы,
               которая  ими  владеет.  По   этой   причине   прикладная
               программа   или   DLL   должна  перед  завершением  явно
               разрушить все созданные ею объекты GDI.

                                  20.6  Пример библиотеки: Select.             

             В данном  примере  библиотеки содержатся функции,  которые
        могут  быть  использованы  для  выполнения  выборки  с  помощью
        мыши. Эти  функции  основываются на методе графической выборки,
        описанном в  главе  6,  "Курсор,  мышь   и   клавиатура".   Они
        обеспечивают  два  вида  обратной  связи  при выборке:  панель,
        показывающую  очертания  выборки,  и  блок,  показывающий   всю
        выборку в   инвертированном   виде.   Библиотека   экспортирует
        следуюшие функции:

        Функция              Действие
        ---------------------------------------------------------------
        StartSelection       Начинает выборку      и     инициализирует
                             прямоугольник выборки.   При   выборке   с
                             помощью мыши эта  функция  вызывается  при
                             получении сообщения WM_LBUTTONDOWN.

        UpdateSelection      Корректирует панель или блок выборки.  При
                             выборке с   помощью   мыши   эта   функция
                             вызывается    при    получении   сообщения
.
       Windows 3.0/pg/3#3                                       = 144 =

                             WM_MOUSEMOVE.

        EndSelection         Заканчивает выборку,  т.  е. прямоугольник
                             выборки       получает        окончательно
                             установленные размеры.   При   выборке   с
                             помощью  мыши  эта  функция вызывается при
                             получении сообщения WM_LBUTTONUP.

        ClearSelection       Удаляет с экрана панель или блок выборки и
                             очищает прямоугольник выборки.
        ---------------------------------------------------------------

             Прямоугольник выборки - это структура RECT, которую задает
        программа и которую заполняют библиотечные функции. Координаты,
        заданные в прямоугольнике, - это координаты пользователя.

             Для создания этой библиотеки необходимо создать  несколько
        файлов:

        Файл                Содержимое
        ---------------------------------------------------------------
        select.c            Исходная  С-программа  для выбранных функ-
                            ций.

        select.def          Файл  определения   модуля  для  библиотеки
                            Select.

        select.h            Включаемый файл для библиотеки Select.

        select              Файл make для библиотеки Select.

        select.lib          Импортируемая   библиотека  для  библиотеки
                            Select.
        ---------------------------------------------------------------

             Библиотека Select  не  должна  иметь  файла инициализации,
        поскольку функции не используют локальную динамическую  область
        памяти и никакой другой инициализации не требуется.

             Примечание: Вместо того, чтобы вводить тексты, приведенные
        в  следующих  разделах,  возможно  вам  будет  удобнее   просто
        переписать исходные тексты из SDK.
                                       20.6.1  Создание функций.               

             Можно создать   библиотечные   функции,  следуя  описанию,
        данному  в  главе  6,  "Курсор,  мышь  и  клавиатура".   Просто
        скопируйте  в  соответствующие функции операторы,  используемые
        для реализации графической выборки.  Кроме того,  чтобы функции
        выборки   были  более  гибкими,  добавьте  блок  дополнительных
        возможностей.

             После изменения  функция  StartSelection  будет  выглядеть
.
       Windows 3.0/pg/3#3                                       = 145 =

        так:

             int FAR PASCAL StartSelection(hWnd, ptCurrent,
                              lpSelectRect, fFlags)

             HWND hWnd;
             POINT ptCurrent;
             LPRECT lpSelectRect;
             int fFlags;
             {
                 if (!IsEmptyRect(lpSelectRect))
                     ClearSelection(hWnd,lpSelectRect,fFlags);
                 if (!fFlags & SL_EXTEND) {
                     lpSelectRect->left = ptCurrent.x;
                     lpSelectRect->top = ptCurrent.y;
                 }
                 lpSelectRect->right = ptCurrent.x;
                 lpSelectRect->bottom = ptCurrent.y;
                 SetCapture(hWnd);
             }

             Эта функция  получает  дескриптор   окна   hWnd,   текущее
        положение мыши  ptCurrent,  длинный  указатель на прямоугольник
        выборки lpSelectRect и  флаги  выборки  fFlags.

             Первый шаг - очистить выборку,  если прямоугольник выборки
        не  пуст.  Если  прямоугольник  пуст,  то  функция  IsRectEmpty
        возвращает TRUE. Функция StartSelection очищает выборку, вызвав
        функцию ClearSelection, которая также находится в библиотеке.

             Следующий шаг   -  инициализация  прямоугольника  выборки.
        Функция  StartSelection  расширяет  выборку  (оставляя  верхний
        левый   угол  выборки  неизменным),  если  в  аргументе  fFlags
        установлен бит SS_EXTEND.  В противном случае она устанавливает
        верхний  левый  и  нижний правый углы прямоугольника выборки на
        текущую позицию  мыши.  Функция   SetCapture   направляет   всю
        вводимую  от  мыши  информацию  в  окно,  даже  если  ее курсор
        перемещается за пределами окна.  Это делается для  того,  чтобы
        процесс выборки продолжался непрерывно. Для вызова этой функции
        прикладной программе следует использовать следующие операторы:

             case WM_LBUTTONDOWN:
                 bTrack = TRUE;
                 StartSelection(hWnd, MAKEPOINT(lParam), &SelectRect,
                     (wParam & MK_SHIFT) ? SL_EXTEND : NULL);
                 break;

             После изменения функция  UpdateSelection  будет  выглядеть
        так:

        int FAR PASCAL UpdateSelection(hWnd, ptCurrent,
                           lpSelectRect, fFlags)
        HWND hWnd;
.
       Windows 3.0/pg/3#3                                       = 146 =

        POINT ptCurrent;
        LPRECT lpSelectRect;
        int fFlags;
        {
            HDC hDC;
            short OldROP;
            hDC = GetDC(hWnd);
            switch (fFlags & SL_TYPE) {
                case SL_BOX:
                    OldROP = SetROP2(hDC, R2_XORPEN);
                    MoveTo(hDC, lpSelectRect->left,
                           lpSelectRect->top);
                    LineTo(hDC, lpSelectRect->right,
                           lpSelectRect->top);
                    LineTo(hDC, lpSelectRect->right,
                           lpSelectRect->bottom);
                    LineTo(hDC, lpSelectRect->left,
                           lpSelectRect->bottom);
                    LineTo(hDC, lpSelectRect->left,
                           lpSelectRect->top);
                    LineTo(hDC, ptCurrent.x, lpSelectRect->top);
                    LineTo(hDC, ptCurrent.x, ptCurrent.y);
                    LineTo(hDC, lpSelectRect->left, ptCurrent.y);
                    LineTo(hDC, lpSelectRect->left,
                           lpSelectRect.top);
                    SetROP2(hDC, OldROP);
                break;
                case SL_BLOCK:
                    PatBlt(hDC,
                        lpSelectRect->left, lpSelectRect->bottom,
                        lpSelectRect->right - lpSelectRect->left,
                        ptCurrent.y - lpSelectRect->bottom,
                        DSTINVERT);
                    PatBlt(hDC, PrevX, OrgY,
                        lpSelectRect->right, lpSelectRect->top,
                        ptCurrent.x - lpSelectRect->right,
                        ptCurrent.y - lpSelectRect->top, DSTINVERT);
                break;
            }
            lpSelectRect->right = ptCurrent.x;
            lpSelectRect->bottom = ptCurrent.y;
            ReleaseDC(hWnd, hDC);
        }

             После того,  как  пользователь  сделал  выборку,   функция
        UpdateSelection   обеспечивает   обратную  связь  с  действиями
        пользователя.  При "панельной" выборке функция сначала  очищает
        текущую панель,  рисуя поверх нее, а затем рисует новую панель.
        Эти действия требуют 8 вызовов функции LineTo.

             Для корректировки      "блочной"      выборки      функция
        UpdateSelection  инвертирует  прямоугольник  с  помощью функции
        PatBlt.  Для того, чтобы избежать мерцания при выборке, функция
.
       Windows 3.0/pg/3#3                                       = 147 =

        UpdateSelection  инвертирует  только  ту  часть прямоугольника,
        которая отличается от предыдущего прямоугольника  выборки.  Это
        означает,  что  функция инвертирует две отдельных части экрана.
        Предполагается,  что  единственной  областью,   которую   нужно
        инвертировать,  является  область  между  предыдущим  и текущим
        положением мыши. На рис. 1 представлены типичные координаты для
        описания инвертируемых областей:

             Рисунок 20.1  Инвертирование примоугольника.


             Первый вызов PatBlt инвертирует самый левый прямоугольник,
        используя  lpSelectRect->left (начальное положение по оси х при
        первом нажатии на кнопку мыши)  и  lpSelectRect->bottom  (самую
        последнюю коррекцию  положения  мыши  по  оси  у) для установки
        начального  положения  инвертируемой  области.  Ширина   первой
        области    определяется    вычитанием   lpSelectRect->left   из
        lpSelectRect->right (самая последняя коррекция  положения  мыши
        по   оси   х).  Высота  этой  области  определяется  вычитанием
        lpSelectRect->bottom из ptCurrent.y (текущей  позиции  мыши  по
        координате у).

             Второй вызов PatBlt инвертирует самый правый прямоугольник
        с помощью lpSelectRect->right (самое последнее положение по оси
        х при  нажатии  на  кнопку мыши) и lpSelectRect->top (начальное
        положение мыши по оси у)  для  установки  начального  положения
        инвертируемой области.  Ширина этой второй области определяется
        вычитанием  lpSelectRect->bottom  (самая  последняя   коррекция
        положения мыши  по  оси  х)  из  ptCurrent.x (текущее положение
        мыши по оси х).  Высота этой  области  определяется  вычитанием
        lpSelectRect->top из ptCurrent.y (текущее положение мыши по оси
        у).

             Когда корректировка    выборки     закончена,     значения
        lpSelectRect->right и lpSelectRect->bottom корректируются путем
        присваивания им текущих значений, содержащихся в ptCurrent.

             Для коррекции  "панельной"  выборки  прикладная  программа
        должна вызвать функцию UpdateSelection следующим образом:

             case WM_MOUSEMOVE:
                 if (bTrack)
                     UpdateSelection(hWnd, MAKEPOINT(lParam),
                         &SelectRect, SL_BOX);

             После изменения функция EndSelection будет выглядеть так:

             int FAR PASCAL EndSelection(ptCurrent, lpSelectRect)
             POINT ptCurrent;
             LPRECT lpSelectRect;
             {
                 if (ptCurrent.x < lpSelectRect->left) {
                     lpSelectRect->right = lpSelectRect->left;
.
       Windows 3.0/pg/3#3                                       = 148 =

                     lpSelectRect->left = ptCurrent.x;
                 }
                 else
                     lpSelectRect->right = ptCurrent.x;
                 if (ptCurrent.y < lpSelectRect->top) {
                     lpSelectRect->bottom = lpSelectRect->top;
                     lpSelectRect->top = ptCurrent.y;
                 }
                 else
                     lpSelectRect->bottom = ptCurrent.y;
                 ReleaseCapture();
             }

             Функция EndSelection  сохраняет  текущее  положение мыши в
        прямоугольнике   выборки.    Окончательное    положение    мыши
        контролируется  для того,  чтобы быть уверенным в том,  что оно
        представляет  собой  точку,  расположеннную   ниже   и   правее
        начальной  точки.  Прямоугольники  обычно  описываются  верхним
        левым и  нижним  правым  углами.   Если   конечная   точка   не
        расположена ниже и правее начальной (т.е.,  если или координата
        х, или  координата  у  конечной  точки меньше координат х или у
        начальной точки),  значения начальной  и  конечной  точек,  при
        необходимости, меняются местами. Поскольку была вызвана функция
        SetCapture,    то    необходима     соответствующая     функция
        ReleaseCapture.  Вообще  говоря,  следует освободить мышь сразу
        же, как только она становится не нужна.

             Наконец, когда   пользователь  освобождает  левую  кнопку,
        прикладная программа должна вызвать  функцию  EndSelection  для
        сохранения конечной точки:

             case WM_LBUTTONUP:
                 bTrack = FALSE;
                 EndSelection(MAKEPOINT(lParam), &SelectRect);
                 break;

             После изменения  функция  ClearSelection  будет  выглядеть
        так:

        int FAR PASCAL ClearSelection(hWnd, lpSelectRect, fFlags)
        HWND hWnd;
        LPRECT lpSelectRect;
        int fFlags;
        {
            HDC hDC;
            short OldROP;
            hDC = GetDC(hWnd);
            switch (fFlags & SL_TYPE) {
                case SL_BOX:
                    OldROP = SetROP2(hDC, R2_XORPEN);
                    MoveTo(hDC, lpSelectRect->left,
                           lpSelectRect->top);
                    LineTo(hDC, lpSelectRect->right,
.
       Windows 3.0/pg/3#3                                       = 149 =

                           lpSelectRect->top);
                    LineTo(hDC, lpSelectRect->right,
                           lpSelectRect->bottom);
                    LineTo(hDC, lpSelectRect->left,
                           lpSelectRect->bottom);
                    LineTo(hDC, lpSelectRect->left,
                           lpSelectRect->top);
                    SetROP2(hDC, OldROP);
                break;
                case SL_BLOCK:
                    PatBlt(hDC,
                        lpSelectRect->left, lpSelectRect->top,
                        lpSelectRect->right - lpSelectRect->left,
                        electRect->top,
                        DSTINVERT);
                break;
            }
            ReleaseDC(hWnd, hDC);
        }

             Очистка "панельной" выборки означает ее удаление с экрана.
        Можно удалить ее очертания, рисуя поверх нее пером XOR. Очистка
        "блочной" выборки означает восстановление инвертированной части
        экрана  в  его  первоначальное  состояние.  Можно  восстановить
        инвертированный экран, инвертируя всю выборку.
                                                                               
                  20.6.2  Создание функции инициализации.

             Select использует    стандартную    процедуру    LibEntry,
        содержащуюся в файле LIBENTRY.OBJ. Эта процедура в свою очередь
        вызывает функцию  с именем LibMain,  определение которой должно
        быть в исходном коде DLL  и  которая  выполняет  инициализацию,
        специфичную для   данной   библиотеки.   Поскольку   Select  не
        трубуется инициализация, кроме выполняемой LibEntry, то LibMain
        просто возвращает 1 для указания успешного завершения.  Функция
        LibMain в библиотеке Select определена следующим образом:

             int FAR PASCAL LibMAin(hInstance,wDataSeg,cbHeapSize,
                                    lpszCmdLine)
             WORD wDataSeg;
             HANDLE hInstance;
             WORD cbHeaSize;
             LPSTR lpszCmdLine;
             {
                return 1;
             }
                                                                               
                  20.6.3  Создание процедуры завершения.

             Как и любая DLL, Select должна иметь процедуру завершения,
        WEP. Поскольку  Select не требуется выполнения никаких операций
        очистки, то процедура WEP будет иметь следующий вид:

.
       Windows 3.0/pg/3#3                                       = 150 =

             VOID FAR PASCAL WEP(nParameter);
             int nParameter;
             {
                return;
             }
                                                                               
                 20.6.4  Создание файла определения модуля

             Для компоновки библиотеки Select необходимо  создать  файл
        определения модуля, содержащий следующие операторы:

             LIBRARY Select
             CODE MOVEABLE DISCARDABLE
             DATA SINGLE
             HEAPSIZE 1024

             EXPORTS
                 WEP             @1, RESIDENTNAME
                 StartSelection  @2
                 UpdateSelection @3
                 EndSelection    @4
                 ClearSelection  @5

             Поскольку функции  выборки  не  используют  глобальных или
        статических переменных, и локальная динамическая область памяти
        отсутствует,  оператор  DATA  специфицирует отсутствие сегмента
        данных, а оператор HEAPSIZE используется для установки нулевого
        размера динамической области памяти.
                                                                               
                    20.6.5  Создание включаемого файла.

             Для библиотеки  Select  необходимо создать включаемый файл
        select.h. Этот файл содержит определения констант, используемых
        в функциях, а также определения функций. Включаемый файл должен
        выглядеть так:

        int  FAR  PASCAL  StartSelection(HWND,  POINT, LPRECT,  int);
        int  FAR  PASCAL  UpdateSelection(HWND,  POINT, LPRECT,  int);
        int FAR PASCAL EndSelection(POINT, LPRECT);
        int FAR PASCAL ClearSelection(HWND, LPRECT, int);

             Необходимо также   использовать  этот  включаемый  файл  в
        прикладных программах,  которые используют функции выборки. Это
        даст уверенность в том,  что в функциях используются правильные
        типы параметров и возвращаемых значений.
                                                                               
                     20.6.6  Трансляция и компоновка.

             Для трансляции и компоновки библиотеки  Select  необходимо
        создать файл make следующим образом:

             select.obj: select.c select.h
                     cl -c -Asnw -Gsw -Os -Zp select.c
.
       Windows 3.0/pg/3#3                                       = 151 =


             select.dll: select.obj
                     link select  libentry,  select.dll,  ,  /NOD  /NOE
                     sdllcew libw, select.def
             rc select.dll
             implib select.lib select.def

             После трансляции  и  компоновки  библиотеки  Select  можно
        создать небольшую тестовую программу для того,  чтобы убедиться
        в правильной работе  библиотеки.  Описание  программы,  которая
        использует функции выборки,  вы найдете в главе 11,  "Растровые
        карты", или в главе 13, "Системный буфер".
                                             20.7  Заключение.                 

             В данной   главе    описаны    динамически    подключаемые
        библиотеки (DLL),  которые  представляют   собой   особый   тип
        библиотек,  позволяющий  прикладным  программам разделять код и
        ресурсы. Например,  библиотеки   Windows   делают   для   любой
        прикладной  программы доступными функции Windows,  и прикладные
        программы таким образом не должны содержать код  этих  функций.
        Вы можете создать свои собственные библиотеки,  чтобы разделять
        код или ресурсы между вашими прикладными программами.

             При использовании   статических   библиотек   (таких   как
        MLIBCEW.LIB) компоновщик  копирует  код  требуемых   прикладной
        программе функций в  выполняемый  файл  (.EXE).  Напротив,  при
        использовании динамических  библиотек  две или более прикладные
        программы могут разделять  единственную  копию  исходного  кода
        процедуры.  Прикладные  программы  подсоединяют  DLL  во  время
        выполнения, а не во время компоновки.

             Обычно DLL  используют  для  создания  собственных  блоков
        управления. Ваша прикладная программа может затем  использовать
        эти блоки  управления,  и  вы  можете их включать в свои панели
        диалога с помощью Dialog Editor.

             Дополнительную информацию    по     поводу     динамически
        подключаемых библиотек  вы можете получить в:

        Раздел               Руководство
        ---------------------------------------------------------------
        Специальные согла-   Руководство программиста, глава 14, "Язык
        шения по созданию    С и язык ассемблера".
        DLL на С

        Управление памятью   Руководство программиста, глава 16, "Еще
        в DLL                об управлении памятью".

        Компоновка и импор-  "Tools", глава 2, "Компоновщик: компоновка
        тирование DLL        прикладных программ".

        Установка собствен-  "Tools", глава 5, "Dialog Editor: Создание
.
       Windows 3.0/pg/3#3                                       = 152 =

        ных блоков управле-  панелей диалога".
        ния с помощью Dialog
        Editor
                           Глава 21. Интерфейс множества документов.           

             Интерфейс множества   документов   (MDI)  -  это  стандарт
        пользовательского интерфейса     для     манипулирования      и
        представления нескольких    документов   в   одной   прикладной
        программе. Прикладные программы,  использующие MDI,  имеют одно
        основное  окно,  внутри которого пользователь может открывать и
        работать   с   несколькими   документами.    Каждый    документ
        представляется собственным дочерним окном. Каждое дочернее окно
        имеет фрейм, системное меню, клавиши максимизации и минимизации
        и икону. Пользователь может обращаться с этими дочерними окнами
        как с обычными независимыми окнами,  за исключением  того,  что
        они не могут выходить за пределы родительского окна.

             В данной главе приведена следующая информация:

             - Структура прикладной программы, использующая MDI.

             - Написание    процедур    для    прикладной    программы,
               использующей MDI.

             - Управление   дочерними   окнами   прикладной  программы,
               использующей MDI.

             В данной главе в качестве примера  используется  текстовый
        редактор Multipad - поставляемый на  диске  SDK  Sample  Source
        Code Disk.  Multipad это версия Notepad,  использующая MDI.  (В
        действительности Multipad используется в качестве  примера  для
        нескольких глав данного руководства. В данной главе обсуждается
        только та часть Multipad, которая относится к MDI).
                 21.1  Структура прикладной программы, использующей MDI.       

             Как и большинство прикладных программ Windows,  прикладные
        программы, использующие, MDI содержат цикл обработки сообщений,
        который рассылает сообщения окнам данной прикладной  программы.
        Цикл обработки сообщений программ, использующих MDI, аналогичен
        циклу  обработки  сообщений  обычных   программ   Windows,   за
        исключением  того,  как  в нем происходит обработка ускорителей
        меню.

             Основное окно   программы,  использующей  MDI,  аналогично
        основному окну  большинства  программ  Windows.  В  программах,
        использующих MDI,  основное  окно называется окном фрейма.  Оно
        отличается от  обычных  окон  программ  Windows  тем,  что  его
        область пользователя заполняется специальными дочерними окнами,
        называемыми окнами пользователя. Поскольку Windows поддерживает
        окна пользователя    и    управляет    интерфейсом    MDI,   то
        пользовательской прикладной   программе    необходим    минимум
.
       Windows 3.0/pg/3#3                                       = 153 =

        информации о  пользовательском  интерфейсе  MDI.  (С этой точки
        зрения окно  пользователя  MDI  аналогично  блоку   управления,
        такому как   клавиша  установки.  Это  окно  имеет  стандартное
        поведение, которое Windows определяет автоматически. Прикладная
        программа может  использовать  окна пользователя MDI,  но ей не
        нужно определять его вид и поведение.)

             С виду    окно   пользователя   MDI   напоминает   большой
        монохромный прямоугольник.   Для    пользователя    это    окно
        представляет собой часть основного окна,  оно дает тот фон,  на
        котором отображаются дочерние окна. Обычно прикладная программа
        на каждый документ определят по одному дочернему окну. Дочерние
        окна MDI выглядят очень похоже  на  основное  окно.  Они  имеют
        фрейм   окна,   строку   заголовка,   клавиши   минимизации   и
        максимизации.  Основным отличием является то, что дочерние окна
        содержат  отдельные документы,  и что они не могут перемещаться
        вне основного окна.

             На рисунке 21.1 показан вид прикладной программы Multipad,
        которая является типичной программой, использующей MDI.

             Рисунок 21.1 Multipad: пример программы, использующей MDI.
             1. Окно фрейма.
             2. Область пользователя.
             3. Дочерние окна.

             В основном,  прикладная  программа  управляет  интерфейсом
        MDI, посылая сообщения вверх или вниз  по  иерархии  окон  MDI.
        Окна пользователя  MDI,  которыми управляет Windows,  выполняют
        многие операции, относящиеся к прикладной программе.

             Оставшаяся часть данной главы посвящена описанию того, как
        писать прикладные   программы,  использующие  MDI,  и  как  для
        управления дочерними окнами MDI использовать сообщения.
                                                                               
        21.2  Инициализация прикладной программы, использующей MDI.

             Первое, чем  прикладные   программы,   использующие   MDI,
        отличаются от   остальных   программ   Windows,   это   процесс
        инициализации. Хотя    в    основном    процесс   инициализации
        аналогичен, для   прикладных   программ,   использующих    MDI,
        требуется  установить  несколько  значений  в  структуре класса
        окна.

             Для инициализации прикладной программы,  использующей MDI,
        вы должны вначале зарегистрировать классы окон (если это первый
        экземпляр программы) так  же,  как  и  для  обычной  прикладной
        программы  Windows.  Затем  вы  создаете  и  отображаете  окна,
        которые должны быть видны.
                                 21.2.1  Регистрация классов окон.             

             В основном для  прикладной  программы,  использующей  MDI,
.
       Windows 3.0/pg/3#3                                       = 154 =

        требуется зарегистрировать два класса окон:

             - Класс для окна фрейма прикладной программы, использующей
               MDI. Структура  для  регистрации  класса   окна   фрейма
               аналогично структуре  регистрации  класса основного окна
               прикладной программы Windows, не используюещй MDI.

             - Класс  окна  для  дочерних  окон  прикладной  программы,
               использующей MDI. Структура регистрации класса для таких
               окон несколько  отличается  от   структуры   регистрации
               класса для обычных дочерних окон.

               Прикладная программа  может  иметь  больше одного класса
               дочерних окон MDI, если программа работает с несколькими
               типами документов.

             Заметим, что  прикладная  программа  не регистрирует класс
        окон - пользователей MDI, который определяется Windows.

             Структура класса   для   дочерних   окон   MDI   несколько
        отличается от  структуры  класса  для  обычных  дочерних окон в
        следующем:

             - Структура  класса  должна  определять  икону,  поскольку
               пользователь может минимизировать дочернее окно MDI.

             - Имя  меню должно быть NULL,  поскольку дочернее окно MDI
               не может иметь собственного меню.

             - Структура  класса  должна  резервировать  дополнительную
               память в   структуре   окна.  Это  позволяет  прикладной
               программе связывать   с   окном   определенные   данные,
               например, имя файла.

             В прикладной программе Multipad,  классы окон регистрирует
        локально определенная функция InitializeApplication.
                                         21.2.2  Создание окон.                

             После регистрации классов окон ваша  прикладная  программа
        может создать собственные окна. Она сначала создает окно фрейма
        с  помощью  функции   CreateWindow.   (В   System   Application
        Architecture,  Common  User  Access:  Advanced Interface Design
        Guide описано,  как должно выглядеть основное  окно  программы,
        использующей MDI).

             После создания  окна  фрейма  прикладная  программа  может
        создать  ее   окна   пользователя   MDI   с   помощью   функции
        CreateWindow.   В  качестве  имени  класса  вы  должны  указать
        MDICLIENT. MDICLIENT - это предопределенный класс окна, который
        регистрирует  Windows.  Параметр  lParam  функции  CreateWindow
        должен указывать  на  структуру  CLIENTCREATESTRUCT.  Структура
        CLIENTCREATESTRUCT содержит следующие поля:
.
       Windows 3.0/pg/3#3                                       = 155 =


        Поле             Описание
        ---------------------------------------------------------------
        hWindowMenu      Дескриптор накладываемого меню, используемого
                         для управления дочерним окном MDI.

                         После создания   дочернего   окна   прикладная
                         программа  может  добавить  заголовки дочерних
                         окон в  качестве  элементов  к  накладываемому
                         меню.  Пользователь может затем активизировать
                         дочернее окно,  выбрав соответствующий элемент
                         в меню. Multipad помещает эти заголовки в меню
                         "Window" и     получает     дескриптор     для
                         накладываемого   подменю   с  помощью  функции
                         GetSubMenu.

        idFirstChild     Это идентификатор первого дочернего окна MDI.

                         Первое дочернее  окно   MDI   получит   данный
                         идентификатор. Следующие  создаваемые дочерние
                         окна получат  следующие  идентификаторы.   При
                         удалении дочернего  окна Windows автоматически
                         переназначает их      идентификаторы       для
                         поддержания их допустимости.
        ---------------------------------------------------------------

             Когда заголовок дочернего окна добавляется  к  меню  окна,
        элементу меню  присваивается идентификатор дочернего окна,  что
        означает, что окно фрейма получит сообщение WM_COMMAND с  тремя
        идентификаторами в     lParam.    Таким    образом,    значение
        идентификатора для дочерних окон необходимо выбирать так, чтобы
        оно  не  конфликтовало  с  идентификаторами элементов меню окна
        фрейма.

             Окно пользователя MDI создается с  типом  WS_CLIPCHILDREN,
        поскольку окно не должно рисовать поверх дочерних окон.

             Локально определенная функция InitializeInstance программы
        Multipad создает окно фрейма MDI Multipad.  Однако Multipad  не
        создает  дочерние  окна  MDI  в  данной  точке.  Вместо  этого,
        создание этих окон выполняется в качестве  обработки  сообщения
        WM_CREATE   окна   фрейма.   Multipad   обрабатывает  сообщение
        WM_CREATE в функции MPFrameWndProc.  После создания окна фрейма
        и  окон  пользователей  MDI,  Multipad выполняет дополнительную
        инициализацию, такую как проверка драйвера принтера и  загрузка
        таблицы ускорителей.

             Затем Multipad  создает  первое  дочернее  окно  MDI,  или
        пустое, или  содержащее  файл,  имя  которого  было  указано  в
        командной строке. (Создание дочерних окон MDI описано в разделе
        21.7.1, "Создание дочерних окон".)

.
       Windows 3.0/pg/3#3                                       = 156 =

                           21.3  Создание цикла обработки сообщений.           

             Цикл обработки     сообщений     прикладной     программы,
        использующий MDI,  аналогичен циклу обработки сообщений обычной
        программы, за исключением того,  что для обработки  ускорителей
        дочерних окон используется функция TranslateMDISysAccel.

             Ускорители системного   меню   для   дочерних   окон   MDI
        аналогичны ускорителям системного меню обычного  окна.  Отличие
        состоит в  том,  что  дочерние окна отвечают на нажатие клавиши
        CONTROL, в то время как ускорители меню используются с клавишей
        Alt.

             Цикл обработки     сообщений     прикладной     программы,
        использующей MDI, обычно выглядит следующим образом:

             while(GetMessage(&msg,NULL,0,0))
             {
                if(!TranslateMDISysAccel(hwndMDIClient,&msg)
                   && !TranslateAccelerator(hwndFrame,hAccel,&msg))
                {
                   TranslateMessage(&msg);
                   DispatchMessage(&msg);
                }
             }

             В данном  примере  цикл  обработки  сообщений   прикладной
        программы, использующей   MDI,   аналогичен   циклу   обработки
        сообщений обычной   прикладной   программы,   за    исключением
        использования в   начале   функции  TranslateMDISysAccel  перед
        проверкой на ускорители меню.

             Функция TranslateMDISysAccel     преобразует     сообщения
        WM_KEYDOWN в  WM_SYSCOMMAND  для  активного дочернего окна MDI.
        Она возвращает FALSE, если это не ускоритель MDI, в этом случае
        прикладная программа  использует  функцию  TranslateAccelerator
        для  проверки  того,  что  это  обычный  ускоритель  прикладной
        программы.  Если  это  не  так,  то цикл передает это сообщение
        функции соответствующего окна.
                 21.4  Создание функции обработки сообщений окна фрейма.       

             Функция окна  фрейма  аналогична  функции  основного  окна
        прикладной программы. Однако имеются несколько отличий:

             - Обычно  функция  окна  посылает  все необрабатываемые ею
               сообщения функции DefWindowProc.  Функция окна для  окна
               фрейма MDI посылает сообщения функции DefFrameProc.

             - Функция  окна  фрейма  посылает функции DefFrameProc все
               сообщения, которые она не обрабатывает,  и, кроме этого,
               посылает   некоторые  сообщения,  которые  обрабатывает.
               Список всех сообщений,  которые  ваша  программа  должна
               передать  DefFrameProc,  приведен  в  томе 1 Справочного
.
       Windows 3.0/pg/3#3                                       = 157 =

               руководства.

             DefFrameProc также обрабатывает сообщения WM_SIZE, изменяя
        окно пользователя MDI так, чтобы оно помещалось в новую область
        пользователя. Прикладная программа  может  определить  дочерним
        окнам и меньший размер, если это нужно (например, для выделения
        места под строку состояния или резиновое окно).

             DefFrameProc также  устанавливает  захват  ввода  на  окно
        пользователя MDI  при  обнаружении сообщения WM_SETFOCUS.  Окно
        пользователя устанавливает захват ввода для активного дочернего
        окна, если такое есть. Как мы уже говорили, сообщение WM_CREATE
        заставляет окно фрейма создать окно пользователя MDI.

             Процедура окна фрема Multipad  называется  MPFrameWndProc.
        Обработка остальных  сообщений аналогична обработке сообщений в
        прикладных программах,   не   использующих    MDI.    Сообщение
        WM_COMMAND обрабатывается   в  Multipad  локально  определенной
        функцией CommandHandler,  которая вызывает функцию DefFrameProc
        в  случае,  если  она  не  обрабатывает это сообщение.  Если бы
        Multipad  не  делала  это,   то   пользователь   не   смог   бы
        активизировать   дочерние   окна   из   меню,   т.к.  сообщение
        WM_COMMAND,  посылаемое при выборе элемента в меню,  может быть
        потеряно.
                                                                               
        21.5 Создание  функции  обработки  сообщений для дочернего
                                     окна.

             Как и функция обработки  сообщений  окна  фрейма,  функция
        обработки сообщений   дочернего   окна  использует  специальную
        функцию для   обработки    сообщений    по    умолчанию.    Все
        необрабатываемые функцией  дочернего окна сообщения должны быть
        переданы функции DefMDIChildProc, вместо функции DefWindowProc.
        Кроме  этого,  некоторые  сообщения управления окнами (WM_SIZE,
        WM_MOVE,   WM_GETMINMAXINFI)   должны   передаваться    функции
        DefMDIChildProc даже   в  том  случае,  если  ваша  функция  их
        обрабатывает.  Это  делается  для  того,  чтобы  интерфейс  MDI
        корректно  функционировал.  Полный  список  сообщений,  которые
        необходимо передавать функции  DefMDIChildProc,  вы  найдете  в
        первом томе Справочного руководства.

             Функция обработки   сообщений   дочерних   окон   Multipad
        называется MPChildWndProc.
                                                                               
                21.6  Связывание данных с дочерними окнами.

             Поскольку число  дочерних   окон   может   отличаться,   в
        зависимости от  открытых  пользователем документов,  прикладная
        программа, использующая MDI, должна иметь возможность связать с
        каждым дочерним  окном некоторые данные (например,  имя файла).
        Это можно сделать двумя способами:

             - Запоминая данные в структуре окна.
.
       Windows 3.0/pg/3#3                                       = 158 =


             - Используя свойства окна.

             Оставшаяся часть данного раздела посвящена обсуждению этих
        способов.
                           21.6.1  Хранение данных в структуре окна.           

             При регистрации  класса  окна  прикладная  программа может
        зарезервировать дополнительную область  в  структуре  окна  для
        хранения данных прикладной программы, относящихся к конкретному
        окну.  Для  занесения  и  получения  данных  из  этой   области
        прикладная    программа   использует   функции   GetWindowWord,
        GetWindowLong, SetWindowWord и SetWindowLong.

             Если прикладной  программе  требуется  связать   с   окном
        большой объем  информации,  то  она  может занести ее в область
        памяти, получить дескриптор этой области данных и хранить его в
        структуре окна.

             Multipad использует    именно   этот   способ.   Например,
        сообщение WM_CREATE  обрабатывается  функцией   MPChildWndProc,
        которая  создает  многострочный  блок редактирования в качестве
        окна текстового редактора.  Multipad сохраняет дескриптор этого
        блока  в  структуре  данных  дочернего  окна  с помощью функции
        SetWindowWord.   При   необходимости    обращения    к    блоку
        редактирования  Multipad  использует  функцию GetWindowWord для
        получения  дескриптора  блока  редактирования.  Таким   образом
        Multipad   поддерживает   на   каждый   документ  по  несколько
        переменных.
                                                                               
                    21.6.2  Использование свойств окна.

             Ваша программа может также сохранять данные, относящиеся к
        документу, используя  свойства  окна.  Свойства  отличаются  от
        хранения данных в структуре окна тем, что не требуется выделять
        дополнительную память  при регистрации класса окна.  Аналогично
        тому, как в структуре вы запоминаете дескриптор области данных,
        так и к свойствам вы обращаетесь по именной строке.

             Связью с  каждым свойством является дескриптор.  Например,
        Multipad могла бы иметь свойство  с  именем  "EditControl"  для
        хранения    дескриптора    редактируемого   блока   управления.
        Дескриптор может быть в  действительности  любым  двух-байтовым
        значением,  и  может быть дескриптором структуры данных.  Часто
        свойства более удобны,  чем дополнительные данные  в  структуре
        окна,  поскольку при использовании свойств прикладная программа
        не должна вычислять необходимый размер дополнительной области в
        структуре  окна  и вычислять смещения на различные элементы.  С
        другой стороны, доступ к данным в структуре окна осуществляется
        быстрее, чем доступ к свойствам.

.
       Windows 3.0/pg/3#3                                       = 159 =

                                21.7  Управление дочерними окнами.             

             Для управления   дочерними  окнами  прикладная  программа,
        использующая MDI, посылает сообщения окнам - пользователям MDI.
        Это   включает  в  себя  создание,  разрушение,  активизацию  и
        изменение состояния дочерних окон.

             В основном,  прикладная  программа  может  иметь  дело   с
        текущим активным   дочерним   окном.   Например,   в  Multipad,
        большинство команд меню File и все команды меню Edit  и  Search
        обращаются   к   текущему   активному  окну.  Поэтому  Multipad
        поддерживает  две  переменные  -  hWndActivEdit  и   hWndActiv,
        поскольку только эти два окна могут получать сообщения.

             Однако имеются исключения.  Например, прикладная программа
        может послать сообщение всем дочерним окнам,  чтобы  определить
        их состояние.   Multipad   делает  это  при  завершении,  чтобы
        убедиться, что все файлы были сохранены.

             Поскольку дочерние окна MDI могут быть в  виде  иконы,  то
        прикладная программа  должны  проявлять осторожность,  чтобы не
        манипулировать окнами в виде иконы как обычным  дочерним  окном
        MDI. Окна  в виде иконы отображаются когда прикладная программа
        причисляет дочерние окна к пользователю MDI,  но  окна  в  виде
        иконы отличаются  от других дочерних окон тем,  что ими владеет
        дочернее окно MDI.  Таким образом,  для определения  того,  что
        окно представляется   в  виде  иконы,  необходимо  использовать
        функцию GetWindow с индексом GW_OWNER. Окна не иконы возвращают
        NULL.   Заметим,  что  такая  проверка  недостаточна  для  окон
        верхнего уровня, таких как накладываемые окна и панели диалога,
        поскольку они также владеют окнами.

             Оставшаяся часть  данной  главы посвящена тому,  как можно
        создать, разрушить,    активизировать,    деактивизировать    и
        переставить дочерние окна MDI.
                                   21.7.1  Создание дочерних окон.             

             Для создания   дочернего  окна  MDI  прикладная  программа
        посылает сообщение WM_MDICREATE пользователю  MDI.  (Прикладная
        программа не   должна  использовать  функцию  CreateWindow  для
        создания дочерних окон MDI).  Параметр lParam сообщения  должен
        содержать указатель   на   структуру  MDICREATESTRUCT,  которая
        содержит поля, аналогичные параметрам функции CreateWindow.

             Multipad создает дочерние  окна  MDI  с  помощью  локально
        определенной функции  AddFile  (содержится  в  файле MPFILE.C).
        Функция AddFile задает заголовок окна,  присваивая полю szTitle
        структуры MDICREATESTRUCT   либо   имя   файла,   либо   строку
        "Untitled".  В поле szClass заносится имя класса дочерних окон,
        зарегистрированного функцией Multipad InitializeApplication.  В
        поле  владельца,  hOwner,   заносится   дескриптор   экземпляра
        прикладной программы.

.
       Windows 3.0/pg/3#3                                       = 160 =

             Структура MDICREATESTRUCT     содержит     четыре    поля,
        определяющих размеры:  x и  y,  которые  определяют  координаты
        окна, и cx и cy,  определяющие размеры окна.  Все или некоторые
        из них могут  содержать  значение  CW_USEDEFAULT,  при  котором
        позиция и/или  размеры  окна  будут задаваться в соответствии с
        каскадным алгоритмом Windows.  В любом случае,  все четыре поля
        должны   быть   инициализированы.   Multipad  во  всех  случаях
        использует CW_USEDEFAULT.

             Последнее поле - style - содержит биты типа окна.  Windows
        требует для  дочерних  окон  MDI некоторые типы и оставляет вам
        некоторые другие, а также маскирует не соответствующие.

             Для определения    исходного    состояния    окна    можно
        использовать биты WS_MINIMIZE или WS_MAXIMIZE.

             Указатель, посылаемый   в   параметре   lParam   сообщения
        WM_MDICREATE, передается создаваемому окну  и  является  первым
        полем структуры    CREATESTRUCT,   передаваемой   в   сообщении
        WM_CREATE. В Multipad дочерние окна  инициализируют  сами  себя
        при обработке  сообщения  WM_CREATE,  инициализируя  переменные
        документов в дополнительной области структуры окна,  и создавая
        блок редактирования дочернего окна.
                                                                               
                     21.7.2  Разрушение дочерних окон.

             Для разрушения  дочернего  окна MDI используется сообщение
        WM_MDIDESTROY. Дескриптор дочернего окна передается в  качетсве
        параметра wParam.
                    21.7.3  Активизация и деактивизация дочерних окон.         

             Активное окно  может  быть  изменено  с  помощью сообщений
        WM_MDICREATE и     WM_MDIACTIVATE.     Сообщение     WM_MDINEXT
        активизирует следующее в списке окон дочернее окно, а сообщение
        WM_MDIACTIVATE активизирует   дочернее    окно,    определяемое
        параметром сообщения  lParam.  Активизация  обычно  выполняется
        пользователем через пользовательский  интерфейс  MDI.  Multipad
        напрямую не использует ни одно из этих сообщений.

             Основным использованием  сообщения WM_MDIACTIVATE является
        следование   за   изменением    активного    окна.    Сообщение
        WM_MDIACTIVATE   посылается   также   дочерним  окнам  MDI  при
        активизации  и   деактивизации.   Таким   образом,   отслеживая
        сообщения WM_MDIACTIVATE, прикладная программа может определять
        текущее активное окно MDI.

             Multipad поддерживает    две    глобальные     переменные,
        hwndActive и   hwndActivEdit,   которые  являются  дескриптором
        текущего активного дочернего  окна  MDI  и  его  редактируемого
        блока соответственно.  Отслеживание  этих окон упрощает посылку
        сообщений.

.
       Windows 3.0/pg/3#3                                       = 161 =

             Получить текущее  активное  окно  MDI  можно   с   помощью
        сообщения WM_MDIGETACTIV,  который возвращает активное дочернее
        окно в  младшем  слове.  Затем,  прикладная   программа   может
        использовать  функцию  GetWindowWord  для получения дескриптора
        окна для блока редактирования.  Для явного максимизирования или
        восстановления дочернего   окна   прикладная   программа  может
        использовать сообщения WM_MDIMAXIMAZE и WM_MDIRESTORE, параметр
        wParam  которых  определяет дескриптор изменяемого окна.  Опять
        же,  это сообщения,  которые  прикладная  программа  не  должна
        обычно     использовать,     поскольку     Windows    управляет
        пользовательским  интерфейсом  MDI   в   интересах   прикладной
        программы.
                                                                               
               21.7.4  Расстановка дочерних окон на экране.

             Windows предоставляет  три  служебных  сообщения,  которые
        вы можете использовать для расстановки  дочерних  окон  MDI  на
        экране.

        Сообщение           Описание
        ---------------------------------------------------------------
        WM_MDICASCADE       Расставляет все дочерние окна, не иконы,
                            в порядке от левого верхнего угла по нап-
                            равлению к правому нижнему. (Это сообщение
                            также расставляет дочерние иконы).

        WM_MDIICONARRANGE   Расставляет все дочерние иконы окна в нижней
                            части окна пользователя MDI.

        WM_MDITILE          Расставляет все дочерние окна, не иконы,
                            так, чтобы  они  были  встык  внутри   окна
                            пользователя   MDI.  (Это  сообщение  также
                            расставляет иконы).
        ---------------------------------------------------------------

.
       Windows 3.0/pg/3#3                                       = 162 =

                                             21.8  Заключение.                 

             В данной   главе   приводится   основная   информация   об
        интерфейсе множества документов Windows и описывается структура
        прикладной программы,  использующей этот интерфейс. Кроме этого
        приводится информация,  поясняющая,   как   писать   прикладные
        программы, использующие MDI.

             Дополнительную информацию относительно MDI вы найдете в:

        Раздел               Руководство
        ---------------------------------------------------------------
        Создание и управ-    Справочное руководство, том 1: Глава 1,
        ление окнами         "Функции системы управления окнами".

        Структура класса     Справочное руководство, том 1: Глава 1,
        окна                 "Функции системы управления окнами".

        Функции MDI          Справочное руководство, том 1: Глава 1,
                             "Функции системы управления окнами", и
                             Глава 4, "Список функций".

        Пример программы,    Пример прикладной программы Multipad,
        использующей MDI     поставляемой на диске SDK Sample Source
                             Code Disk.


.
       Windows 3.0/pg/3#3                                       = 163 =

                              Глава 22. Динамический обмен данными.            
       ----------------------------------------------------------------
             Microsoft Windows   предоставляет  несколько  методов  для
        передачи данных между  программами.  Одним  из  путей  передачи
        данных -  является  использование  динамического обмена данными
        Windows (DDE). DDE - это протокол передачи сообщений для обмена
        данными  между  прикладными  программами Windows.  Он позволяет
        разработчикам    программного    обеспечения     организовывать
        разделение данных между прикладными программами и таким образом
        предоставить пользователю более интегрированную среду.

             В данной  главе  содержится  руководство   по   реализации
        протокола DDE  в  ваших прикладных программах.  Полное описание
        протокола содержится во втором томе Справочного руководства.

             В данной главе содержится информация по следующим темам:

             - Обмен данными в Windows.

             - Концепции DDE.

             - Сообщения DDE.

             - Поток сообщений DDE.

             В данной главе также описано, как использовать два примера
        программ, Client и Server, которые иллюстрируют данные темы.
                                                                               
                      22.1  Обмен данными в Windows.

             В основном,  Windows  поддерживает  три  механизма  обмена
        данными между прикладными программами:

             - Передача через системный буфер.

             - Динамически подключаемые библиотеки.

             - Динамический обмен данными.

             Windows не поддерживает разделение  напрямую  дескрипторов
        глобальных блоков памяти.  Для работы с расширенной памятью,  а
        так же для совместимости с будущими  версиями  Windows,  вы  не
        должны пытаться  получить  адрес  (c  помощью GlobalUnlock) или
        любым другим способом манипулировать  дескрипторами  глобальных
        блоков  памяти,  созданных другой прикладной программой.  Такую
        возможность предоставляет  вам  только  DDE,  с  использованием
        которого  вы  можете  передавать  дескрипторы глобальных блоков
        памяти в другие прикладные программы.

.
       Windows 3.0/pg/3#3                                       = 164 =

                                                                               
              22.1.1  Передача данных через системный буфер.

             Системный буфер позволяет пользователю  передавать  данные
        между прикладными    программами    в   системе.   Пользователь
        инициирует команду   прикладной   программы   для   копирования
        выбранных данных в системный буфер.  Затем, в другой прикладной
        программе пользователь инициирует команду по вставке данных  из
        системного буфера в область данных второй прикладной программы.
        В  основном,  системный  буфер  используется   для   временного
        хранения  данных  при  прямом  переносе пользователем данных из
        одной прикладной программы в другую.
                                                                               
               22.1.2  Динамически подключаемые библиотеки.

             Динамически подключаемые  библиотеки  (DLL)   могут   быть
        созданы для обеспечения временного хранения данных, разделяемых
        прикладной программой.  В этом случае DLL  рассматривается  как
        программный  интерфейс для получения и сохранения данных.  Сами
        данные хранятся в локальной динамической области памяти DLL или
        в ее сегменте данных в области статических данных.  Дескрипторы
        или адреса этих данных могут быть посланы прикладной  программе
        только  как логические идентификаторы,  но прикладная программа
        не может их  получить  в  действительности.  Только  DLL  может
        преобразовывать эти идентификаторы в действительные дескрипторы
        или адреса  с  помощью  функций  GlobalUnlock,  LocalUnlock.  В
        основном,  для  обмена  данными  вы  можете использовать только
        сегмент данных DLL.
                                22.1.3  Динамический обмен данными.            

             Протокол Windows DDE  является  стандартом  для  связанных
        прикладных программ,  который позволяет им обмениваться данными
        и инициировать удаленные команды в виде сообщений Windows.

             Поскольку Windows базируется на архитектуре,  использующей
        сообщения, то  посылка  сообщений  является наиболее подходящим
        методом для автоматической передачи  данных  между  прикладными
        программами. Однако,  сообщения  Windows  для  передачи  данных
        имеют только два  параметра  (wParam  и  lParam).  Поэтому  эти
        параметры могут использоваться для передачи косвенных ссылок на
        другие блоки информации.

             Протокол DDE точно  определяет,  как  параметры  lParam  и
        wParam  используются  для передачи больших блоков данных в виде
        глобальных атомов и глобальных дескрипторов разделяемой памяти.

             Глобальный атом  -  это  ссылка  на  текстовую  строку.  В
        протоколе DDE  атомы используются для идентификации информации,
        которой   обмениваются   прикладные   программы,   для    самой
        обмениваемой  информации,  для определения природы обмениваемой
        информацией.

             Дескриптор разделяемого глобального  блока  памяти  -  это
        дескриптор  блока  памяти,  выделенного  функцией GlobalAlloc с
        использованием ключа GMEM_DDECHARE. В протоколе DDE разделяемые
        глобальные объекты используются для хранения передаваемых между
        прикладными программами данных,  параметров протокола  и  строк
.
       Windows 3.0/pg/3#3                                       = 165 =

        выполняемых удаленных команд.

             Протокол DDE   имеет   очень   специфические  правила  для
        назначения ответственности прикладных программ,  участвующих  в
        обмене данными  с использованием DDE,  для выделения и удаления
        глобальных атомов и разделяемых объектов памяти. Во втором томе
        Справочного руководства,  глава  15,  содержатся  описание этих
        правил для каждого сообщения.
                             22.1.4  Когда использовать Windows DDE.           

             Windows DDE наиболее  подходит  для  обмена  данными,  при
        котором не  требуется  взаимодействие  с пользователем.  Обычно
        прикладная программа  предоставляет  пользователю   метод   для
        установления связи      между      прикладными     программами,
        обменивающимися данными.  Однако,  после   установления   связи
        программы обмениваются данными без участия пользователя.

             DDE может  быть использован в широком диапазоне прикладных
        задач, включая:

             - Подсоединение  данных  реального  времени,  таких,   как
               модификации   основных  данных  для  конкретного  рынка,
               научные инструменты или данные управление процессом.

             - Создание  сложных  документов,   таких   как   документы
               текстового процессора, включающих графики, выполненные в
               графических программах.  С  использованием  DDE  графики
               могут  изменяться  при  изменении исходных данных,  в то
               время,   как   оставшаяся   часть   документа   остается
               неизменной.

             - Создание  очередей данных между прикладными программами,
               например, электронная таблица встает в  очередь  к  базе
               данных для получения последних счетов.
                            22.1.5  DDE с точки зрения пользователя.           

             Следующий пример   иллюстрирует,   как   с   точки  зрения
        пользователя взаимодействуют две прикладные программы Windows с
        использованием DDE.

             Пользователь хотел,  чтобы  электронная  таблица Microsoft
        Excel  отслеживала   стоимость   определенных   акций   на   на
        Нью-Йоркской   фондовой  бирже.  Пользователь  имел  прикладную
        программу Quote которая,  в свою очередь имела доступ к  данным
        Нью-Йоркской фондовой биржи.  Взаимодействие этих двух программ
        выглядело бы следующим образом.

             - Пользователь инициализирует взаимодействие, указывая имя
               прикладной    программы    (Quote),   которая   получает
               интересующие его данные. В результирующем преобразовании
               с    использованием    DDE   применяется   к   стоимости
.
       Windows 3.0/pg/3#3                                       = 166 =

               интересующих акций.

             - Excel рассылает имя прикладной программы и  имя  раздела
               всем работающим прикладным программам, использующим DDE.
               Quote отвечает,  устанавливая связь с Excel,  о  данных,
               требуемых от Нью-Йоркской фондовой биржи.

             - Пользователь может затем потребовать,  чтобы электронная
               таблица автоматически   модифицировала   результат   при
               изменении   исходных  данных,  введя  формулу  в  ячейку
               электронной таблицы.   Например,   пользователю    может
               потребоваться выполнить  вычисления  при изменении курса
               продаж акций IBM.  Он может это сделать,  введя в ячейку
               электронной таблицы формулу:

               = 'Quote' | 'NYSE'!IBM

             - Пользователь может прекратить автоматическую модификацию
               курса акций IBM в любой  момент.  Соединение  по  другим
               данным, установленное отдельно, например, по курсу акций
               других фирм,  останется активным при использовании  того
               же соединения для Нью-Йоркской фондовой биржи (NYSE).

             - Пользователь может также прервать весь обмен между Excel
               и Quote по NYSE таким образом,  что в  дальнейшем  может
               быть установлена   связь   по  определенным  данным  без
               повторной его инициализации.
                                                                               
                           22.2  Концепции DDE.

             Для понимания   динамического  обмена  данными  необходимо
        описание некоторых  концепций  и   определенной   терминологии.
        Наиболее важные из них описаны в следующих разделах.
                                  22.2.1  Клиент, сервер и диалог.             

             Две прикладные  программы,   осуществляющие   динамический
        обмен данными,  называются  вовлеченными  в диалог.  Прикладная
        программа, которая инициирует диалог,  известна как  клиент,  а
        прикладная программа, обслуживающая клиента - как сервер. Любая
        прикладная программа может быть вовлечена в несколько  диалогов
        одновременно, действуя как клиент в одном случае и как сервер -
        в другом.

             DDE -   диалог   между   двумя   программами,   фактически
        осуществляется между двумя окнами,  принадлежащими каждое своей
        программе. Окно может быть основным окном прикладной программы,
        окном, связанным   с   отдельным   документом   (в  программах,
        использующих интерфейс MDI),  или  скрытым  (невидимым)  окном,
        специально   предназначенным   для   обработки  сообщений  DDE.
        Поскольку диалог в DDE определяется  парой  дескрипторов  окон,
        участвующих в нем,  то ни одно окно не может участвовать больше
        чем в одном диалоге одновременно. Прикладная программа "сервер"
.
       Windows 3.0/pg/3#3                                       = 167 =

        или прикладная  программа "клиент" должны для каждого диалога с
        определенной программой сервера или клиента заводить  отдельное
        окно.

             Для исключения   возможности  участия  окна  в  нескольких
        диалогах вы можете  для  каждого  диалога  создавать  отдельное
        скрытое окно.  Их  назначение  заключается  только  в обработке
        сообщений DDE.
                              22.2.2  Элемент, предмет, приложение.            


             Для однозначной   идентификации   элементов   данных   DDE
        использует  трехуровневую  их   иерархию:   элемент,   предмет,
        приложение.

             Каждый диалог  DDE  полностью  определяется  приложением и
        предметом. В начале диалога DDE клиент и сервер соглашаются  на
        имя приложения   и   предмет.   Приложение  -  это  обычно  имя
        прикладной программы сервера.  Например,  в диалоге с Microsoft
        Excel,  выступающим в качестве сервера,  приложение будет иметь
        имя "Excel".

             Предмет - это логический контекст данных,  внутри которого
        могут "обсуждаться"  (изменяться)  различные  данные  во  время
        диалога. Для   программ,   которые    оперируют    документами,
        хранящимися  в  файлах,  предмет  -  это обычно имя файла;  для
        других прикладных программ - это имя,  зависящее от  прикладной
        программы.

             Поскольку окно    клиента    и    окно    сервера   вместе
        идентифицируют диалог   DDE,   имя   приложения   и    предмет,
        определяющие диалог,  они  не  должны изменяться во время всего
        диалога.

             Элементы данных DDE - это в  действительности  информация,
        относящаяся к предмету диалога, которой обмениваются прикладные
        программы.  Значения элементов данных  могут  быть  посланы  от
        сервера клиенту или от клиента серверу.  Формат элемента данных
        может быть любым из форматов данных,  поддерживаемых  системным
        буфером (Смотрите том 1 Справочного руководства).
                                                                               
        22.2.3  Постоянная ("горячая" и "теплая") связь по данным.

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

             В DDE имеется  два  типа  постоянной  связи:  "горячая"  и
        "теплая". При  теплой связи сервер при изменении объекта данных
.
       Windows 3.0/pg/3#3                                       = 168 =

        уведомляет об этом прикладную программу клиента, но сами данные
        не посылает,  пока  клиент  не  запросит их.  При горячей связи
        сервер при изменении данных сразу же посылает их клиенту.

             Прикладные программы,  поддерживающие горячую  или  теплую
        связь, обычно  содержат  в  меню Edit команды Copy Link и Paste
        Link,  для того,  чтобы пользователь мог установить связь между
        прикладными программами. Смотрите раздел 22.4.3, "Инициализация
        связи данных с помощью команды Paste Link".
                                           22.3  Сообщения DDE.                

             Поскольку протокол DDE базируется на  передаче  сообщений,
        то он  не использует специальных функций или библиотек Windows.
        Все транзакции выполняются с помощью передачи между программами
        клиентом и сервером некоторых, определенных DDE сообщений.

             Имеется девять сообщений DDE.  Символические константы для
        этих сообщений определены во включаемом файле SDK DDE.H, а не в
        WINDOWS.H. Некоторые  структуры  данных для различных сообщений
        DDE также определены в DDE.H.

             Ниже приведено   краткое   описание   всех   этих   девяти
        сообщений. Полное  описание  сообщений  DDE приведено во втором
        томе Справочного руководства.

        Сообщение            Описание
        ---------------------------------------------------------------
        WM_DDE_ASK           Посылается в ответ на полученное сообщение.
                             Определяет положительное или отрицательное
                             подтверждение в получении сообщения.

        WM_DDE_ADVISE        Требование от прикладной программы сервера
                             поддержки модификации  или уведомления при
                             изменении элемента данных.  Это  сообщение
                             устанавливает постоянную связь данных.

        WM_DDE_DATA          Посылает значение элемента данных приклад-
                             ной программе клиенту.

        WM_DDE_EXECUTE       Посылает строку прикладной программе сер-
                             веру, которая   должна  выполнить  ее  как
                             последовательность команд.

        WM_DDE_INITIATE      Инициализирует диалог между прикладными
                             программами сервера и клиента.

        WM_DDE_POKE          Посылает значение элемента данных приклад-
                             ной программе серверу.

        WM_DDE_REQUEST       Запрашивает у прикладной программы сервера
                             значение элемента данных.

.
       Windows 3.0/pg/3#3                                       = 169 =

        WM_DDE_TERMINATE     Завершает диалог.

        WM_DDE_UNADVISE      Завершает постоянную связь данных.
        ---------------------------------------------------------------
                                      22.4  Поток сообщений DDE.               

             Типичный диалог с использованием DDE состоит из  следующих
        событий:

             1. Прикладная программа - клиент инициализирует диалог,  а
                программа сервер ей отвечает.

             2. Прикладные    программы    обмениваются    данными    с
                использованием любого    или   всех   нижеперечисленных
                методов:

                - По запросу программы - клиента  сервер  посылает  ему
                  данные.

                - Программа  -  клиент посылает серверу незапрашиваемые
                  данные.

                - Программа - клиент запрашивает  программу  -  сервер,
                  чтобы  она  посылала данные при их изменении (горячая
                  связь).

                - Программа - клиент запрашивает  программу  -  сервер,
                  чтобы  она  посылала уведомление при изменении данных
                  (теплая связь).

                - Сервер  выполняет  команды  по  запросу  программы  -
                  клиента.

             3. Либо клиент, либо сервер завершает диалог.

             В следующих разделах описан нормальный поток сообщений DDE
        между клиентом и сервером.
                                   22.4.1  Инициализация диалога.              

             Для инициализации     диалога    DDE    клиент    посылает
        широковещательное сообщение   WM_DDE_INITIATE.   Обычно  клиент
        посылает его всем прикладным программам,  указывая  в  качестве
        первого параметра  -1.  Однако,  если  программе  - клиенту уже
        известен  дескриптор  окна  программы  сервера,  то  она  может
        послать   его  прямо  этому  окну.  Клиент  подготавливает  для
        приложения   атомы   и   имя   предмета   с   помощью   функции
        GlobalAddAtom.   Клиент   может   затребовать  связь  со  всеми
        потенциальными программами  -  серверами  по  любому  предмету,
        указав  нулевые  атомы (шаблон) соответственно для приложения и
        предмета.

.
       Windows 3.0/pg/3#3                                       = 170 =

             Следующий пример иллюстрирует,  как клиент  инициализирует
        диалог, указывая приложение и предмет.

        (1)  atomApplication = GlobaAddlAtom("Server");
             atomTopic = GlobalAddAtom(szTopic);
        (2)  SendMessage(-1,
                         WM_DDE_INITIATE,
                         hWndClientDDE,
                         MAKELONG(atomApplication,atomTopic));
        (3)  GlobalDeleteAtom(atomApplication);
             GlobalDeleteAtom(atomTopic);

             В данном примере:

        1)   Прикладная программа - клиент создает два глобальных атома,
             содержащих соответственно имя сервера и имя предмета.

        2)   Программа - клиент посылает  сообщение  WM_DDE_INITIATE  с
             lParam, содержащим имя приложения и  предмет.  Специальный
             дескриптор -1  заставляет  Windows разослать это сообщение
             всем остальным  активным  прикладным  программам.  Функция
             SendMessage не возвращает управление программе до тех пор,
             пока программы, получившие это сообщение, не вернут его, в
             свою очередь,  Windows.  Это означает, что все посланные в
             ответ  сервером  сообщения  WM_DDE_ASK  будут   обработаны
             клиентом после возврата из функции SendMessage.

        3)   После возврата управления функцией SendMessage  программа -
             клиент удаляет глобальные атомы.

             Программа сервер   отвечает   в  соответствии  с  логикой,
        показаной на рисунке 22.1.

             Рисунок 22.1  Реакция на сообщение WM_DDE_INITIATE.
             1. Указано то приложение?
             2. Это экземпляр нужного приложения?
             3. Ничего не делать (возврат).
             4. Требуемый предмет?
             5. Послать клиенту положительное сообщение WM_DDE_ASK на
                каждый поддерживаемый приложением предмет.
             6. Данное приложение поддерживает этот предмет?
             7. Послать клиенту положительное сообщение WM_DDE_ASK на
                требуемый предмет.

             Для подтверждения одного или нескольких  предметов  сервер
        должен  создать  атомы  для каждого дилога (требуя дублирование
        атома с именем приложения, если имеются несколько предметов), и
        для каждого диалога послать сообщение WM_DDE_ASK,  как показано
        ниже:

             atomApplication = GlobaAddlAtom("Server");
             atomTopic = GlobalAddAtom(szTopic);
             if(!SendMessage(-1,
.
       Windows 3.0/pg/3#3                                       = 171 =

                         WM_DDE_ASK,
                         hWndServerDDE,
                         MAKELONG(atomApplication,atomTopic))
             {
                GlobalDeleteAtom(atomApplication);
                GlobalDeleteAtom(atomTopic);
             }

             Когда сервер  отвечает  с  помощью  сообщения  WM_DDE_ASK,
        программа - клиент должна сохранить  дескриптор  окна  сервера.
        Программа  - клиент получает этот дескриптор в параметре wParam
        сообщения WM_DDE_ASK.  Программа - клиент после этого  посылает
        все следующие сообщения этому окну программы - сервера.

             Если программа   -   клиент   использует  для  определения
        приложения или предмета нулевой атом,  то  необходимо  помнить,
        что  она  получит ответы от больше чем одной программы сервера.
        Как сказано  в  разделе  22.2.1,  "Клиент,  сервер  и  диалог",
        создание для  каждого диалога уникальных скрытых окон исключает
        возможность участия одного окна больше  чем  в  одном  диалоге.
        Однако, чтобы  следовать  этому,  прикладная  программа клиент,
        должна завершить диалоги со  всеми,  кроме  одной,  программами
        серверами,  откликнувшимися  на  посланное  клиентом  сообщений
        WM_DDE_INITIATE.
                                 22.4.2  Передача одного элемента.             

             После установления   диалога   клиент   может   с  помощью
        сообщения WM_DDE_REQUEST получить от сервера элемент данных или
        сообщения WM_DDE_POKE передать на сервер элемент данных.

                     Получение элемента данных от сервера.

             Для получения  элемента  данных  от  сервера  программа  -
        клиент  посылает  серверу  сообщение WM_DDE_REQUEST в следующей
        форме:

             atomItem = GlobalAddAtom(szItemName);
             if(!PostMessage(hWndServerDDE,
                         WM_DDE_REQUEST,
                         hWndClientDDE,
                         MAKELONG(CF_TEXT,atomItem));
             GlobalDeleteAtom(atomItem);

             В данном примере  клиент  в  качестве  формата  требуемого
        элемента определяет формат системного буфера CF_TEXT.

             Получатель сообщения    WM_DDE_REQUEST   (сервер)   обычно
        отвечает за удаление атома элемента, однако, если вызов функции
        PostMessage завершится  по  ошибке,  то  атом  должен   удалить
        клиент.

             Если у сервера имеется доступ к требуемому элементу  и  он
.
       Windows 3.0/pg/3#3                                       = 172 =

        может преобразовать   его   к  требуемому  формату,  то  сервер
        копирует значение элемента как  объект  глобальной  разделяемой
        памяти и   посылает  клиенту  сообщение  WM_DDE_DATA  следующим
        образом:

             /* Выделить блок размером в заголовок данных DDE плюс */
             /* данные: строка, . Завершающий нулевой */
             /* байт считается в DDEData.Value[1] */

        (1)  if(!(hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                 (LONG)sizeof(DDEDATA)+strlen(szItemValue)+2)))
                 return;

        (2)  if(!(lpData = (DDEDATA FAR *)GlobalLock(hData)))
             {
                GlobalFree(hData);
                return;
             }
                .
                .
                .
        (3)  lpData -> cfFormat = CF_TEXT;
        (4)  lstrcopy((LPSTR)lpData->Value,(LPSTR)szItemValue);
             /* каждая строка данных завершается  */
             lstrcat((LPSTR)lpData->Value,(LPSTR)"\r\n");
        (5)  GlobalUnlock(hData);
        (6)  atomItem = GlobalAddAtom(szItemName);
        (7)  if(!PostMessage(hWndClientDDE,
                         WM_DDE_DATA,
                         hWndServerDDE,
                         MAKELONG(hData,atomItem));
             {
                GlobalFree(hData);
                GlobalDeleteAtom(atomItem);
             }

             В данном примере:

        (1)  Программа - сервер  выделяет блок  памяти   для   хранения
             элемента данных.   Блок   памяти   выделяется   с   ключем
             GMEM_DDESHARE, который   позволяет   серверу   и   клиенту
             разделять один блок памяти.

        (2)  Затем, программа - сервер для  получения  адреса блокирует
             блок памяти.  Блок  данных   инициализируется   структурой
             данных DDEDATA.

        (3)  Программа - сервер устанавливает  в  поле  cfFormat  блока
             данных значение CF_TEXT  для  идентификации  используемого
             формата.

        (4)  Клиент копирует  значение  требуемых  данных  в поле Value
             структуры DDEDATA.
.
       Windows 3.0/pg/3#3                                       = 173 =


        (5)  Здесь сервер заполняет блок  данных  и  разблокирует  блок
             памяти.

        (6)  Затем сервер   создает   глобальный  атом, содержащий  имя
             элемента данных.

        (7)  В конце, сервер с  помощью  функции  PostMessage  посылает
             сообщение WM_DDE_DATA.   В   параметре   lParam  сообщения
             передается дескриптор блока данных и атом,  содержащий имя
             элемента данных.

             Если сервер не может  удовлетворить  запрос,  он  посылает
        клиенту отрицательное сообщение WM_DDE_ASK:

             /* отрицательное сообщение */
             PostMessage(hWndClientDDE,
                     WM_DDE_ASK,
                     hWndServerDDE,
                     MAKELONG(0,atomItem));

             При получении сообщения WM_DDE_DATA,  программа  -  клиент
        обрабатывает   его   соответствующим  образом.  Затем,  если  в
        указанном сообщении WM_DDE_DATA бит  fAskReq  установлен  в  1,
        клиент  должен послать положительное сообщение WM_DDE_ASK,  как
        показано ниже:

             hData = LOWORD(lPAram);    /* для сообщения WM_DDE_DATA */
             atomItem = HIWORD(lParam);
        (1)  if(!(lpDDEData = (DDEDATA FAR *)GlobalLock(hData)) ||
                 (lpDDEData -> cfFormat != CF_TEXT))
             {

                /* отрицательное сообщение */
                PostMessage(hWndServerDDE,
                        WM_DDE_ASK,
                        hWndClientDDE,
                        MAKELONG(0,atomItem));
             }

             /* скопировать данные из lpDDEData */

        (2)  if(lpDDEData -> fAskReq)
             {
                /* положительное сообщение */
                PostMessage(hWndServerDDE,
                        WM_DDE_ASK,
                        hWndClientDDE,
                        MAKELONG(0x8000,atomItem));
             }
        (3)  bRelease = lpDDEData->fRelease;
             GlobalUnlock(hData);
             if(bRelease)
.
       Windows 3.0/pg/3#3                                       = 174 =

                GlobalFree(hData);

             В данном примере:

        1)   Клиент проверяет формат данных. Если это CF_TEXT (или если
             клиент не   смог   выделить  память  для  данных),  клиент
             посылает отрицательное сообщение WM_DDE_ASK для  индикации
             того, что не может обработать данные.

        2)   Если клиент  может  обработать  данные,  он проверяет флаг
             fAskReq структуры данных DDEDATA,  чтобы определить, нужно
             ли  сообщать  серверу,  что  данные  получены и обработаны
             успешно.  Если  да,  то  клиент   посылает   положительное
             сообщение WM_DDE_ASK.

        3)   Клиент сохраняет  флаг  fRelease  перед  разблокированием
             блока данных,  поскольку  при  разблокировании   указатель
             становится некорректным.   Затем   он  проверяет  значение
             флага, чтобы определить нужно,  ли очищать блок глобальной
             памяти, и действует соответственно.

             При получении  отрицательного  ответа  WM_DDE_ASK   клиент
        может снова  запросить  тот  же  самый  элемент данных,  указав
        другой формат системного буфера.  Обычно в начале запрашивается
        наиболее сложный   формат,  поддерживаемый  клиентом,  и  затем
        запросы повторяются с уменьшением сложности форматов,  пока  не
        будет найден формат, поддерживаемый сервером.

             Если сервер поддерживает элемента Formats предмета System,
        клиент может   сразу  определить,  какие  форматы  поддерживает
        сервер,  вместо  того,  чтобы  определять  это  последовательно
        запрашивая  сервер.  Информацию о предмете System вы найдете во
        втором томе Справочного руководства.

                          Посылка элемента на сервер.

             Клиент может  послать на сервер значение элемента данных с
        помощью сообщения WM_DDE_POKE.  Клиент  определяет  и  посылает
        элемент следующим образом:

             if(!(hPokeData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                 (LONG)sizeof(DDEPOKE)+strlen(szValue)+2)))
                 return;

             if(!(lpPokeData = (DDEPOKE FAR *)GlobalLock(hPokeData)))
             {
                GlobalFree(hPokeData);
                return;
             }
             lpPokeData -> fRelease = TRUE;
             lpPokeData -> cfFormat = CF_TEXT;
             lstrcopy((LPSTR)lpPokeData->Value,(LPSTR)szValue);
             /* каждая строка данных завершается  */
.
       Windows 3.0/pg/3#3                                       = 175 =

             lstrcat((LPSTR)lpPokeData->Value,(LPSTR)"\r\n");
             GlobalUnlock(hPokeData);
             atomItem = GlobalAddAtom((LPSTR)szItem);
                .
                .
                .
             if(!PostMessage(hWndServerDDE,
                         WM_DDE_POKE,
                         hWndClientDDE,
                         MAKELONG(hPokeData,atomItem));
             {
                GlobalFree(hPokeData);
                GlobalDeleteAtom(atomItem);
             }

             Отметим, что  передача элемента на сервер с использованием
        сообщения WM_DDE_POKE  аналогична  передаче   элемента   данных
        клиенту с  помощью сообщения WM_DDE_DATA,  за исключением того,
        что в одном случае передача осуществляется от клиента  серверу,
        а в другом от сервера клиенту.

             Если сервер способен воспринимать формат значения элемента
        данных, который   передан   ему   клиентом,   то   сервер   его
        соответствующим образом  обрабатывает  и посылает положительное
        сообщение WM_DDE_ASK.  Если  сервер  не   способен   обработать
        значение элемента данных из-за формата или по какой-либо другой
        причине, то сервер посылает отрицательное сообщение WM_DDE_ASK.

             hPokeData = LOWORD(lPAram);
             atomItem = HIWORD(lParam);
        (1)  GlobalGetAtomName(atomItem,szItemName,ITEM_NAME_MAX_SIZE);
        (2)  if(!(lpPokeData = (DDEPOKE FAR *)GlobalLock(hPokeData)) ||
                 (lpPokeData -> cfFormat != CF_TEXT ||
                 !IfItemSypportedByServer(szItemName)))
             {

                /* отрицательное сообщение */
                PostMessage(hWndClientDDE,
                        WM_DDE_ASK,
                        hWndServerDDE,
                        MAKELONG(0,atomItem));
             }

             lstrcpy(szItemValue,lpPokeData->Value); /* копирование */
             bRelease = lpPokeData->fRelease;
             GlobalUnlock(hPokeData);
             if(bRelease)
             {
                GlobalFree(hPokeData);
                GlobalDeleteAtom(atomItem);
             }
                /* положительное сообщение */
                PostMessage(hWndClientDDE,
.
       Windows 3.0/pg/3#3                                       = 176 =

                        WM_DDE_ASK,
                        hWndServerDDE,
                        MAKELONG(0x8000,atomItem));
             }

             В данном примере:

        1)   Сервер вызывает функцию  GlobalGetAtomName  для  получения
             имени посланного клиентом элемента данных.

        2)   Затем сервер  определяет, поддерживает ли он эти данные,  и
             формат, в  котором  они посланы (CF_TEXT).  Если нет,  или
             если сервер не может  заблокировать  данные,  он  посылает
             отрицательное сообщение программе - клиенту.
                      22.4.3  Установление постоянной связи по данным.         

             Программа клиент  может  использовать DDE для установления
        связи с   элементом   данных   программы   -   сервера.   После
        установления такой   связи  сервер  периодически,  (обычно  при
        изменении элемента данных) посылает клиенту связанный  элемент.
        Таким образом,     между    двумя    прикладными    программами
        устанавливается поток данных и остается до  тех  пор,  пока  не
        будет явно прерван.

                        Инициализация связи по данным.

             Клиент инициализирует  связь по данным,  посылая сообщение
        WM_DDE_ADVISE:

             if(!hOptions = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                        sizeof(DDEADVISE))))
                  return;
             if(!lpOptions = (DDEADVISE FAR *)GlobalLock(hOptions)))
             {
                GlobalFree(hOptions);
                return;
             }
             lpOptions -> cfFormat = CF_TEXT;
             lpOptions -> fAskReq  = TRUE;
        (1)  lpOptions -> fDefurUpd = FALSE;
             GlobalUnlock(hOptions);
             atomItem = GlobalAddAtom(szItemName);
             if(!PostMessage(hWndServerDDE,
                         WM_DDE_ADVISE,
                         hWndClientDDE,
                         MAKELONG(hOptions,atomItem));
             {
                GlobalFree(hOptions);
                GlobalDeleteAtom(atomItem);
             }

             В данном примере:
.
       Windows 3.0/pg/3#3                                       = 177 =


        1)   Программа -  клиент устанавливает флаг fDeferUpd сообщения
             WM_DDE_ADVISE в  FALSE.  Этим вы инфрормируете программу -
             сервер,  что она должна при изменении данных  посылать  не
             уведомление. а сами данные.

             Если сервер   имеет   доступ   к   требуемому  элементу  и
        поддерживает необходимый формат,  сервер отмечает  новую  связь
        (запоминая флаги,  указанные  в  hOptions)  и  посылает клиенту
        положительное сообщение WM_DDE_ASK.  С этого момента и  до  тех
        пор,   пока   клиент   не   пошлет   соответствующее  сообщение
        WM_DDE_UNADVISE,  сервер при любом изменении  данного  элемента
        будет посылать клиенту измененные данные.

             Если сервер  не  может удовлетворить запрос WM_DDE_ADVISE,
        он посылает отрицательное сообщение WM_DDE_ASK.

             void DoPasteLink(hwndClientDDE;)
             HWND   hwndClientDDE;

             {

              HANDLE hData;
              LPSTR  lpData;
              HWND   hwndServerDDE;
              char   szApplication[APP_MAX_SIZE+1];
              char   szTopic[TOPIC_MAX_SIZE+1];
              char   szItem[ITEM_MAX_SIZE+1];
              int    nBufLen;

         (1)  if (OpenClipboard(hwndClientDDE))
              {
               if (!(hData = GetClipboardData(cfLink)) ||
                  !(lpData = GlobalLock(hData)))
               {
                  CloseClipboard();
                  return;
               }


               /* Проверить данные в системном буфере */
         (2)   if ((nBufLen = lstrlen(lpData)) >= APP_MAX_SIZE)
               {
                    CloseClipboard();
                    GlobalUnlock(hData);
                    return;
               }
               lstrcpy(szApplication, lpData);
               lpData += (nBufLen+1); /* перепрыгнуть через 0 */
               if ((nBufLen = lstrlen(lpData)) >= TOPIC_MAX_SIZE)
               {
                    CloseClipboard();
                    GlobalUnlock(hData);
.
       Windows 3.0/pg/3#3                                       = 178 =

                    return;
               }
               lstrcpy(szTopic, lpData);
               lpData += (nBufLen+1); /* перепрыгнуть через 0 */
               if ((nBufLen = lstrlen(lpData)) >= ITEM_MAX_SIZE)
               {
                    CloseClipboard();
                    GlobalUnlock(hData);
                    return;
               }
               lstrcpy(szItem, lpData);

               GlobalUnlock(hData);
               CloseClipboard();

         (3)   if (hwndClientDDE =
                   FindConvGivenAppTopic(szApplication, szTopic))
               { /* диалог приложение/предмет уже начат */
                 if (DoesAdviseAlreadyExist(hwndClientDDE, szItem))
                 MessageBox(hwndMain,"Advisory already established",
                      "Client", MB_ICONEXCLAMATION | MB_OK);
                 else
                    hwndServerDDE = GetHwndServerDDE(hwndClientDDE);
                    SendAdvise(hwndClientDDE, hwndServerDDE, szItem);
               }
         (4)   else
               {   /* необходимо инициализировать новый диалог */
                   SendInitiate(szApplication, szTopic);
                   if (hwndClientDDE =
                      FindConvGivenAppTopic(szApplication, szTopic))
                   {
                    hwndServerDDE = GetHwndServerDDE(hwndClientDDE);
                    SendAdvise(hwndClientDDE, hwndServerDDE, szItem);
                   }
               }
              }

              return;
             }

            В данном примере:

        1)   Программа - клиент открывает системный  буфер и проверяет,
             содержит ли  он  данные  в  том  формате  связи  (cfLink),
             который   был   предварительно   определен.   Если  он  не
             поддерживает этот формат или если не  может  заблокировать
             данные в системном буфере, то он возвращает управление.

        2)   После получения  указателя  на  данные в системном буфере
             она выделяет в данных имя приложения, предмета и элемента.

        3)   Прикладная программа - клиент определяет,  начат ли  между
             ней и программой - сервером диалог по данному  предмету  и
.
       Windows 3.0/pg/3#3                                       = 179 =

             приложению.  Если да,  то она проверяет,  существует ли по
             данному  элементу  связь.  Если  такая  связь  существует,
             программа выводит для пользователя панель с сообщением,  в
             противном случае  она  вызывает  функцию  SendAdvise   для
             посылки   серверу   сообщения  WM_DDE_ADVISE  для  данного
             элемента.

        4)   Если по  данному предмету диалог между клиентом и сервером
             еще не  начат,  то  клиент   вызывает   свою   собственную
             процедуру   SendInitiate  для  посылки  широковещательного
             сообщения WM_DD E_INITIATE,  чтобы  установить  диалог,  и
             устанавливает его с окном, ответившим на запрос, с помощью
             функции    FindServerGivenAppTopic.   После   установления
             диалога клиент с помощью  функции  SendAdvise  запрашивает
             связь по данным.

                   Уведомление клиента об изменении данных.

             Когда клиент   устанавливает   связь  с  флагом  fDeferUpd
        сообщения WM_DDE_ADVISE,  установленным  в  FALSE  (ноль),  это
        означает,  что  сервер  при  изменении данных будет посылать их
        программе - клиенту. В таких случаях сервер преобразует элемент
        данных в  определенный  в  начале  формат  и  посылает  клиенту
        сообщение WM_DDE_DATA:

             /* Выделить блок размером в заголовок данных DDE плюс */
             /* данные: строка, . */

             if(!(hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                 (LONG)sizeof(DDEDATA)+strlen(szItemValue)+2)))
                 return;

             if(!(lpData = (DDEDATA FAR *)GlobalLock(hData)))
             {
                GlobalFree(hData);
                return;
             }
             lpData -> bAskReq = bAskReq;
             lpData -> cfFormat = CF_TEXT;
             lstrcopy((LPSTR)lpData->Value,(LPSTR)szItemValue);
             lstrcat((LPSTR)lpData->Value,(LPSTR)"\r\n");
             GlobalUnlock(hData);
             atomItem = GlobalAddAtom(szItemName);
             if(!PostMessage(hWndClientDDE,
                         WM_DDE_DATA,
                         hWndServerDDE,
                         MAKELONG(hData,atomItem));
             {
                GlobalFree(hData);
                GlobalDeleteAtom(atomItem);
             }

             Клиент обрабатывает    соответствующим   образом   элемент
.
       Windows 3.0/pg/3#3                                       = 180 =

        данных. Если для  данного  элемента  установлен  флаг  fAskReq,
        клиент посылает серверу положительное сообщение WM_DDE_ASK.

             Когда клиент  устанавливает  связь  с установленным в TRUE
        (1) флагом fDeferUpd,  это означает,  что он запрашивает, чтобы
        при изменении  данных программа сервер посылала не сами данные,
        а только уведомление об их изменении. В таких случаях программа
        сервер  при  изменении данных не обрабатывает данные,  а только
        посылает клиенту сообщение WM_DDE_DATA с  нулевым  дескриптором
        данных:

             if(bDeferUpd)  /* проверка флага, установленного при приеме
                               сообщения WM_DDE_ADVISE */
             {
                 atomItem = GlobalAddAtom(szItemName);
                 if(!PostMessage(hWndClientDDE,
                             WM_DDE_DATA,
                             hWndServerDDE,
                             MAKELONG(0,atomItem));
                 {
                    GlobalDeleteAtom(atomItem);
                 }
             }

             По своему усмотрению программа -  клиент  может  при  этом
        получить данные с помощью обычного сообщения WM_DDE_REQUEST или
        вообще игнорировать уведомление об изменении элемента данных. В
        обеих случаях,  если установлен флаг fAslReq,  то клиент должен
        послать положительное сообщение WM_DDE_ASK.

                          Завершение связи по данным.

             Если клиент хочет завершить связь по определенным  данным,
        он посылает серверу сообщение WM_DDE_UNADVISE:

             atomItem = GlobalAddAtom(szItemName);
             if(!PostMessage(hWndServerDDE,
                         WM_DDE_UNADVISE,
                         hWndClientDDE,
                         MAKELONG(0,atomItem));
             {
                GlobalDeleteAtom(atomItem);
             }

             Сервер проверяет,  имеется ли для данного элемента  данных
        связь с   клиентом.   Есди   да,  то  сервер  посылает  клиенту
        положительное сообщение WM_DDE_ASK,  после этого он не отвечает
        за посылку измененного данного элемента клиенту. Если сервер не
        имел такой   связи,   он   посылает   отрицательное   сообщение
        WM_DDE_ASK.

             Для завершения   всех   связей  в  данном  диалоге  клиент
        посылает серверу сообщение WM_DDE_UNADVISE, указывая в качестве
.
       Windows 3.0/pg/3#3                                       = 181 =

        атома  данных ноль.  Сервер проверяет,  имеется ли хотя бы одна
        связь.  Если  да,  сервер  посылает   положительное   сообщение
        WM_DDE_ASK; он больше не отвечает за передачу клиенту изменений
        в данных.  Если в данном диалоге нет  ни  одной  связи,  сервер
        посылает клиенту отрицательное сообщение WM_DDE_ASK.
                 22.4.4  Запуск команд в удаленной прикладной программе.       

             Прикладная программа  Windows  может  с  помощью сообщения
        WM_DDE_EXECUTE инициировать     выполнение     команды      или
        последовательности команд другой прикладной программой.  Клиент
        посылает серверу    сообщение    WM_DDE_EXECUTE,     содержащее
        дескриптор командной строки:

             if(!hCommand = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
                                        sizeof(szCommandString))))
                  return;
             if(!lpCommand = GlobalLock(hCommand)))
             {
                GlobalFree(hCommand);
                return;
             }
             lstrcpy(lpCommand,szCommandString);
             GlobalUnlock(hCommand);
             if(!PostMessage(hWndServerDDE,
                         WM_DDE_EXECUTE,
                         hWndClientDDE,
                         MAKELONG(0,hCommand));
             {
                GlobalFree(hCommand);
             }

             Сервер пытается выполнить указанную командную строку.  При
        успешном завершении  сервер  посылает  положительное  сообщение
        WM_DDE_ASK или отрицательное сообщение в противном случае.  Это
        сообщение WM_DDE_ASK  заново  использует  дескриптор  hCommand,
        посланный в исходном сообщении WM_DDE_EXECUTE.

                         Набор команд Program Manager.

             Программа Windows  Program  Manager  использует  интерфейс
        командных строк  DDE,  который  позволяет   другим   прикладным
        программам создавать,  отображать и удалять группы, добавлять в
        группы элементы и закрывать Program Manager. Эти действия можно
        выполнить с помощью следующих команд:

             - CreateGroup

             - AddItem

             - DeleteGroup

             - ShowGroup
.
       Windows 3.0/pg/3#3                                       = 182 =


             - ExitProgman

             Например, программа   установки   вашей   программы  может
        использовать эти команды для занесения иконы вашей программы  в
        группу.

             Примечание: Пользователь   может  установить  конфигурацию
        Windows таким образом,  чтобы использовать в качестве  оболочки
        не Program Manager, а другую программу. Поэтому, ваша программа
        не должна подразумевать,  что Program Manager  всегда  доступна
        для установления диалога.

             Для использования  этих  команд  ваша прикладная программа
        должна в начале инициализировать диалог с Program Manager.  Для
        диалога нужно  использовать  "PROGMAN" в качестве имени как для
        приложения, так и для предмета.  Затем, ваша программа посылает
        сообщение  WM_DDE_EXECUTE,  указывая  необходимую  команду и ее
        параметры.  Например, следующий набор команд добавит WINAPP.EXE
        к группе Windows Applications:

             [CreateGroup(Windows Applications)]
             [ShowGroup(1)]
             [AddItem(winapp.exe, Win App,wunapp.exe,2);

             В следующих  параграфах  командные  строки Program Manager
        описаны более подробно.

                                 CreateGroup.

             Синтаксис команды CreateGroup:

             CreateGroup(GroupName[,GroupPath])

             Команда CreateGroup сообщает Program  Manager,  что  нужно
        создать новую   группу  или  активизировать  окно  существующей
        группы.

             Параметр GroupName - это строка,  содержащая  имя  группы.
        Если   такая   группа   уже   существует,  то  Program  Manager
        активизирует окно этой группы.

             Необязательный параметр GroupPath содержит  маршрут  файла
        группы. Если  вы  не  укажете  этот параметр,  то Windows будет
        использовать для файла имя по умолчанию.

                                   AddItem.

             Синтаксис команды AddItem:

             AddItem(CmdLine[,Name[,IconPath[,IconIndex[,xPos,yPos]]]])

             Команда AddItem добавляет к существующей группе икону.
.
       Windows 3.0/pg/3#3                                       = 183 =


             Требуемый параметр  содержит  строку  с  полной  командной
        строкой, необходимой  для запуска программы.  Как минимум,  это
        должно быть имя выполняемого файла  прикладной  программы.  Она
        может  также включать полный маршрут и параметры,  передаваемые
        программе.

             Необязательный параметр  Name  -  это  строка,  содержащая
        заголовок, который будет отображаться под иконой в окне группы.

             Необязательный параметр  IconPath  -  это  строка с именем
        файла,  содержащего икону,  отображаемую в данном окне  группы.
        Это может быть как выполняемый файл Windows,  так и файл иконы,
        созданный с помощью SDKPaint.  Если  вы  не  укажете  IconPath,
        Program    Manager    воспользуется    первой   иконой   файла,
        определяемого CmdLine.  Если этот файл не  содержит  иконы,  то
        Program Manager воспользуется стандартной иконой.

             Необязательный параметр    IconIndex    -    это    целое,
        определяющее индекс иконы в IconPath,  которую будет отображать
        Program  Manager.  PROGMAN.EXE  содержит  пять встроенных икон,
        которые вы можете использовать для прикладных программ  не  для
        Windows.

             Необязательные параметры   xPos   и   yPos  -  это  целые,
        определяющие горизонтальную и вертикальную позицию иконы в окне
        группы. Вы  можете  использовать  оба параметра для определения
        необходимого положения иконы.  Если вы их не  укажете,  Program
        Manager поместит икону в первое свободное место.

                                 DeleteGroup.

             Синтаксис команды DeleteGroup:

             DeleteGroup(GroupName);

             Команда DeleteGroup    удаляет    группу,     определяемую
        параметром GroupName.

                                  ShowGroup.

             Синтаксис команды ShowGroup:

             ShowGroup(GroupName,ShowCommand);

             Команда ShowGroup        позволяет         минимизировать,
        максимизировать  или восстанавливать окно группы,  определяемой
        параметром GroupName.

             Обязательный параметр   ShowCommand   представляет   собой
        целое, определяющее действие,  которое Program  Manager  должен
        выполнить с   окном   группы.  Этот  параметр  может  принимать
        следующие значения:
.
       Windows 3.0/pg/3#3                                       = 184 =


        Значение         Описание
        ---------------------------------------------------------------
        1                Активизирует и отображает окно группы. Если
                         окно было минимизировано или  максимизировано,
                         то Windows восстанавливает его исходный размер
                         и положение.

        2                Активизирует окно группы и отображает его в
                         виде иконы.

        3                Активизирует окно группы и максимизирует его.

        4                Отображает окно группы в том положении и того
                         же размера, каким оно было в последный раз.

        5                Активизирует окно группы и отображает его в
                         текущем положении и с текущими размерами.

        6                Минимизирует окно группы.

        7                Отображает окно группы в виде иконы. Текущее
                         активное окно остается активным.

        8                Отображает окно группы в его текущем состоянии.
                         Текущее активное окно остается активным.
        ---------------------------------------------------------------

                                 ExitProgman.

             Синтаксис команды ExitProgman:

             ExitProgman(bSaveState)

             Команда ExitProgman  заставляет  Program Manager завершить
        свою работу и,  возможно,  запомнить свое соостояние.  Параметр
        bSaveState является  булевским  переключателем,  который,  если
        равен TRUE,   заставляет   Program   Manager   сохранить   свое
        состояние.  Если bSaveState равен FALSE,  то Program Manager не
        будет сохранять своего состояния.
                                      22.4.5  Завершение диалога.              

             Как программа  -  клиент,  так  и программа сервер могут с
        помощью сообщения WM_DDE_TERMINATE  в  любой  момент  завершить
        диалог. Естественно,  обе  программы,  клиент и сервер,  должны
        быть  подготовлены   к   приему   сообщения   WM_DDE_TERMINATE.
        Прикладная программа обязана перед завершением работы завершить
        все свои диалоги.

             Прикладная программа завершает диалог,  посылая  сообщение
        WM_DDE_TERMINATE:

.
       Windows 3.0/pg/3#3                                       = 185 =

          PostMessage(hWndServerDDE,WM_DDE_TERMINATE,hWndClientDDE,0L);

             Этим ваша  программа  информирует,  что  больше  не  будет
        посылать сообщений и что другая программа  может  закрыть  свое
        окно.  Получив  это  сообщение программа обязана в любом случае
        послать в  ответ  сообщение  WM_DDE_TERMINATE.  Не  допускается
        посылать  отрицательное,  положительное  или  занятое сообщение
        WM_DDE_ASK.

             После того,  как  прикладная  программа  послала сообщение
        WM_DDE_TERMINATE своему партнеру  по  диалогу,  она  не  должна
        отвечать ни на какие его сообщения,  т.к.  окно, которому будет
        посылаться ответ, уже может быть разрушено.

             При завершении   прикладной  программы  она  должна  перед
        окончанием  обработки  сообщения   WM_DESTROY   завершить   все
        активные    диалоги.    Ваша    прикладная   программа   должна
        обчеспечивать задержку, т.к, один из ее партнеров, возможно, не
        сможет  ответить на сообщение WM_DDE_TERMINATE.  Ниже приведена
        процедура завершения диалога:

             void TerminateConversations(hwndServerDDE)
             HWND  hwndServerDDE;
             {
                HWND  hwndClientDDE;
                LONG  lTimeOut;
                MSG   msg;


                /* завершить все активные диалоги */
                hwndClientDDE = NULL;
                while (hwndClientDDE = GetNextConv(hwndClientDDE))
                {
                     hwndServerDDE = GetHwndServerDDE(hwndClientDDE);
                     if (IsWindow(hwndServerDDE))
                         SendTerminate(hwndClientDDE, hwndServerDDE);
                }

                /* Ожидание завершения всех диалогов или задержки */
                lTimeOut = GetTickCount() + (LONG)nAckTimeOut;
                while (PeekMessage(&msg, NULL, WM_DDE_FIRST,
                                          WM_DDE_LAST, PM_REMOVE))
                {
                      DispatchMessage (&msg);
                      if (msg.message == WM_DDE_TERMINATE)
                      {
                          if (!AtLeastOneConvActive())
                              break;
                      }
                      if (GetTickCount() > lTimeOut)
                          break;
                }

                return;
             }
.
       Windows 3.0/pg/3#3                                       = 186 =


                             22.5  Примеры программ Client и Server.           

             На диске  SDK  Sample  Source  Code  Disk в директории DDE
        содержатся два примера программ, с именами Server и Client. Эти
        программы иллюстрируют     большинство    обсуждавшихся    выше
        транзакций.

             Программа Server   содержит   окно   с    тремя    блоками
        редактирования, помеченными как "Item1", "Item2" и "Item3". Они
        представляют  собой  элементы  данных,  для  которых может быть
        установлена постоянная связь.

             Можно запустить  несколько  экземпляров  программы Server.
        Каждому экземпляру  присваивается  уникальное   имя   ("File1",
        "File2" и т.д.),  которые определяют предмет, по которому будет
        установлена связь.

             Прикладная программа Client  содержит  меню  с  командами,
        запускающими соответствующие транзакции:

             - Initiate

             - Terminate

             - Advise

             - Unadvise

             - Request

             - Poke

             - Execute

             Кроме этого  меню  Edit  программ Client и Server содержит
        команду PasteLink,  которая инициализирует  горячую  связь  для
        выбранного элемента данных.

             Окно программы  Client  показывает  все  текущие  диалоги,
        отображая дескрипторы  окон  клиента   и   сервера,   и   имена
        приложения и предмета.  Под каждым отображаемым диалогом в окно
        Client выводятся все связи по данным,  которые были установлены
        с помощью команд  Advise  и  PasteLink.  Отображение  связи  по
        данным включает показ текущего значения, посланного с сервера.

             Прикладная программа   Client   поддерживает   диалоги   с
        несколькими серверами,  несколько предметов для каждого сервера
        и несколько связей по данным для каждого предмета.  Аналогично,
        программа Server поддерживает связь с несколькими клиентами, по
.
       Windows 3.0/pg/3#3                                       = 187 =

        несколько  предметов  для каждого клиента и по несколько связей
        по данным для каждого предмета.

             Программы Client  и  Server  имеют  параллельно  модульную
        структуру. Каждая программа имеет три модуля.  Первый (Client.C
        и Server.C)  обрабатывает  все  пользовательские  транзакции  и
        поэтому  включает  процедуры  окон  и  панелей диалога.  Второй
        модуль (CLIDATA.C и SERVDATA.C) управляют  всеми  данными  всех
        активных диалогов и связей по данным. Третий модуль (CLIDDE.C и
        SERVDDE.C) включает всю логику, специфичную для транзакций DDE.
        При  такой модульной структуре эти два примеру могут быть более
        удобно преобразованы в необходимые вам приложения.
                                             22.6  Заключение.                 

             В данной  главе   описано,   как   использовать   протокол
        динамического  обмена  данными Windows для обмена данными между
        прикладными программами.  В ней приводятся  основные  концепции
        DDE и  описывается,  как  прикладные  программы  инициализируют
        диалог,  требуют данные или отвечают на запрос данных  и  затем
        завершают диалог.  В данной главе также описано, как установить
        "горячую"  или  "теплую"  связь  по  данным  между  прикладными
        программами и   как   одна  программа  может  запросить  другую
        выполнить команды.

             Дополнительную информацию относительно обмена  данными  вы
        найдете в:

        Раздел               Руководство
        ---------------------------------------------------------------
        Обмен данными с      Руководство программиста: глава 13, "Сис-
        помощью системного   темный буфер".
        буфера

        Выделение и исполь-  Руководство программиста: глава 15, "Управ-
        зование блоков памя- ление памятью", и глава 16, "Еще об управ-
        ти для обмена данны- лении памятью".
        ми в DDE

        Посылка сообщений    Справочное руководство, том 1: глава 1,
        другой программе     "Функции системы управления окнами" и
                             глава 4, "Список функций".

        Полное описание      Справочное руководство, том 2: глава 15,
        протокола DDE        "Определения протокола Windows DDE".
.

Delphi  |  JBuilder
::Главная ->Литература ->Руководство по программированию в Windows
C++ Builder
Реклама - двигатель сами понимаете чего...
Russian LinkExchange Banner Network
(c) 2000 by AlmigoR
Сайт управляется системой uCoz