Операционная система Microsoft Windows 3.1 для программиста -том 1

         

Алгоритм работы приложения


Если вы, забегая вперед, посмотрите листинги файлов нашего приложения, то обратите внимание на то, что оно имеет несколько необычную (с точки зрения программиста, составляющего программы для MS-DOS) структуру. В частности, функция WinMain после выполнения инициализирующих действий входит в цикл обработки сообщений, после выхода из которого работа приложения завершается. Функция WndProc вообще не вызывается ни из какой другой функции приложения, хотя именно она выполняет всю "полезную" работу.

Составляя программы для MS-DOS, вы привыкли к тому, что за весь сценарий работы программы отвечает функция main. Эта функция выполняет вызов всех остальных функций (за исключением функций обработки прерываний), из которых и состоит программа.

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

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

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

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

Функция окна обрабатывает три сообщения с кодами WM_LBUTTONDOWN, WM_RBUTTONDOWN и WM_DESTROY.

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

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

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

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

if(приложение уже было запущено) Завершение работы текущей копии приложения; Создание класса окна; Создание главного окна приложения на основе созданного ранее класса; Отображение окна на экране; while(в очереди нет сообщения WM_QUIT) Выборка сообщения и распределение его функции окна; Завершение работы приложения;

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

switch(Идентификатор сообщения) { case WM_LBUTTONDOWN: Вывод диалоговой панели с сообщением о том, что была нажата левая кнопка мыши; case WM_RBUTTONDOWN: Выдача звукового сигнала; Вывод диалоговой панели с сообщением о том, что была нажата правая кнопка мыши; case WM_DESTROY: Запись в очередь приложения сообщения WM_QUIT, при выборке которого завершается цикл обработки сообщений; } Вызов функции DefWindowProc, обрабатывающей все остальные сообщения, поступающие в функцию окна;

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

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


Библиотеки динамической загрузки DLL




Для того чтобы сформировать файл программы MS-DOS (типа exe или com), после компиляции исходных текстов отдельных модулей вы должны запустить редактор связей. Редактор связей соберет файл программы, включив в него все используемые в проекте модули. Фактически файл программы MS-DOS содержит весь код, который может потребоваться для ее выполнения. Единственное исключение - код обработчиков программных прерываний MS-DOS и BIOS, который не включается в файл программы, так как он всегда загружен в оперативную память.

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

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

Обратите внимание на приложения Calculator и Clock. Загрузочные файлы для этих приложений имеют размеры 43072 и 16416 байт, что совсем немного (и даже подозрительно мало!), особенно если учесть сложность выполняемых этими приложениями функций. Это возможно только благодаря тому, что большинство кода, выполняемого этими приложениями, находится вне загрузочных файлов. И в самом деле, все, что относится к формированию изображения калькулятора или часов, к перемещению и изменению размеров окон, выполняется модулями, расположенными в библиотеках динамической загрузки Windows.


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

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

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


Цикл обработки сообщений


После отображения окна функция WinMain запускает цикл обработки сообщений:

while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); }

Функция GetMessage предназначена для выборки сообщений из очереди приложения и имеет следующий прототип:

BOOL GetMessage(LPMSG lpmsg, HWND hwnd, WORD uMsgFilterMin, WORD uMsgFilterMax);

Первый параметр функции (lpmsg) является дальним указателем на структуру типа MSG, в которую будет записано выбранное из очереди сообщение. Тип LPMSG определен в файле windows.h следующим образом:

typedef MSG FAR* LPMSG;

Второй параметр (hwnd) является идентификатором окна, для которого необходимо выбрать сообщение из очереди приложения.

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

Третий (uMsgFilterMin) и четвертый (uMsgFilterMax) параметры функции GetMessage позволяют определить диапазон сообщений, выбираемых из очереди приложения, задавая соответственно минимальное и максимальное значение кодов выбираемых сообщений. Если для этих параметров указать нулевые значения (как это сделано в нашем приложении), из очереди приложения будут выбираться все сообщения.

Как работает функция GetMessage?

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

Выбранное функцией GetMessage сообщение удаляется из очереди сообщений приложения и записывается в структуру, адрес которой задан первым параметром функции.


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

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

DWORD DispatchMessage(LPMSG lpmsg);

Единственный параметр функции (lpmsg) является указателем на структуру, содержащую сообщение. Функция DispatchMessage возвращает значение, полученное при возврате из функции окна. Обычно это значение игнорируется приложением.

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

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


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


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

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



Дочерние окна


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

Базовый стиль дочерних окон определяется при помощи константы WS_CHILD:

#define WS_CHILD 0x40000000L

По аналогии с другими базовыми стилями в файле windows.h определена константа WS_CHILDWINDOW, которая полностью эквивалентна константе WS_CHLD:

#define WS_CHILDWINDOW (WS_CHILD)

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

Перечислим особенности дочерних окон.

Само собой разумеется, что дочерние окна должны иметь окно-родителя (окон-сирот не бывает!). Только дочерние окна могут иметь родителей, перекрывающие и временные окна могут иметь окно-хозяина, но не родителя. У дочерних окон могут быть "братья" (или "сестры", кому что больше нравится).

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

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

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

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

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



Драйверы устройств ввода/вывода


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

Необходимо отметить, что задача создания собственного драйвера для Windows значительно сложнее задачи разработки драйвера для MS-DOS. Драйверы Windows - тема для отдельной книги (возможно, не одной). Однако для стандартных устройств, таких, как принтер, мышь, порт последовательной передачи данных и т. п. драйверы уже имеются и поставляются в составе Windows либо в комплекте с устройствами ввода/вывода.

Если же вам необходимо создать драйвер собственного нестандартного устройства, придется приобрести такой программный продукт, как Driver Development Kit (DDK), поставляемый фирмой Microsoft. В состав DDK входит вся необходимая документация, средства разработки и готовые примеры драйверов.



Другие функции для вывода текста


Кроме рассмотренной нами функции TextOut программный интерфейс Windows содержит и другие, более сложные и многоцелевые функции, предназначенные для вывода текста.



Другие компоненты и подсистемы


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

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

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

Другая подсистема, устанавливаемая дополнительно, имеет отношение к управлению памятью и называется Win32s. Это подмножество 32-разрядного программного интерфейса операционной системы Windows NT, использующее сплошную несегментированную модель памяти. В этой модели памяти приложения обычно никогда не изменяют содержимого сегментных регистров процессора, так как они могут непосредственно адресовать огромные объемы виртуальной оперативной памяти.

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

Система Win32s поставляется фирмой Microsoft отдельно вместе с соответствующими средствами разработки, а также входит в комплект поставки трансляторов Borland C++ версии 4.0 и Symantec C++ версии 6.0.



Файл определения модуля


Для того чтобы создать программу MS-DOS, вам достаточно было иметь ее исходный текст, компилятор и редактор связей. Компилятор создавал один или несколько объектных модулей, транслируя файлы исходных текстов программы, редактор собирал из этих модулей с использованием библиотек загрузочный модуль. Наше самое первое приложение Windows (листинг 1.1) также состояло из одного файла, однако при сборке загрузочного модуля вы получали предупреждающее сообщение о том, что в проекте недостает файла определения модуля.

Файл определения модуля (мы будем называть его def-файлом) используется редактором связей при создании exe-файла: в нем указывается имя загрузочного модуля приложения, тип exe-файла, атрибуты сегментов кода и данных, необходимый объем оперативной памяти для стека и кучи, а также имена экспортируемых функций, определенных в приложении.

Наше приложение использует def-файл, представленный в листинге 1.3.

Листинг 1.3. Файл window\window.def

; ============================= ; Файл определения модуля ; ============================= ; Имя приложения NAME WINDOW ; Описание приложения DESCRIPTION 'Приложение WINDOW, (C) 1994, Frolov A.V.' ; Определение типа загрузочного модуля как ; приложения Windows EXETYPE windows ; Программа, которая будет записана в начало файла ; приложения. Эта программа получит управление ; при попытке запуска приложения в среде MS-DOS STUB 'winstub.exe' ; Размер стека в байтах STACKSIZE 5120 ; Размер локальной кучи памяти (local heap) ; приложения в байтах HEAPSIZE 1024 ; Атрибуты сегмента кода CODE preload moveable discardable ; Атрибуты сегмента данных DATA preload moveable multiple

Файл содержит отдельные операторы, такие, как NAME, DESCRIPTION или EXETYPE, причем практически все эти операторы имеют дополнительные параметры.

Оператор NAME определяет имя приложения, которое используется операционной системой Windows для идентификации приложения в своих сообщениях. К этому имени предъявляются такие же требования, как и к имени любого файла MS-DOS.
Дополнительно после имени приложения может быть указано ключевое слово WINDOWAPI, означающее, что данное приложение предназначено для работы в среде Windows. Это ключевое слово используется по умолчанию и может быть опущено (что мы и сделали).

Оператор DESCRIPTION (необязательный) помещает в exe-файл дополнительную информацию, такую, как название приложения и сведения о разработчике. Эта информация никогда не загружается в оперативную память.

Оператор EXETYPE отмечает exe-файл как предназначенный для работы в среде Windows. Если для создания приложения вы используете среду разработки, способную создавать приложения OS/2, вам следует указать в def-файле нужный тип загрузочного модуля. При использовании транслятора Borland C++ версии 3.1 этот оператор может быть опущен.

Оператор STUB определяет имя файла с программой, подготовленной для работы в среде MS-DOS, которая получит управление при попытке запустить приложение Windows из командной строки MS-DOS. В составе Borland C++ поставляется программа с именем winstub.exe, которая выводит сообщение о том, что данный модуль предназначен для работы в среде Windows:

This program requires Microsoft Windows.

Однако в принципе вы можете подготовить два варианта своей программы - для MS-DOS и для Windows. В зависимости от того, в какой среде будет запущено ваше приложение, будет выбран соответствующий вариант.

Другой способ использования программы, описанной при помощи оператора STUB, заключается в том, что эта программа запускает Windows, передавая ей при запуске в качестве параметра имя файла, в котором она находится. При попытке запуска приложения из среды MS-DOS будет запущена операционная система Windows и затем нужное приложение. Но будьте осторожны - вы можете случайно запустить приложение из виртуальной машины MS-DOS, работающей под управлением Windows. Поэтому перед запуском Windows следует проверить среду, в которой был выполнен запуск приложения.

Оператор STACKSIZE определяет размер стека для приложения. Стандартное значение параметра для этого оператора составляет 5120 байт.


Однако при использовании некоторых функций Windows версии 3.1 может потребоваться увеличение размера стека до 9 - 10 Кбайт. Такой большой размер стека (по сравнению с размером стека программ MS-DOS) связан с широко распространенной в Windows практикой неявного рекурсивного вызова функций.

Оператор HEAPSIZE определяет размер кучи приложения. Однако размер, указанный в качестве параметра, не является критичным. Можно указать любое число, большее нуля. При попытке заказать дополнительную память из кучи приложения операционная система Windows динамически увеличивает размер кучи до необходимой величины (если это возможно). Для кучи, стека и хранения ближних (описанных с ключевым словом near), глобальных, а также статических переменных может быть использовано максимально 64 Кбайт оперативной памяти.

Оператор CODE определяет атрибуты стандартного сегмента кода с именем _TEXT. Атрибут preloaded используется для обеспечения автоматической загрузки сегмента в оперативную память сразу после запуска приложения. Атрибут movable позволяет Windows перемещать сегмент в памяти, например для освобождения большого сплошного участка памяти. И наконец, атрибут discardable позволяет Windows при необходимости уничтожить сегмент и отдать занимаемую им память другим приложениям. При необходимости данный сегмент будет автоматически прочитан из exe-файла и восстановлен.

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

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


Файл windows.h


Исходные тексты любого приложения Windows включают файл windows.h:

#include <windows.h>

Этот файл содержит множество определений типов данных, макросов, прототипов функций, констант и т. д.

Наряду с этим файлом вы можете включить в приложения и другие, знакомые вам по MS-DOS, include-файлы, такие, как stdlib.h и string.h.

Если в проекте Borland C++ for Windows версии 3.1 указывается, что создается приложение Windows, компилятор определяет символ _WINDOWS. Этот символ влияет на включаемые файлы стандартной библиотеки Си. В частности, из include-файлов могут быть исключены прототипы функций, не совместимых с Windows.

Перед включением файла windows.h для выполнения более строгой проверки типов рекомендуется определить символ STRICT:

#define STRICT

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

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

Символ Что исключает
NOATOM Прототипы функций для работы с атомами
NOCLIPBOARD Прототипы функций, идентификаторы сообщений и константы для работы с универсальным буфером обмена Clipboard
NOCOMM Прототипы функций, структуры и константы для работы с портом последовательной передачи данных
NOCTLMGR Прототипы функций для работы с диалоговыми панелями, структуры и константы для работы с диалоговыми панелями, сообщения WM_CTLCOLOR, WM_GETFONT, WM_SETFONT, стили органов управления
NOCOLOR Прототипы функций GetSysColor, SetSysColor и константы с префиксом COLOR
NODRAWTEXT Прототип функции DrawText и связанные с этой функцией константы
NOGDI Все определения функций и констант для графического интерфейса GDI
NOHELP Функцию WinHelp, структуры и константы для нее
NOICONS Идентификаторы встроенных пиктограмм
NOKERNEL Прототип функции WinMain, определения всех функций ядра Windows, структур данных и константы
NOMB Прототип функций MessageBox, MessageBeep и константы для них
NOMENUS Прототипы функций и константы для работы с меню
NOMETAFILE Прототипы функций и константы для работы с метафайлами
NOSYSCOMMANDS Константы системных команд с префиксом SC
NOUSER Прототипы и определения констант для пользовательских функций (эти функции относятся к программному интерфейсу Windows и вызываются приложениями)
NOVIRTUALKEYCODES Коды виртуальных клавиш
NOWINMESSAGES Определения кодов сообщений Windows
NOWINSTYLES Константы для определения стилей окон



Файловая система


Версия 3.1 операционной системы Microsoft Windows использует файловую систему MS-DOS. И это удобно с точки зрения совместимости с MS-DOS. Вы, наверное, знаете, что Windows запускается из MS-DOS как обычная программа с именем win.com. После запуска Windows не выгружает из памяти MS-DOS, но использует некоторые ее компоненты (например, файловую систему) в несколько измененном виде.

В следующих версиях Windows наряду с файловой системой MS-DOS планируется использовать высокопроизводительную файловую систему NTFS, которая произошла от файловой системы HPFS (High Performance File System), применяемой в операционной системе OS/2. Windows NT уже использует файловую систему NTFS. Новая файловая система имеет меньше ограничений, в частности в ней нет таблицы размещения файлов FAT и сняты практически все ограничения на длину имени файла (максимальная длина имени в OS/2 составляет 255 символов).



Функция DrawText


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

int WINAPI DrawText(HDC hdc, LPCSTR lpsz, int cb, RECT FAR* lprc, UINT fuFormat);

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

Адрес выводимой текстовой строки задается при помощи параметра lpsz. Этот параметр является дальним указателем на строку символов.

Длина строки символов задается в байтах параметром cb.

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

Параметр задается как набор флагов с использованием операции логического ИЛИ:

Значение Описание
DT_BOTTOM Выравнивание текста по верхней границе прямоугольника, заданного параметром lprc. Этот флаг должен использоваться в комбинации с флагом DT_SINGLELINE
DT_CALCRECT Определение высоты и ширины прямоугольника без вывода текста. Если указан этот флаг, функция DrawText возвращает высоту текста. Если выводимый текст состоит из нескольких строк, функция использует ширину прямоугольника, заданную параметром lprc, и расширяет базу этого прямоугольника до тех пор, пока прямоугольник не вместит в себя последнюю строку текста. Если текст состоит из одной строки, функция изменяет правую сторону прямоугольника до тех пор, пока последний символ строки не поместится в прямоугольник.

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

DT_CENTER Центрирование текста по горизонтали
DT_EXPANDTABS Расширение символов табуляции. По умолчанию каждый символ табуляции расширяется в восемь символов
DT_EXTERNALLEADING Вывод текста выполняется с учетом межстрочного расстояния (external leading), определенного для выбранного шрифта разработчиком шрифтов
DT_LEFT Выравнивание текста по левой границе прямоугольной области, заданной параметром lprc
DT_NOCLIP Вывод текста выполняется без ограничения области вывода. Этот режим увеличивает скорость вывода текста
DT_NOPREFIX Выключение директивы подчеркивания &. По умолчанию символ & используется для того, чтобы вывести следующий символ с выделением подчеркиванием. Для вывода самого символа & его следует повторить дважды. Флаг DT_NOPREFIX выключает этот режим
DT_RIGHT Выравнивание текста по правой границе прямоугольной области, заданной параметром lprc
DT_SINGLELINE Текст состоит только из одной строки. Символы возврата каретки и перевода строки не вызывают перехода на следующую строку
DT_TABSTOP Установить точки останова по символам табуляции
DT_TOP Выравнивание текста по верхней границе прямоугольной области, заданной параметром lprc. Флаг используется только для текста, состоящего из одной строки
DT_VCENTER Выравнивание текста по вертикали. Флаг используется только для текста, состоящего из одной строки. Если текст состоит из одной строки, необходимо вместе с этим флагом указывать флаг DT_SINGLELINE
DT_WORDBREAK Выполнять свертку слов в пределах заданной параметром lprc прямоугольной области. Если слово не помещается в строке, оно может быть перенесено на следующую строку

Если перед вызовом функции вы установили режим обновления текущей позиции (вызвав функцию SetTextAligh с параметром TA_UPDATECP), текст будет выведен начиная с текущей позиции, которая устанавливается за последним выведенным ранее символом. Свертка слов при этом не выполняется.

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



Функция ExtTextOut


Функция ExtTextOut обеспечивает более широкие возможности по сравнению с функцией TextOut и, соответственно, имеет большее количество параметров:

BOOL WINAPI ExtTextOut(HDC hdc, int nXStart, int nYStart, UINT fuOptions, const RECT FAR* lprc, LPCSTR lpszString, UINT cbString, int FAR* lpDx);

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

Параметр hdc задает идентификатор контекста отображения, который используется для вывода текста.

Параметры nXStart и nYStart определяют соответственно X- и Y-координаты начальной позиции вывода текста. Если перед вызовом этой функции вы установили режим обновления текущей позиции (вызвав функцию SetTextAligh с параметром TA_UPDATECP), параметры nXStart и nYStart игнорируются. Текст будет выведен начиная с текущей позиции, которая устанавливается за последним выведенным ранее символом.

Параметр fuOptions позволяет определить тип ограничивающей прямоугольной области, заданной параметром lprc. Этот параметр задается в виде двух флагов, которые можно объединять логической операцией ИЛИ.

Первый флаг имеет имя ETO_CLIPPED. Если указан этот флаг, прямоугольная область, заданная параметром lprc, определяет область ограничения для вывода текста.

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

Параметр lprc является дальним указателем на структуру типа RECT. Он определяет прямоугольную область, используемую для ограничения или закрашивания. В качестве этого параметра вы можете указать значение NULL, при этом область ограничения не используется.

Для определения адреса выводимой текстовой строки следует указать параметр lpszString. Этот параметр является дальним указателем на строку символов.

Длина строки символов задается в байтах параметром cbString.

Параметр lpDx позволяет задать расстояние между отдельными символами. Если этот параметр указан как NULL, при выводе текста расстояние между символами определяется шрифтом, выбранным в контекст отображения. Если же в качестве этого параметра указать дальний адрес массива значений типа int, вы сможете определять индивидуальное расстояние между отдельными символами. Элемент массива с индексом n определяет расстояние между n-м и n+1-м символами строки. Размер массива должен быть равен значению, указанному в параметре cbString.

Функция ExtTextOut при нормальном завершении возвращает ненулевое значение (TRUE). В противном случае возвращается значение FALSE.



Функция окна


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

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

Можно считать, что единственная для каждого окна функция окна реализует все методы окна как объекта. В языке программирования C++, напротив, каждый метод объекта (класса) реализуется отдельной функцией, называемой обычно функцией-членом. Для реализации всех методов функция окна анализирует код сообщения, однозначно идентифицирующий событие и, следовательно, определяющий нужный метод.

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

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

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

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

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


В нашем приложении был создан класс окна, в котором определена функция окна с именем WndProc:

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_LBUTTONDOWN: { MessageBox(NULL, "Нажата левая клавиша мыши", "Сообщение", MB_OK | MB_ICONINFORMATION); return 0; } case WM_RBUTTONDOWN: { MessageBeep(-1); // звуковой сигнал MessageBox(NULL, "Нажата правая клавиша мыши", "Сообщение", MB_OK | MB_ICONINFORMATION); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

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

Функция окна должна иметь следующий прототип (имя функции окна может быть любым, а не только WndProc):

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

Тип LRESULT определен в файле windows.h следующим образом:

typedef signed long LONG; typedef LONG LRESULT;

Таким образом, функция окна возвращает значение с типом LONG, что в текущей реализации Windows (версия 3.1) соответствует двойному слову.

Модификатор CALLBACK указывает на соглашения о передаче параметров _pascal и определяет функцию окна как функцию _far:

#define CALLBACK _far _pascal

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

Вы могли бы описать функцию окна как long _far _pascal, однако для обеспечения возможности переноса вашей программы в 32-разрядные версии Windows (Windows NT) или для использования 32-разрядного расширения Win32s текущей версии Windows следует пользоваться символами LRESULT и CALLBACK.

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



Функция окна приложения TEXTOUT


Отложим описание деталей вывода текста, связанное с изменением шрифтов и других параметров текста, и вернемся к функции окна приложения TEXTOUT (листинг 2.7). Эта функция выводит текст при получении сообщений WM_PAINT, WM_LBUTTONDOWN и WM_RBUTTONDOWN.

Когда функция окна получает сообщение WM_PAINT, фон окна закрашивается кистью, определенной при регистрации класса окна, и начиная с точки с логическими координатами (10, 20) выводится строка "Сообщение WM_PAINT".

Если вы установите курсор мыши в главное окно приложения и будете нажимать левую или правую клавишу мыши, в функцию окна будет посылаться соответственно сообщение WM_LBUTTONDOWN или WM_RBUTTONDOWN. По первому сообщению в точке (10, 40) будет выведена строка "Сообщение WM_LBUTTONDOWN", по второму - в точке (10, 60) будет выведена строка "Сообщение WM_RBUTTONDOWN" (рис. 2.2).

Рис. 2.2. Главное окно приложения TEXTOUT

Если вы при помощи рамки измените размер главного окна, уменьшив, а затем немного увеличите его, первая строка, выведенная по сообщению WM_QUIT, будет восстановлена полностью. Остальные строки могут исчезнуть целиком или частично, в зависимости от того, остались ли они в окне при уменьшении размера окна (рис. 2.3).

Рис. 2.3. Исчезновение строк при изменении размера окна

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



Функция TabbedTextOut


Функция TabbedTextOut предназначена для вывода текстовых строк, содержащих символы табуляции. Эту функцию удобно использовать для вывода текстовых таблиц.

Приведем прототип функции:

LONG WINAPI TabbedTextOut(HDC hdc, int xPosStart, intyPosStart, LPCSTR lpszString, int cbString, int cTabStops, int FAR* lpnTabPositions, int nTabOrigin);

Параметр hdc задает идентификатор контекста отображения, который используется для вывода текста.

Параметры xPosStart и yPosStart определяют соответственно X- и Y-координаты начальной позиции вывода текста. Если перед вызовом этой функции вы установили режим обновления текущей позиции (вызвав функцию SetTextAligh с параметром TA_UPDATECP), параметры xPosStart и yPosStart игнорируются. Текст будет выведен начиная с текущей позиции, которая устанавливается за последним выведенным ранее символом.

Для определения адреса выводимой текстовой строки следует указать параметр lpszString. Этот параметр является дальним указателем на строку символов.

Длина строки символов задается в байтах параметром cbString.

Параметр cTabStops определяет количество значений в массиве позиций символов табуляции. Если значение этого параметра равно 1, расстояние между символами табуляции определяется первым элементом массива, адрес которого передается через параметр lpnTabPositions.

Указатель lpnTabPositions определяет адрес массива целых чисел, определяющих расположение символов табуляции. Массив должен быть отсортирован в порядке увеличения значений.

Параметр nTabOrigin определяет логическую X-координату начальной позиции, относительно которой происходит расширение символов табуляции.

Функция TabbedTextOut возвращает размер (в логических единицах) области, занятой выведенной строкой. Старшее слово возвращаемого значения содержит высоту строки, младшее - ширину строки.



Функция WinMain


Любая программа MS-DOS, составленная на языке программирования C или C++, должна содержать функцию с именем main. Эта функция первой получает управление сразу после того, как специальный стартовый модуль устанавливает расположение стека и кучи программы, а также выполняет все необходимые инициализирующие действия.

Если вы создаете приложение Windows с использованием языка программирования C или C++, прежде всего вы должны создать функцию с именем WinMain, которая является аналогом функции main в программах для MS-DOS.

Функция WinMain должна быть определена следующим образом:

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { // Тело функции }

В определении функции WinMain использованы, вероятно, незнакомые вам типы - PASCAL, HINSTANCE, LPSTR. Эти типы описаны в include-файле с именем windows.h, который поставляется вместе с компилятором и должен быть включен в исходный текст программы при помощи оператора #include.

Тип PASCAL определен в файле windows.h следующим образом:

#define PASCAL _pascal

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

Это связано с тем, что такая функция должна сама освобождать стек перед возвратом. Если перед вызовом функции, описанной с ключевым словом _pascal или PASCAL, записать в стек неправильное количество параметров, перед возвратом из функции обязательно произойдет неправильное освобождение стека.

В частности, функция WinMain должна использовать ровно четыре параметра, как показано в предыдущем примере. Привычная вам функция main программы MS-DOS могла либо совсем не иметь параметров, либо использовать параметры argc и argv.

Функция WinMain возвращает значение типа int, что позволяет передать операционной системе Windows или отладчику код завершения приложения.


Первые два параметра имеют тип HINSTANCE, который в Windows версии 3.1 является 16-разрядным идентификатором. Не следует однако думать, что тип HINSTANCE эквивалентен типу int. Изучив include-файл windows.h, вы сможете убедиться в том, что это не так.

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

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

Так как Windows - мультизадачная среда, вы можете запустить одновременно несколько приложений, и каждое приложение будет иметь свой, уникальный идентификатор.

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

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

Анализируя параметр hPrevInstance, приложение может выполнять различные действия в зависимости от того, была ли уже загружена на момент запуска другая копия приложения.

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

if(hPrevInstance) return 0;

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

Третий параметр функции WinMain имеет имя lpszCmdLine. Он имеет тип LPSTR, который определяется в include-файле windows.h следующим образом:



# define FAR _far typedef char FAR* LPSTR;

Из этого определения видно, что параметр lpszCmdLine является дальним указателем на символьную строку. Что это за строка?

Программе MS-DOS при запуске из командной строки вы можете передать параметры. Эти параметры программа получает через параметры argc и argv функции main.

Аналогично, при запуске приложения Windows вы также можете указать параметры. Эти параметры должны быть записаны в текстовом виде после имени exe-файла приложения. Параметры можно задать для любой пиктограммы любой группы Program Manager. Для этого выберите из меню "File" строку "Properties" и в поле "Command Line" после пути к exe-файлу приложения допишите необходимые параметры.

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

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

Последний параметр функции WinMain имеет имя nCmdShow и тип int.

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

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


При запуске приложения с помощью строки "Run..." меню "File" приложения Program Manager кроме командной строки вы можете указать режим запуска "Run Minimized". В этом режиме правильно спроектированное приложение (способное анализировать параметр nCmdShow) при запуске сразу сворачивает свое главное окно в пиктограмму.

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

У вас может возникнуть вопрос - зачем переопределять тип _pascal как PASCAL, а тип _far как FAR?

Дело в том, что операционная система Windows задумана (во всяком случае Windows NT) как переносимая на различные платформы. Windows NT успешно работает не только на процессорах фирмы Intel, но и, например, на процессоре Alpha, созданном фирмой DEC и имеющем свою собственную архитектуру. Если вы планируете в будущем перетранслировать исходные тексты своих приложений для Windows NT, вам нельзя закладывать в них особенности архитектуры процессоров Intel. Например, тип FAR для Windows версии 3.1 определен как _far, а для Windows NT этот же тип может быть определен по-другому. Например, так:

#define FAR

Как дополнительная помощь в создании переносимых (мобильных) приложений, в операционной системе Windows программный интерфейс (API) отделен от самой операционной системы. Поэтому программный интерфейс Windows в принципе может быть реализован в среде другой операционной системы, такой, как UNIX или OS/2.


Имена констант в файле windows.h


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

Символические имена большинства констант, определенные в файле windows.h и других файлах, включаемых в исходные тексты приложений Windows, начинаются с префиксов, таких, как WM или WS. Имена некоторых констант не имеют таких префиксов, например OPAQUE, TRANSPARENT и т. п.

Приведем список некоторых префиксов имен констант, определенных в файле windows.h.

Префикс символического имени константы Описание
BI Компрессия изображений bitmap
BN Коды сообщений от кнопок
BS Стили логических кистей
CB Стили органа управления Combo Box
CBN Коды сообщений от органа управления Combo Box
CBS Стили при создании органа управления Combo Box
CE Коды ошибок при передаче данных
CF Форматы универсального буфера обмена данными Clipboard
COLOR Системные цвета отдельных элементов пользовательского интерфейса Windows
CS Стили класса окна
DRIVE Тип диска
DS Стили при создании диалоговых панелей
DT Технология, использованная при создании устройства ввода/вывода
DT Режимы форматирования при выводе текста функцией DrawText
HT Коды, возвращаемые функцией DefWindowProc при обработке сообщения WM_NCHITTEST
ID Идентификаторы команд диалоговой панели
IDC Идентификаторы встроенных курсоров
IDI Идентификаторы встроенных пиктограмм
MB Флаги для функции MessageBox
META Коды для метафайлов
MM Режимы отображения
СС Способность устройства рисовать различные кривые
OF Константы для работы с файлами
PS Стили пера
RT Типы ресурсов
SB Команды и константы для полосы просмотра (Scroll Bar)
SBS Стили полосы просмотра
SC Значения системных команд, передаваемых вместе с сообщением WM_SYSCOMMAND
SIZE Константы для описания способа изменения размера окна
SM Константы для определения системных метрик
SW Константы для функции ShowWindow
TA Константы для выбора способа выравнивания текста при его выводе с помощью функций TextOut и ExtTextOut.
VK Коды виртуальных клавиш
WM Сообщения Windows
WS Стили окна
WS_EX Расширенные стили окна



Имена параметров функций


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

Префикс Тип данных
atm ATOM
f BOOL
b BYTE
lpb BYTE FAR*
lpch char FAR*
dlgprc DLGPROC
dw DWORD
lpdw DWORD FAR*
haccl HACCEL
hbm HBITMAP
hbr HBRUSH
hcur HCURSOR
hdc HDC
hdrvr HDRVR
hdwp HDWP
hf HFILE
hfont HFONT
hgdiobj HGDIOBJ
hglb HGLOBAL
hhook HHOOK
hicon HICON
hinst HINSTANCE
hloc HLOCAL
hmenu HMENU
hmf HMETAFILE
hmod HMODULE
hkprc HOOKPROC
hpal HPALETTE
hpen HPEN
hrgn HRGN
hrsrc HRSRC
hstr HSTR
htask HTASK
hwnd HWND
n int
l LONG
lParam LPARAM
lpb LPBYTE
lpsz LPCSTR
lpn LPINT
lpl LPLONG
lpsz LPSTR
lpv LPVOID
lpw LPWORD
lResult LRESULT
npsz NPSTR
npb PBYTE
lppt POINT FAR*
lprc RECT FAR*
tmprc TIMERPROC
u UINT
wndenmprc WNDENUMPROC
wndprc WNDPROC
u или w WORD
wParam WPARAM



Инициализация приложения


Первое, что делает функция WinMain после запуска приложения, это проверяет наличие уже запущенной ранее копии этого же приложения:

if(!hPrevInstance) { if(!InitApp(hInstance)) return FALSE; } else { MessageBox(NULL, "Можно запускать только одну копию приложения", "Ошибка", MB_OK | MB_ICONSTOP); return FALSE; }

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

Если параметр hPrevInstance равен нулю, функция WinMain вызывает функцию инициализации приложения InitApp. В противном случае на экран выводится сообщение о невозможности запуска второй копии приложения.

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

Обратите внимание на вызов функции MessageBox. В качестве последнего параметра указано значение MB_OK | MB_ICONSTOP. Последний параметр функции представляет собой набор битовых флагов, которые мы рассмотрим позже. Флаг MB_OK предназначен для создания на диалоговой панели кнопки с надписью "OK", флаг MB_ICONSTOP нужен для изображения пиктограммы с надписью "STOP" (рис. 1. 10).

Рис. 1.10. Сообщение об ошибке



Интерфейс EasyWin


Посмотрите на исходный текст приложения Windows, приведенный в листинге 8.1. Не торопитесь делать вывод, что эта программа попала сюда случайно из учебника по языку программирования Си Кернигана и Риччи. То, что вы видите, и в самом деле приложение Windows. Вы можете легко в этом убедиться, если запустите exe-файл этого приложения, который есть на дискете (дискета продается вместе с книгой). Главное окно приложения изображено на рис. 8.2.

Листинг 8.1. Файл easywin\easywin1.cpp

#include <stdio.h> int main(void) { printf("Hello, world!"); return 0; }

Рис. 8.2. Окно приложения EASYWIN1

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

Если вы не купили дискету, прилагаемую к книге, и набрали приведенный выше текст программы самостоятельно, выберите из меню "Options" среды Borland C++ for Windows строку "Application...". На экране появится диалоговая панель "Application Options" (рис. 8.3).

Рис. 8.3. Диалоговая панель "Application Options"

В этой диалоговой панели вам надо нажать кнопку с надписью "Windows App".

Когда вы собираете загрузочный модуль приложения, компилятор находит там вместо функции WinMain функцию main. Так как в проекте указано, что данное приложение является приложением Windows, компилятор автоматически подключает к приложению функции интерфейса EasyWin и оно становится приложением Windows.

А как же, спросите вы, быть без функции WinMain? Очень просто - эта функция есть, она определена в подключаемых к вашему приложению функциях EasyWin.

Фактически интерфейс EasyWin полностью скрывает от программиста интерфейс Windows, предоставляя ему взамен старый добрый интерфейс консольного ввода/вывода. В программах EasyWin вы можете пользоваться всеми стандартными функциями ввода/вывода, такими, как printf, gets, getch и т. д. Дополнительно можно вызывать функции gotoxy, wherex, wherey, clrscr и clreol, которые входят в стандартную библиотеку Borland C++ для MS-DOS.


Аналогичный интерфейс предусмотрен и в системе разработки Microsoft Visual C++. Этот интерфейс называется QuickWin. Он позволяет использовать практически любые функции стандартной библиотеки транслятора для MS-DOS, в том числе графические. Создаваемое с помощью этого интерфейса приложение может работать в среде Windows в полноэкранном режиме, внешне не отличаясь от своего варианта для MS-DOS.

Зачем все это нужно? Мы так долго говорили о преимуществах Windows, а теперь снова возвратились к MS-DOS?

Возвратились, но не совсем.

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

Для чего же обычно используют интерфейс EasyWin?

Во-первых, для переноса старых программ в среду Windows. Если у вас есть исходные тексты программ MS-DOS, составленных на языке программирования Си, которыми вы продолжаете пользоваться и которые для ввода/вывода на экран используют только функции консольного ввода/вывода, вы сможете без особого труда сделать из этих программ приложения Windows. Для этого вам достаточно перетранслировать их в среде Borland C++ for Windows версии 3.1, указав в проекте, что нужно сделать приложение Windows. Скорее всего вам не придется менять ни одной строчки исходного кода.

Во-вторых, интерфейс EasyWin удобно использовать для создания небольших тестовых приложений, когда нужно быстро получить результат.

В качестве примера такого приложения приведем второй вариант приложения TMETRICS, предназначенного для просмотра пространственных характеристик шрифта, выбранного в контекст отображения (листинг 8.2).

Листинг 8.2. Файл easywin\easywin2.cpp

#include <windows.h> #include <stdio.h> #include <conio.h>

// Прототип функции void Print(int, char *);



// ============================================== // Функция main // ==============================================

int main(void) { HDC hdc; TEXTMETRIC tm; // структура для записи метрик шрифта

printf("Нажмите любую клавишу...");

// Ожидаем ввода с клавиатуры любого символа getch();

printf("\nПараметры шрифта:\n" "-----------------\n");

// Создаем контекст отображения, // необходимый для определения метрик шрифта hdc = CreateDC("DISPLAY", NULL, NULL, NULL);

// Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm);

// Выводим параметры шрифта Print(tm.tmHeight, "tmHeight"); Print(tm.tmAscent, "tmAscent"); Print(tm.tmDescent, "tmDescent"); Print(tm.tmInternalLeading, "tmInternalLeading"); Print(tm.tmExternalLeading, "tmExternalLeading"); Print(tm.tmAveCharWidth, "tmAveCharWidth"); Print(tm.tmMaxCharWidth, "tmMaxCharWidth"); Print(tm.tmWeight, "tmWeight"); Print(tm.tmItalic, "tmItalic"); Print(tm.tmUnderlined, "tmUnderlined"); Print(tm.tmStruckOut, "tmStruckOut"); Print(tm.tmFirstChar, "tmFirstChar"); Print(tm.tmLastChar, "tmLastChar"); Print(tm.tmDefaultChar, "tmDefaultChar"); Print(tm.tmBreakChar, "tmBreakChar"); Print(tm.tmPitchAndFamily, "tmPitchAndFamily"); Print(tm.tmCharSet, "tmCharSet"); Print(tm.tmOverhang, "tmOverhang"); Print(tm.tmDigitizedAspectX,"tmDigitizedAspectX"); Print(tm.tmDigitizedAspectY,"tmDigitizedAspectY");

// Уничтожаем созданный нами контекст DeleteDC(hdc);

// Выводим сообщение о завершении работы программы MessageBox(NULL,"Работа программы закончена, " "для просмотра результатов нажмите кнопку OK", "Демонстрация EASYWIN", MB_OK | MB_ICONINFORMATION);

return 0; }

// ========================================== // Функция для вывода параметров шрифта // в окно // ========================================== void Print(int tmValue, char *str) { printf("%-20s\t= %d\n", str, tmValue); }



В этом приложении мы создаем контекст отображения, вызывая функцию программного интерфейса Windows CreateDC. Затем мы вызываем знакомую вам функцию GetTextMetrics, определяющую метрики шрифта. Названия метрик и соответствующие численные значения выводятся в окно приложения функцией Print. Функция Print определена в нашем приложении и выглядит чрезвычайно просто благодаря применению для вывода функции printf.

Главное окно приложения изображено на рис. 8.4.



Рис. 8.4. Главное окно приложения EASYWIN2

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

Обратите внимание также на то, что мы вызываем в этой программе функцию программного интерфейса MessageBox.

А можно ли поступить наоборот - из обычного приложения Windows инициализировать интерфейс EasyWin и воспользоваться функциями консольного ввода/вывода?

Можно, именно так мы и поступили в нашем следующем приложении, которое выполняет такие нетипичные для приложений задачи, как перезагрузка компьютера или перезапуск операционной системы Windows (листинг 8.3).

Листинг 8.3. Файл easywin\easywin3.cpp

#define STRICT #include <windows.h> #include <stdio.h> #include <conio.h>

// =========================================== // Функция WinMain // =========================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { BYTE szBuf[128]; int cbBufSize; BYTE ch;

// Инициализация интерфейса EasyWin _InitEasyWin();

// Получаем путь к каталогу, в котором // установлена Windows GetWindowsDirectory(szBuf, cbBufSize);

printf("Каталог Windows: %s\n\n", szBuf);

printf("Нажмите:\n\n" "R - перезапуск системы\n" "W - перезапуск Windows\n" "N - запуск Norton Commander и возврат в Windows\n\n" "или любую другую клавишу ->\n" );

ch = getch();



if(ch == 'R' ch == 'r') { // Перезагрузка компьютера if(!ExitWindows(EW_REBOOTSYSTEM, 0)) { printf("Ошибка при завершении работы Windows"); } }

else if(ch == 'W' ch == 'w') { // Перезапуск Windows if(!ExitWindows(EW_RESTARTWINDOWS, 0)) { printf("Ошибка при завершении работы Windows"); } } else if(ch == 'N' ch == 'n') { // Временное завершение работы Windows, // запуск программы MS-DOS, затем // снова запуск Windows if(!ExitWindowsExec("g:\\nc\\nc.exe", NULL)) { printf("Ошибка при завершении работы Windows"); } } return 0; }

Это приложение содержит стандартную для Windows функцию WinMain. Для инициализации интерфейса EasyWin вызывается функция _InitEasyWin, описанная в файлах stdio.h, io.h, iostream.h:

void _Cdecl _InitEasyWin(void);

После вызова этой функции появляется окно и вы можете вызывать стандартные функции консольного ввода/вывода.

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

UINT WINAPI GetWindowsDirectory(LPSTR lpszSysPath, UINT cbSysPath);

Параметр lpszSysPath является указателем на буфер, в который будет записан путь к каталогу Windows. Длина буфера должна быть не менее 144 символов, она задается параметром cbSysPath.

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

Получив строку пути к каталогу Windows, приложение отображает его на экране и предлагает меню (рис. 8.5).



Рис. 8.5. Главное окно приложения EASYWIN3

В этом меню вам предлагается три возможности. Нажав клавишу <R>, вы сможете завершить работу Windows и перезагрузить компьютер. При этом будет выполнена загрузка MS-DOS.

Если вы нажмете клавишу <W>, произойдет перезапуск Windows без перезагрузки компьютера.

Если нажать клавишу <N>, работа Windows будет завершена и запустится Norton Commander (если он есть на диске вашего компьютера).


После завершения работы программы Norton Commander произойдет автоматический запуск Windows.

Для перезапуска Windows и перезагрузки компьютера мы использовали функцию программного интерфейса Windows с именем ExitWindows:

BOOL WINAPI ExitWindows(DWORD dwReturnCode, UINT wReserved);

Старший байт параметра dwReturnCode должен быть равен нулю, младший байт должен содержать код возврата, передаваемый MS-DOS при завершении работы Windows:

Параметр Описание
EW_RESTARTWINDOWS Перезапуск Windows
EW_REBOOTSYSTEM Завершение работы Windows и перезапуск системы. Этот код допустим только для Windows версии 3.1 и более старших версий
Параметр wReserved зарезервирован и должен быть равен нулю.

Функция ExitWindowsExec определена в программном интерфейсе Windows версии 3.1. Она завершает работу Windows, передавая управление указанной в параметре программе MS-DOS. После завершения работы этой программы Windows запускается вновь. Приведем прототип функции ExitWindowsExec:

BOOL WINAPI ExitWindowsExec(LPCSTR lpszExe, LPCSTR lpszParams);

Параметр lpszExe является дальним указателем на строку символов, закрытую двоичным нулем, содержащую путь к запускаемой программе MS-DOS. Через параметр lpszParams запускаемой программе можно передать строку параметров. Это значение можно задать как NULL.

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

Как правило, функции ExitWindowsExec и ExitWindows используются в программах установки программного обеспечения для Windows (в инсталляторах). О том, как создавать собственные инсталляторы, мы расскажем в отдельной главе в одной из следующих книг серии "Библиотека системного программиста".


Интерфейс графических устройств GDI


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

В Windows реализована концепция графического интерфейса, независимого от аппаратной реализации используемого устройства ввода/вывода. Этот интерфейс называется GDI (Graphics Device Interface). В рамках этого интерфейса определены все функции для работы с графикой.

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



Использование функции таймера


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

void CALLBACK _export TimerProc(HWND hwnd, UINT msg, UINT idTimer, DWORD dwTime);

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

Первый параметр функции таймера - идентификатор окна, с которым связан таймер. Если при создании таймера в качестве идентификатора было указано значение NULL, это же значение будет передано функции таймера.

Второй параметр представляет собой идентификатор сообщения WM_TIMER.

Третий параметр является идентификатором таймера, пославшего сообщение WM_TIMER.

И наконец, последний параметр - текущее время по системным часам компьютера. Это время выражается в количестве тиков таймера с момента запуска Windows. Вы можете узнать текущее системное время в любой момент, если воспользуетесь функцией GetCurrentTime или GetTickCount:

DWORD WINAPI GetCurrentTime(void); DWORD WINAPI GetTickCount(void);

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

Если для создания приложения вы пользуетесь современными средствами разработки, такими, как Borland C++ версии 3.1, Microsoft С++ версии 7.0, Microsoft Visual C++, для определения функции обратного вызова достаточно использовать ключевое слово _export. В этом случае вы можете создать таймер, например, так:

nTimerID = SetTimer(hwnd, 0, 1000, (TIMERPROC)TimerProc);

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

KillTimer(hwnd, nTimerID );

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


Когда вы не можете ограничиться использованием ключевого слова _export, для работы с функциями обратного вызова нужно сделать специальный переходник (thunk), вызвав функцию MakeProcInstance:

FARPROC WINAPI MakeProcInstance(FARPROC lpProc, HINSTANCE hinst);

В качестве первого параметра функции (lpProc) необходимо передать адрес функции, для которой создается переходник, а в качестве второго (hinst) - идентификатор приложения hInstance, полученный функцией WinMain при запуске приложения.

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

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

TIMERPROC lpfnTimerProc; lpfnTimerProc = (TIMERPROC)MakeProcInstance( (FARPROC)TimerProc, hInstance); nTimerID = SetTimer(hwnd, 0, 1000, (TIMERPROC)lpfnTimerProc);

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

void WINAPI FreeProcInstance(FARPROC lpProc);

Этой функции необходимо передать адрес уничтожаемой функции-переходника:

FreeProcInstance(lpfnTimerProc);

Функции MakeProcInstance и FreeProcInstance можно использовать совместно с современными средствами разработки, понимающими ключевое слово _export. Это не приведет к неправильной работе приложения.

Старые средства разработки приложений Windows требуют, чтобы все функции обратного вызова, такие, как функции окон и функции таймеров, были описаны в файле определения модуля, имеющем расширение .def, при помощи оператора EXPORTS, например:

EXPORTS WndProc TimerProc

Транслятор Borland C++ версии 3.1 распознает ключевое слово _export и автоматически формирует нужный пролог и эпилог для функций обратного вызова. То же самое относится и к трансляторам Microsoft C++ версии 7.0 и Microsoft Visual C++. Поэтому в наших примерах функции MakeProcInstance и FreeProcInstance, а также оператор файла определения модуля EXPORTS не используются.


Использование символов кириллицы


При использовании редактора текста, входящего в состав систем разработки Borland C++ for Windows версии 3.1 или Turbo C++ for Windows версии 3.1, вы можете столкнуться с невозможностью просмотра комментариев или текстовых строк, набранных с использованием символов кириллицы. Это связано с тем, что указанные выше средства используют для работы с текстом собственный шрифт, который не содержит символов кириллицы.

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

Для замены шрифта из главного меню системы разработки выберите меню "Options". В меню "Options" выберите строку "Environment". Затем на экране появится еще одно (!) меню, в котором надо выбрать строку "Preferences...". На экране появится диалоговая панель "Preferences" (рис. 8.1).

Рис. 8.1. Выбор нового шрифта

По умолчанию в меню "Font" выбран шрифт BorlandTE, в котором нет символов кириллицы. Мы рекомендуем вам вместо этого шрифта выбрать шрифт Courier Cyrillic, который есть в русской версии Windows и который устанавливается практически всеми программами локализации Windows, такими, как CyrWin. К сожалению, версия 3.1 системы разработки Borland C++ for Windows не может работать со шрифтами TrueType. Этот недостаток устранен в следующей версии.



Изменение режимов вывода текста


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

Функция SetTextAlign позволяет определить выравнивание прямоугольной области, используемой для вывода текста, относительно указанных координат вывода. Эта функция имеет следующий прототип:

UINT WINAPI SetTextAlign(HDC hdc , UINT fuAlign);

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

Второй параметр, fuAlign, состоит из трех наборов битовых флагов. Флаги из каждого набора можно объединять при помощи логической операции ИЛИ.

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

Символическое имя флага Описание
TA_CENTER Выравнивание по центру
TA_LEFT Выравнивание по левой границе. Этот способ выравнивания используется по умолчанию
TA_RIGHT Выравнивание по правой границе

Вторая группа флагов отвечает за выравнивание текста по вертикали:

Символическое имя флага Описание
TA_BASELINE Выравнивание по базовой линии шрифта
TA_TOP Выравнивание по верхней границе. Этот режим используется по умолчанию
TA_BOTTOM Выравнивание по нижней границе

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

Символическое имя флага Описание
TA_NOUPDATECP Не обновлять текущую позицию после вывода текста функциями TextOut и ExtTextOut. Этот режим используется по умолчанию
TA_UPDATECP Обновлять текущую позицию после вывода текста функциями TextOut и ExtTextOut

Вы можете узнать состояние флагов выравнивания, выбранных в контекст отображения, при помощи функции GetTextAlign:


UINT WINAPI GetTextAlign(HDC hdc);

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

Можно изменить цвет текста. Для этого необходимо вызвать функцию SetTextColor:

COLORREF WINAPI SetTextColor(HDC hdc, COLORREF clrref);

Параметр hdc определяет контекст отображения, для которого вы собираетесь изменить цвет текста.

Структура clrref определяет цвет.

Функция SetTextColor возвращает значение цвета, которое использовалось для вывода текста раньше.

Тип данных COLORREF определен в файле windows.h следующим образом:

typedef DWORD COLORREF;

Для указания цвета удобно использовать макрокоманду RGB, определенную в файле windows.h:

#define RGB(r,g,b) ((COLORREF)(((BYTE)(r) | ((WORD)(g)<<8)) | (((DWORD)(BYTE)(b))<<16)))

В качестве параметров для этой макрокоманды вы можете указывать интенсивность отдельных цветов в диапазоне от 0 до 255:

Параметр Цвет
r Красный
g Зеленый
b Голубой
Для определения отдельных цветовых компонент из возвращаемого функцией SetTextColor значения удобно использовать макрокоманды GetRValue, GetGValue и GetBValue, определенные в файле windows.h:

#define GetRValue(rgb) ((BYTE)(rgb)) #define GetGValue(rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue(rgb) ((BYTE)((rgb)>>16))

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

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

COLORREF WINAPI GetTextColor(HDC hdc);

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


Клавиатура


5.1.

5.2.

5.3.

5.4.

5.5.

5.6.

5.7.

Способы работы с клавиатурой в среде Windows коренным образом отличаются от тех, к которым вы привыкли, когда составляли программы для MS-DOS. Из-за чрезвычайно слабой поддержки клавиатуры со стороны MS-DOS программы обычно вызывали прерывание INT16h, обработчик которого находится в BIOS. Приложения Windows не могут воспользоваться этим прерыванием, но в их распоряжении находится куда более мощный механизм сообщений.

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

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

Для распределения сообщений от клавиатуры используется концепция так называемого фокуса ввода (input focus). Фокус ввода - это атрибут, который может присваиваться окну, созданному приложением или Windows. Если окно имеет фокус ввода, соответствующая функция окна получает все клавиатурные сообщения из системной очереди.

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

Функция окна может проследить за получением и потерей фокуса ввода.
Когда окно получает фокус ввода, функции окна передается сообщение WM_SETFOCUS. Когда окно теряет фокус ввода, функции окна передается сообщение WM_KILLFOCUS. В ответ на эти сообщения функция окна не может запретить получение или потерю фокуса ввода, так что это сообщение носит чисто информирующий характер.

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

Сообщения, генерируемые драйвером клавиатуры, являются сообщениями низкого уровня. Они несут в себе такую информацию, как, например, скан-код нажатой клавиши. Приложения не пользуются такими сообщениями. Операционная система Windows выполняет преобразование клавиатурных сообщений низкого уровня в сообщения, которые содержат коды виртуальных клавиш (virtual-key code).

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

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

Сразу отметим, что приложения Windows редко используют методику посимвольного ввода и отображения, широко распространенную при создании программ MS-DOS.


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

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

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

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

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


Клавиатурные сообщения


От клавиатуры может поступать четыре сообщения - WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP. Когда вы нажимаете клавишу, генерируется сообщение WM_KEYDOWN или WM_SYSKEYDOWN, в зависимости от того, какая была нажата клавиша и была ли эта клавиша нажата в комбинации с клавишей <Alt>. При отпускании клавиши генерируется сообщение WM_KEYUP или WM_SYSKEYUP.

Клавиатурные сообщения с префиксом WM_SYS называются системными клавиатурными сообщениями. Приложение редко обрабатывает системные сообщения от клавиатуры, передавая их в неизменном виде функции DefWindowProc. Обработка системных клавиатурных сообщений обычно сводится к выбору элементов меню, переключения на другое окно или другое приложение. Системные сообщения предназначены для Windows.

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

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



Компоненты и подсистемы Windows


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

Рассмотрим кратко основные особенности отдельных подсистем Microsoft Windows.



Контекст отображения


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

При обработке сообщения WM_PAINT перед выводом текста вызывается функция BeginPaint, возвращающая идентификатор так называемого контекста отображения (display context):

hdc = BeginPaint(hwnd, &ps);

Контекст отображения используется функцией вывода текста TextOut:

TextOut(hdc, 10, 10, "Сообщение WM_PAINT", 18);

Перед завершением обработки сообщения WM_PAINT приложение должно обязательно вызвать функцию EndPaint, освобождающую полученный ранее контекст отображения устройства:

EndPaint(hwnd, &ps);

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

Функция TextOut, которую мы использовали для вывода текста, также относится к интерфейсу GDI. Она имеет пять параметров:

BOOL TextOut(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString, int cbString);

Первый параметр (hdc) определяет используемый контекст отображения. При обработке сообщения WM_PAINT контекст отображения создается функцией BeginPaint для того окна, идентификатор которого указывается этой функции в качестве первого параметра.

Второй (nXStart) и третий (nYStart) параметры задают координаты (x, y) начальной позиции, начиная с которой будет выполняться вывод текста.

Четвертый параметр (lpszString) является дальним указателем на выводимую строку, длина которой определяется последним, пятым параметром (cbString).

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

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

Используемая система координат также определяется в контексте отображения. По умолчанию в левом верхнем углу внутренней области окна (client area), ограниченной сверху заголовком, а с других сторон рамкой, находится начало координат - точка с координатами (0,0). Ось x направлена слева направо, ось y - сверху вниз. Изменяя контекст отображения, вы можете выбрать другую систему координат, например расположив начало координат в центре внутренней области окна.

Контекст отображения не позволит вам вывести текст за пределами внутренней области окна. Однако позже вы научитесь создавать другой тип контекста - контекст устройства (device context), относящийся к всей поверхности экрана видеомонитора. Этот контекст позволит вам выводить текст или графические изображения в любое место экрана.

Существует и другой способ получения контекста отображения, который должен использоваться при обработке других, отличных от WM_PAINT, сообщений:

hdc = GetDC(hwnd); TextOut(hdc, 10, 40, "Сообщение WM_LBUTTONDOWN", 24); ReleaseDC(hwnd, hdc);

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

Обратим еще раз ваше внимание на то, что при обработке сообщения WM_PAINT для создания и освобождения контекста отображения необходимо использовать функции BeginPaint и EndPaint, а во всех остальных случаях - функции GetDC и ReleaseDC.


Курсор мыши


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

Вы также можете изменять форму курсора мыши. Можно определить форму курсора (или, иными словами, определить курсор) при регистрации класса окна или изменить ее в любое время в процессе работы приложения.

При регистрации класса окна мы задавали форму курсора следующим способом:

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

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

Символическое имя Описание
IDC_ARROW Стандартный курсор в виде стрелки
IDC_CROSS Курсор в виде перекрещивающихся линий
IDC_IBEAM Текстовый курсор в виде буквы "I"
IDC_ICON Пустая пиктограмма
IDC_SIZE Курсор в виде четырех стрелок, указывающих в разных направлениях
IDC_SIZENESW Двойная стрелка, указывающая в северо-восточном и юго-западном направлении
IDC_SIZENS Двойная стрелка, указывающая в севером и южном направлении
IDC_SIZENWSE Двойная стрелка, указывающая в северо-западном и юго-восточном направлении
IDC_SIZEWE Двойная стрелка, указывающая в восточном и западном направлении
IDC_UPARROW Вертикальная стрелка
IDC_WAIT Курсор в виде песочных часов

Вы можете попробовать изменить курсор в любом из уже рассмотренных нами ранее приложений, создающих окна.

Забегая вперед, скажем, что вы можете создать курсор произвольной формы с помощью такого средства, как Borland Resource Workshop или Microsoft SDK. В этом случае вы должны нарисовать курсор в виде небольшой картинки, состоящей из отдельных точек. Эта картинка создается специальным графическим редактором и сохраняется в файле с расширением .cur. Затем файл подключается к ресурсам приложения, которые записываются в исполняемый exe-файл. Каждый ресурс в файле имеет свой идентификатор.
Вы можете изменить форму курсора, если укажете идентификатор ресурса, соответствующего новому изображению курсора.

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

HCURSOR WINAPI LoadCursor(HINSTANCE hinst, LPCSTR lpszCursor);

Для загрузки нового курсора из ресурсов приложения в качестве первого параметра (hinst) необходимо указать идентификатор приложения, полученный через параметры функции WinMain. Второй параметр (lpszCursor) при этом должен указывать на идентификатор ресурса. Подробнее об использовании ресурсов мы поговорим позже в отдельной главе.

Если же в качестве первого параметра указать значение NULL, для загрузки курсора можно использовать перечисленные выше символические имена с префиксом IDC_. Именно так мы и поступаем при регистрации класса окна:

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

Функция LoadCursor возвращает идентификатор загруженного курсора или NULL при ошибке.

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

HCURSOR WINAPI SetCursor(HCURSOR hcur);

Параметр hcur функции SetCursor должен указывать идентификатор нового курсора, подготовленный при помощи функции LoadCursor. Если указать параметр как NULL, изображение курсора исчезнет с экрана.

Для того чтобы выключить изображение курсора мыши или вновь включить его, используют функцию ShowCursor:

int WINAPI ShowCursor(BOOL fShow);

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

Для включения курсора в качестве параметра fShow функции следует передать значение TRUE, для выключения - FALSE.



Возвращаемое функцией ShowCursor значение равно новому содержимому счетчика.

Наблюдая за работой стандартных приложений Windows, вы можете заметить, что часто на время выполнения длительных операций курсор принимает форму песочных часов. Как правило, все такие операции выполняются во время обработки какого-либо одного сообщения. Перед началом выполнения операции вы можете вызвать функцию LoadCursor с параметром IDC_WAIT, а затем вернуть прежнюю форму, вызвав эту же функцию еще раз. Дополнительно на время выполнения операции обработчик сообщения должен захватить мышь, вызвав функцию SetCapture. В этом случае вы не сможете с помощью мыши переключиться на другое приложение и прервать таким образом ход длительной операции. После выполнения операции следует освободить мышь, вызвав функцию ReleaseCapture.

Ваше приложение может установить курсор мыши в новое положение или определить текущие координаты курсора.

Для установки курсора мыши в новое положение следует вызвать функцию SetCursorPos:

void WINAPI SetCursorPos(int x, int y);

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

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

void WINAPI GetCursorPos(POINT FAR* lppt);

Эта функция записывает в поля x и y структуры типа POINT соответственно горизонтальную и вертикальную координату курсора мыши.

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

void WINAPI ClipCursor(const RECT FAR* lprc);

В качестве параметра lprc функции передается указатель на структуру типа RECT, в которой указываются координаты области ограничения. Как только необходимость в ограничении пропадет, следует освободить движение мыши, вызвав функцию ClipCursor с параметром NULL.

Программный интерфейс Windows версии 3.1 содержит функцию GetClipCursor, с помощью которой можно определить расположение и размер области, ограничивающей движение курсора:

void WINAPI GetClipCursor(RECT FAR* lprc);

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


Листинги файлов приложения


В отличие от первого приложения (листинг 1.1) новое приложение состоит из двух файлов - файла window.cpp (листинг 1.2), содержащего исходный текст функций приложения, и файла window.def (листинг 1.3), который является файлом определения модуля.

В файле window.cpp определены функции WinMain, InitApp и функция окна WndProc, то есть все функции, из которых состоит приложение. Файл window.def содержит инструкции редактору связей. Эти инструкции используются при создании загрузочного exe-файла приложения.

Рассмотрим подробно файл window.cpp.

Листинг 1.2. Файл window\window.cpp

// ---------------------------------------- // Простейшее приложение Windows // с циклом обработки сообщений // ---------------------------------------- #define STRICT #include <windows.h> #include <mem.h> // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); // Имя класса окна char const szClassName[] = "WindowAppClass"; // Заголовок окна char const szWindowTitle[] = "Window Application"; // ===================================== // Функция WinMain // Получает управление при запуске // приложения // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, // идентификатор текущей // копии приложения HINSTANCE hPrevInstance, // идентификатор предыдущей // копии приложения LPSTR lpszCmdLine, // указатель на командную // строку int nCmdShow) // способ отображения // главного окна приложения { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Проверяем, не запускалось ли это приложение ранее if(!hPrevInstance) { // Если не запускалось, вызываем функцию InitApp // для инициализации приложения. // Если инициализацию выполнить не удалось, // завершаем приложение if(!InitApp(hInstance)) return FALSE; } // Если данное приложение уже работает, // выводим сообщение о том, что допускается // запуск только одной копии приложения, и // затем завершаем работу приложения else { MessageBox(NULL, "Можно запускать только одну копию приложения", "Ошибка", MB_OK | MB_ICONSTOP); return FALSE; } // После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE; // Рисуем окно.
Для этого после функции ShowWindow, // рисующей окно, вызываем функцию UpdateWindows, // посылающую сообщение WM_PAINT в функцию окна ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } // Возвращаем значение WParam, переданное // в качестве параметра функции PostQuitMessage // в процессе инициирования завершения работы // приложения из функции окна. // Затем завершаем работу приложения return msg.wParam; } // ===================================== // Функция InitApp // Вызывается из функции WinMain для // инициализации приложения. // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc)); // Стиль окна wc.style = 0; // Указатель на функцию окна, обрабатывающую // сообщения, предназначенные для всех окон, // созданных на основе данного класса wc.lpfnWndProc = (WNDPROC) WndProc; // Размер дополнительной области данных, // зарезервированной в описании класса окна wc.cbClsExtra = 0; // Размер дополнительной области данных, // зарезервированной для каждого окна, // созданного на основе данного класса wc.cbWndExtra = 0; // Идентификатор приложения, которое // создало данный класс wc.hInstance = hInstance; // Идентификатор пиктограммы, используемой // для окна данного класса wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // Идентификатор курсора, используемого // для окна данного класса wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Цвет фона окна wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // Идентификатор меню wc.lpszMenuName = (LPSTR)NULL; // Имя, которое присваивается создаваемому // классу и используется при создании // окон данного класса wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); // Возвращаем результат регистрации класса return (aWndClass != 0); } // ===================================== // Функция WndProc // НЕ ВЫЗЫВАЕТСЯ ни из одной функции приложения. // Эту функцию вызывает Windows в процессе // обработки сообщений.


Для этого адрес функции WndProc // указывается при регистрации класса окна. // Функция выполняет обработку сообщений главного // окна приложения // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Выполняем обработку сообщений. Идентификатор // сообщения передается через параметр msg switch (msg) { // Это сообщение приходит, когда вы поместили курсор // мыши в область главного окна приложения и нажали // левую клавишу мыши case WM_LBUTTONDOWN: { MessageBox(NULL, "Нажата левая клавиша мыши", "Сообщение", MB_OK | MB_ICONINFORMATION); return 0; } // Это сообщение приходит, когда вы поместили курсор // мыши в область главного окна приложения и нажали // правую клавишу мыши case WM_RBUTTONDOWN: { MessageBeep(-1); // звуковой сигнал MessageBox(NULL, "Нажата правая клавиша мыши", "Сообщение", MB_OK | MB_ICONINFORMATION); return 0; } // Это сообщение приходит, когда вы завершаете // работу приложения стандартным для // Windows способом case WM_DESTROY: { // Инициируем завершение работы приложения, // помещая в очередь приложения сообщение // WM_QUIT. Это приведет к завершению // цикла обработки сообщений в функции WinMain PostQuitMessage(0); return 0; } } // Все сообщения, которые не обрабатываются нашей // функцией окна, ДОЛЖНЫ передаваться функции // DefWindowProc return DefWindowProc(hwnd, msg, wParam, lParam); }


ЛИТЕРАТУРА


Charles Petzold. Programming Windows. Microsoft Press. One Microsoft Way. Redmont, Washington, 1990

Brent Rector. Developing Windows 3.1 Applications with Microsoft C/C++. SAMS, 1992

Daniel A. Norton. Writing Windows Device Drivers. Addison-Wesley Publishing Company, 1992

А. В. Фролов, Г. В. Фролов. Библиотека системного программиста. Том 6. Защищенный режим процессоров Intel 80286/80386/80486. Москва, "ДИАЛОГ-МИФИ", 1993

Windows. Справочник для программистов. Версия 3.0. Часть 1, 2. Москва, "Научный центр", 1991

The Windows Interface: An Application Design Guide. Microsoft Windows Software Development Kit. Microsoft, 1992

С. А. Гладков, Г. В. Фролов. Программирование в Microsoft Windows. В двух частях. Москва, "ДИАЛОГ-МИФИ", 1992

М. Эллис, Б. Строуструп. Справочное руководство по языку программирования C++ с комментариями. Москва, МИР, 1992

Кевин Стрело. Фирма Microsoft представляет пакет Windows NT SDK. Мир ПК N 7/92

Windows. Специальный выпуск. Компьютер Пресс, N 6/92

Марк Адлер. Система Windows: введение в программирование. Мир ПК N 5/91, 6/91

Рей Дункан. Изучаем формат исполняемого файла Windows NT. PC Magazine, Russian Edition, N 3/93

Рэй Дункан. Многозадачность и многопроцессорность в среде Windows NT. PC Magazine, Russian Edition, N 3/93

Джефф Просис. Пиктограммы Windows изнутри. Часть 1. PC Magazine, Russian Edition, N 3/93

Рэй Дункан. Библиотека ToolHelp для Windows 3.1. Часть 1. PC Magazine, Russian Edition, N 5/92

Эндрю Шулман. Windows 3.0 в защищенном режиме - достигая невозможного. PC Magazine, Russian Edition, N 2/91

Рэй Дункан. Постигая интерфейс прикладных программ оболочки Windows. PC Magazine, Russian Edition, N 2/91



Метрики текста


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

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

Прежде всего следует отметить, что все шрифты в Windows можно разделить на две группы. К первой группе относятся шрифты с фиксированной шириной букв (fixed-pitch font). Все буквы (и знаки, такие, как запятая, точка и т. д.) такого шрифта имеют одинаковую ширину. Вторая группа шрифтов - шрифты с переменной шириной букв (variable-pitch font). Для таких шрифтов каждая буква имеет свою ширину. Буква "Ш", например, шире буквы "i".

Кроме того, шрифты Windows можно разделить на растровые (raster font), контурные (stroke font) и масштабируемые (типа TrueType). Образцы этих шрифтов представлены на рис. 4.3.

Рис. 4.3. Растровый, контурный и масштабируемый шрифты

Растровые шрифты состоят из отдельных пикселов и используются при выводе текста на экран видеомонитора или принтер. Для обеспечения приемлемого качества текста в Windows имеется набор одинаковых растровых шрифтов для нескольких размеров букв. Если попытаться выполнить масштабирование растрового шрифта в сторону увеличения размера букв, наклонные линии и закругления будут изображаться в виде "лестницы" (см. рис. 4.3).

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

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


Любой из перечисленных выше шрифтов может быть с фиксированной или переменной шириной букв.

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

Системный шрифт относится к растровым шрифтам с переменной шириной букв.

Так как ширина букв переменная, вы не сможете выполнять вывод таблицы в несколько столбцов, ориентируясь на ширину букв. Вам придется либо выводить каждый столбец таблицы по отдельности, начиная с некоторой позиции в окне, либо подсчитывать длину каждой ячейки и дополнять ее пробелами до некоторой фиксированной ширины (первый способ нам кажется удобнее).

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

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

BOOL WINAPI GetTextMetrics(HDC hdc, TEXTMETRIC FAR* lptm);

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

Второй параметр функции (lptm) является дальним указателем на структуру типа TEXTMETRIC, в которую будет записана информация о метриках шрифта, выбранного в указанный контекст устройства.

В случае успешного завершения функция возвращает значение TRUE, в противном случае - FALSE.

Структура TEXTMETRIC описана в файле windows.h следующим образом:

typedef struct tagTEXTMETRIC { int tmHeight; int tmAscent; int tmDescent; int tmInternalLeading; int tmExternalLeading; int tmAveCharWidth; int tmMaxCharWidth; int tmWeight; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmFirstChar; BYTE tmLastChar; BYTE tmDefaultChar; BYTE tmBreakChar; BYTE tmPitchAndFamily; BYTE tmCharSet; int tmOverhang; int tmDigitizedAspectX; int tmDigitizedAspectY; } TEXTMETRIC;



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



Рис. 4.4. Метрики шрифта

Отсчет всех размеров выполняется от так называемой базовой линии шрифта.

Общая высота букв находится в поле tmHeight структуры TEXTMETRIC. Эта высота складывается из двух компонент - tmAscent и tmDescent. Компонента tmAscent представляет собой высоту букв от базовой линии с учетом таких элементов, как тильда в букве "Й". Компонента tmDescent определяет пространство, занимаемое буквами ниже базовой линии. Сумма tmAscent и tmDescent в точности равна tmHeight.

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

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

А как определить ширину букв?

В структуре TEXTMETRIC есть два поля с именами tmAveCharWidth и tmMaxCharWidth. Поле tmAveCharWidth содержит среднее значение ширины строчных букв шрифта. Это значение приблизительно соответствует ширине латинской буквы "x". Поле tmMaxCharWidth определяет ширину самой широкой буквы в шрифте. Для шрифта с фиксированной шириной букв поля tmAveCharWidth и tmMaxCharWidth содержат одинаковые значения, которые зависят от самого шрифта.

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



Другие поля структуры TEXTMETRIC мы рассмотрим позже, в главе, посвященной шрифтам.

Для иллюстрации использования метрик шрифта мы подготовили приложение TMETRICS. Главный файл приложения, содержащий функцию WinMain, приведен в листинге 4.15.

Листинг 4.15. Файл tmetrics\tmetrics.cpp

// ---------------------------------------- // Определение метрики шрифта // ----------------------------------------

#define STRICT #include <windows.h> #include <mem.h>

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "TMETRICSAppClass"; char const szWindowTitle[] = "TMETRICS Application";

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

if(!InitApp(hInstance)) return FALSE;

hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры if(!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна

memset(&wc, 0, sizeof(wc));

// Определяем стиль класса окна, при // использовании которого окно требует // перерисовки в том случае, если // изменилась его ширина или высота wc.style = CS_HREDRAW | CS_VREDRAW;



wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;

aWndClass = RegisterClass(&wc); return (aWndClass != 0); }

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

wc.style = CS_HREDRAW | CS_VREDRAW;

Функция окна будет получать сообщение WM_PAINT при изменении ширины или высоты окна.

Как и раньше, вся полезная работа выполняется функцией главного окна приложения (листинг 4.16).

Листинг 4.16. Файл tmetrics\wndproc.cpp

// ===================================== // Функция WndProc // =====================================

#define STRICT #include <windows.h> #include <stdio.h> #include <string.h>

void Print(HDC, int, char *);

static int cxChar, cyChar; static int cxCurrentPosition; static int cyCurrentPosition;

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования static TEXTMETRIC tm; // структура для записи метрик // шрифта switch (msg) { case WM_CREATE: { // Получаем контекст отображения, // необходимый для определения метрик шрифта hdc = GetDC(hwnd);

// Заполняем структуру информацией // о метрике шрифта, выбранного в // контекст отображения GetTextMetrics(hdc, &tm);

// Запоминаем значение ширины для // самого широкого символа cxChar = tm.tmMaxCharWidth;

// Запоминаем значение высоты букв с // учетом межстрочного интервала cyChar = tm.tmHeight + tm.tmExternalLeading;

// Инициализируем текущую позицию // вывода текста cxCurrentPosition = cxChar; cyCurrentPosition = cyChar;

// Освобождаем контекст ReleaseDC(hwnd, hdc); return 0; }

case WM_PAINT: { // Инициализируем текущую позицию // вывода текста cxCurrentPosition = cxChar; cyCurrentPosition = cyChar;



hdc = BeginPaint(hwnd, &ps);

// Выводим параметры шрифта, полученные во // время создания окна при обработке // сообщения WM_CREATE Print(hdc, tm.tmHeight, "tmHeight"); Print(hdc, tm.tmAscent, "tmAscent"); Print(hdc, tm.tmDescent, "tmDescent"); Print(hdc, tm.tmInternalLeading, "tmInternalLeading"); Print(hdc, tm.tmExternalLeading, "tmExternalLeading"); Print(hdc, tm.tmAveCharWidth, "tmAveCharWidth"); Print(hdc, tm.tmMaxCharWidth, "tmMaxCharWidth"); Print(hdc, tm.tmWeight, "tmWeight"); Print(hdc, tm.tmItalic, "tmItalic"); Print(hdc, tm.tmUnderlined, "tmUnderlined"); Print(hdc, tm.tmStruckOut, "tmStruckOut"); Print(hdc, tm.tmFirstChar, "tmFirstChar"); Print(hdc, tm.tmLastChar, "tmLastChar"); Print(hdc, tm.tmDefaultChar, "tmDefaultChar"); Print(hdc, tm.tmBreakChar, "tmBreakChar"); Print(hdc, tm.tmPitchAndFamily, "tmPitchAndFamily"); Print(hdc, tm.tmCharSet, "tmCharSet"); Print(hdc, tm.tmOverhang, "tmOverhang"); Print(hdc, tm.tmDigitizedAspectX,"tmDigitizedAspectX"); Print(hdc, tm.tmDigitizedAspectY,"tmDigitizedAspectY");

EndPaint(hwnd, &ps); return 0; }

case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

// ========================================== // Функция для вывода параметров шрифта // в окно // ==========================================

void Print(HDC hdc, int tmValue, char *str) { char buf[80]; int i;

// Подготавливаем в рабочем буфере // и выводим в окно начиная с текущей // позиции название параметра sprintf(buf, "%s", str); i = strlen(str);

TextOut(hdc, cxCurrentPosition, cyCurrentPosition, buf, i);

// Подготавливаем в рабочем буфере // и выводим в текущей строке окна // со смещением значение параметра sprintf(buf, "= %d", tmValue); i = strlen(buf);

TextOut(hdc, cxCurrentPosition + 12 * cxChar, cyCurrentPosition, buf, i);



// Увеличиваем текущую позицию по // вертикали на высоту символа cyCurrentPosition += cyChar; }

Как вы уже знаете, при создании окна в функцию окна передается сообщение WM_CREATE. Это сообщение удобно использовать для инициализации данных, имеющих отношение к окну. В нашем случае при получении сообщения WM_CREATE функция окна получает контекст отображения (с помощью функции GetDC) и вызывает функцию GetTextMetrics, которая записывает сведения о метрике текущего выбранного в контекст шрифта в структуру с именем tm.

На основании информации, хранящейся в этой структуре, вычисляются значения переменных cxChar и cyChar, используемых соответственно в качестве ширины и высоты символов.

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

cxCurrentPosition = cxChar; cyCurrentPosition = cyChar;

Перед возвратом обработчик сообщения WM_CREATE освобождает полученный контекст, вызывая функцию ReleaseDC.

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

Функция Print выводит параметры в виде таблицы, состоящей из двух колонок. Для этого она вызывает уже знакомую вам функцию TextOut два раза для каждого параметра. В первый раз выводится название параметра, во второй - знак "=" и значение параметра.

Перед возвратом из функции текущая позиция вывода текста по вертикали увеличивается на одну строку:

cyCurrentPosition += cyChar;

Файл определения модуля для приложения TMETRICS приведен в листинге 4.17.

Листинг 4.17. Файл tmetrics\tmetrics.def

; ============================= ; Файл определения модуля ; ============================= NAME TMETRICS DESCRIPTION 'Приложение TMETRICS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple

Внешний вид главного окна приложения TMETRICS показан на рис. 4.5.



Рис. 4.5. Главное окно приложения TMETRICS

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

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



Рис. 4.6. Окно уменьшенного размера

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


Метрики Windows


4.1.

4.2.

4.3.

4.4.

4.5.

4.6.

4.7.

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

В приложении WSTYLE мы указывали абсолютные значения координат и размеров создаваемых окон. Однако Windows может работать в режимах с различным разрешением видеоадаптера. Если вы подберете все размеры для разрешения 640 х 480 точек, в режиме 1200 х 1024 все ваше изображение окажется в верхнем левом углу экрана и вам придется напрягать зрение для того, чтобы что-нибудь рассмотреть. Было бы логичнее определять размеры и расположение создаваемых окон исходя из общего размера экрана, определяя последние динамически при работе приложения.



Модели памяти


Так же как и при разработке программ для MS-DOS, при создании приложений Windows вам следует правильно выбрать модель памяти. Напомним основные различия между моделями памяти.

Модель памяти Tiny. Для этой модели создается один сегмент кода и один сегмент данных, причем суммарный размер этих сегментов не должен превышать 64 Кбайт. Эта модель памяти используется при создании загрузочных модулей с расширением имени com. Вы не можете использовать эту модель памяти при создании приложений Windows.

Модель памяти Small. Для этой модели создается один сегмент кода и один сегмент данных, причем суммарный размер этих сегментов не должен превышать 128 Кбайт. Размер массивов данных, созданных с использованием этой модели, не должен превышать 64 Кбайт. Все указатели являются ближними. Эта модель памяти пригодна для приложений Windows. Все примеры приложений, приведенные в нашей книге, созданы с использованием модели памяти Small.

Модель памяти Medium. В этой модели памяти создается один сегмент данных и несколько сегментов кода. Размер массива данных не должен превышать 64 Кбайт. Эта модель памяти часто используется при создании относительно сложных приложений Windows, для которых невозможно обойтись одним сегментом кода размером 64 Кбайт. Для вызова функций в данной модели памяти используются 32-разрядные адреса, состоящие из компонент <селектор:смещение>.

Модель памяти Compact. При использовании этой модели памяти можно создать только один сегмент кода, но несколько сегментов данных. Размер массива данных не должен превышать 64 Кбайт. Для вызова функций, определенных внутри приложения, используется только компонента смещения. Для адресации данных применяются 32-разрядные адреса.

Модель памяти Large. В этой модели памяти создается несколько сегментов кода и несколько сегментов данных. Размер массива данных по-прежнему не должен превышать 64 Кбайт. Для вызова функций и адресации данных используются 32-разрядные адреса в формате <селектор:смещение>.

Модель памяти Huge.
Эта модель памяти во всем аналогична модели Large, но допускает работу с массивами неограниченного размера.

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

Большинство приложений Windows использует либо модель памяти Small, либо Medium, либо так называемую смешанная модель памяти.

Смешанная модель памяти. Для использования смешанной модели памяти в программах MS-DOS используются такие ключевые слова, как _near или _far. Приложения Windows должны пользоваться словами NEAR или FAR.

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

Следует отметить, что, строго говоря, вне зависимости от того, какую модель памяти вы укажете при создании проекта, все равно вам придется пользоваться смешанной моделью памяти. Даже при использовании модели памяти Small ваше приложение будет вызывать функции программного интерфейса Windows. Все эти функции в Windows версии 3.1 описаны с ключевым словом _far и расположены в сегментах, принадлежащих не вашему приложению, а операционной системе. Программа MS-DOS, созданная с использованием модели памяти Small, не может содержать дальних вызовов, так как все вызываемые ей функции расположены в одном сегменте кода, а вызовы MS-DOS выполняются через прерывания. Приложения Windows, напротив, не используют прерывания для вызова операционной системы. Они всегда (при использовании любой модели памяти) вызывают операционную систему с помощью команды дальнего вызова.


Мышь


6.1.

6.2.

6.3.

6.4.

6.5.

6.6.

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

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

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

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



О сложности программирования для MS-DOS и Windows


Итак, вы познакомились с простейшим приложением Windows, обрабатывающим сообщения. У вас могло сложиться впечатление, что создание приложений Windows - достаточно сложная задача, гораздо более сложная, чем программирование для MS-DOS. Попытаемся убедить вас в обратном.

Вспомните, какие проблемы были у вас с резидентными программами, реализующими примитивное переключение задач в MS-DOS. Даже появление в составе MS-DOS версий 5.0 и 6.0 специального переключателя задач не позволяет достигнуть такого эффекта, который получается в Windows. Все, что должно сделать приложение Windows для обеспечения работы в мультизадачном режиме, - это создать цикл обработки сообщений и следить за тем, чтобы в процессе обработки отдельных сообщений процессор не захватывался на большой период времени. Такие задачи, как совместное использование параллельно работающими приложениями экрана видеомонитора, клавиатуры или мыши, значительно упрощаются благодаря поддержке со стороны Windows.

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

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

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

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

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

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

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

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

Для стандартизации пользовательского диалогового интерфейса разработчики приложений Windows должны пользоваться руководством по разработке интерфейса "The Windows Interface: An Application Design Guide", созданным фирмой Microsoft.Это руководство поставляется в составе средств разработки приложений Windows, таких, как Windows SDK или FoxPro for Windows. Это руководство содержит очень подробные рекомендации по разработке как интерфейса в целом, так и его отдельных компонентов.

По сравнению с MS-DOS операционная система Windows предлагает гораздо более удобный механизм управления памятью, предоставляя в распоряжение приложения гигантские области непосредственно адресуемой памяти, реальной или виртуальной. Это позволяет легко решать такие задачи, которые даются в MS-DOS с большим трудом и, как правило, требуют использования специальных расширителей MS-DOS (DOS-экстендеров).


Обработка сообщений


Откуда берутся сообщения?

Большинство сообщений создают драйверы устройств ввода/вывода, таких, как клавиатура, мышь или таймер. Драйверы создают сообщения при поступлении аппаратных прерываний. Например, когда вы нажимаете и затем отпускаете клавишу, драйвер обрабатывает прерывания от клавиатуры и создает несколько сообщений. Аналогично сообщения создаются при перемещении мыши или в том случае, когда вы нажимаете кнопки на корпусе мыши. Можно сказать, что драйверы устройств ввода/вывода транслируют аппаратные прерывания в сообщения.

Куда направляются сообщения, созданные драйверами?

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

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

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

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

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

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

В Windows существует понятие фокуса ввода (input focus), помогающее в распределении сообщений. Фокус ввода - это атрибут, который в любой момент времени может относиться только к одному окну. Если окно имеет фокус ввода, все сообщения от клавиатуры распределяются сначала в очередь сообщений приложения, создавшего окно, а затем - функции окна, владеющего фокусом ввода. Нажимая определенные клавиши, вы можете перемещать фокус ввода от одного окна к другому. Например, если вы работаете с диалоговой панелью, содержащей несколько окон, предназначенных для ввода текста, с помощью клавиши <Tab> вы можете переключать фокус с одного такого окна на другое и вводить текст в различные окна диалоговой панели. Существуют также комбинации клавиш, с помощью которых вы можете переключиться на другое приложение. При этом фокус ввода будет отдан другому окну, принадлежащему другому приложению.

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

Простейший цикл обработки сообщений состоит из вызовов двух функций - GetMessage и DispatchMessage.



Функция GetMessage предназначена для выборки сообщения из очереди приложения. Сообщение выбирается из очереди и записывается в область данных, принадлежащую приложению.

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

Вот как выглядит простейший вариант цикла обработки сообщений:

MSG msg; while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); }

Завершение цикла обработки сообщений происходит при выборке из очереди специального сообщения, в ответ на которое функция GetMessage возвращает нулевое значение. Тип MSG описан в файле windows.h.

Процесс обработки сообщений схематически показан на рис. 1.9.



Рис. 1.9. Процесс обработки сообщений

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

Функция WinMain в цикле обработки сообщений с помощью функции GetMessage выбирает сообщения из очереди сообщений приложения и распределяет их функциям окон, вызывая функцию DispatchMessage.

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


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

Функция окна получает сообщения при создании окна, в процессе работы приложения, а также при разрушении окна.

Сообщение с кодом WM_CREATE передается функции окна в момент создания окна. Функция окна при обработке этого сообщения выполняет инициализирующие действия (аналогично конструктору класса в языке C++). Коды сообщений определены в файле windows.h, включаемом в исходные тексты любых приложений Windows.

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

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


Обработка сообщения WM_PAINT


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

Сообщение WM_PAINT передается функции окна, если стала видна область окна, скрытая раньше другими окнами, если вы изменили размер окна или выполнили операцию свертки (пролистывания) изображения в окне. Приложение может передать функции окна сообщение WM_PAINT явным образом, вызывая функции UpdateWindow, InvalidateRect или InvalidateRgn.

Иногда операционная система Windows может сама восстановить содержимое окна, не посылая сообщение WM_PAINT. Например, при перемещении курсора мыши или пиктограммы свернутого приложения Windows самостоятельно восстанавливает содержимое окна. Если же Windows не может восстановить окно, функция окна получает сообщение WM_PAINT и перерисовывает окно самостоятельно.

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

Что делать в том случае, когда по логике работы приложения требуется изменить содержимое окна не во время обработки сообщения WM_PAINT, а в любом другом месте приложения?

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


Как функция окна может определить область окна, подлежащую обновлению при обработке сообщения WM_PAINT?

Для ответа на этот вопрос вспомним о втором параметре функции BeginPaint, который является указателем на структуру PAINTSTRUCT:

HDC BeginPaint(HWND hwnd, PAINTSTRUCT FAR * lpps);

Структура PAINTSTRUCT определена в файле windows.h следующим образом:

typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[16]; } PAINTSTRUCT;

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

Начальные координаты и размер области, подлежащей обновлению в процессе обработки сообщения WM_PAINT, передаются через поле rcPaint. Это поле представляет собой структуру типа RECT, описывающую прямоугольную область:

typedef struct tagRECT { int left; int top; int right; int bottom; } RECT;

Поля left, top, right и bottom задают координаты области следующим образом:

Поле Описание
left x-координата верхнего левого угла области
top y-координата верхнего левого угла области
right x-координата правого нижнего угла области
bottom y-координата правого нижнего угла области
Координаты задаются в единицах измерения, называемых пикселами. Пикселы - это маленькие точки, из которых строится изображение. Приложение может определить размер любого своего окна или размер используемого шрифта при помощи специальных функций, которые мы рассмотрим позже.

Поле fErase структуры PAINTSTRUCT определяет необходимость стирания фона окна в области, подлежащей обновлению. Если это поле установлено в состояние TRUE, функция BeginPaint посылает функции окна сообщение WM_ERASEBKGND.

Как правило, сообщение WM_ERASEBKGND передается функции DefWindowProc, которая при получении этого сообщения перерисовывает фон соответствующей области окна (используя кисть, определенную при регистрации класса окна). Если поле fErase содержит значение FALSE, фон окна не изменяется.



Остальные поля структуры PAINTSTRUCT используются Windows, приложение не должно изменять их содержимое.

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

Рассмотрим некоторые функции, имеющие отношение к сообщению WM_PAINT.

Функция UpdateWindow имеет следующий прототип:

void UpdateWindow(HWND hwnd);

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

При помощи функции InvalidateRect вы можете объявить любую область окна как требующую обновления. Прототип функции:

void InvalidateRect(HWND hwnd, LPRECT lprc, BOOL fErase);

Первый параметр (hwnd) функции является идентификатором окна, для которого выполняется операция. Второй параметр (lprc) - дальний указатель на структуру типа RECT, определяющую прямоугольную область, подлежащую обновлению. Третий параметр (fErase) определяет необходимость стирания фона окна. Если этот параметр задан как TRUE, фон окна подлежит стиранию (см. поле fErase структуры PAINTSTRUCT).

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

void ValidateRect(HWND hwnd, LPRECT lprc);

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

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

Приложение должно выполнять вывод в окно "централизованно" в функции окна при получении сообщения WM_PAINT.

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

Используя специальные функции, приложение в любой момент времени может определить любую область окна как подлежащую (или не подлежащую) обновлению и послать самому себе в соответствующую функцию окна сообщение WM_PAINT.


Очереди сообщений


Иногда говорят, что работа операционной системы Windows основана на передаче сообщений (message). Что это за сообщения, кто, кому и зачем их передает?

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

Само по себе сообщение представляет собой структуру данных:

typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSGMSG;

Эта структура содержит уникальный для Windows код сообщения message и другие параметры, отражающие адресат (идентификатор получателя) сообщения hwnd, содержимое сообщения wParam и lParam, время отправления time и информацию о координатах pt.

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

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

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

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


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

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

Приложение анализирует очередь сообщений и выполняет обработку сообщений. Например, если вы нажали кнопку с надписью "Exit", приложение может завершить свою работу.

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

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

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


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

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

Программы MS-DOS в "классическом" исполнении работают по другому. Как правило, такие программы выполняются линейно, ожидая от пользователя ввода той или иной команды и блокируя нежелательные на данный момент действия.

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

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

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


Окна Windows


С точки зрения пользователя Windows окном является прямоугольная область экрана, в которой приложение может что-либо рисовать или писать, а также выполнять все операции взаимодействия с пользователем. Например, на рис. 1.8 показано главное окно приложения Media Player, которое в данном случае используется для проигрывания звукового компакт-диска.

Рис. 1.8. Главное окно приложения Media Player

С точки зрения программиста то, что изображено на рис. 1.8, является совокупностью большого количества отдельных объектов, которые созданы приложением Media Player и самой операционной системой Windows. Для каждого объекта в приложении имеются свои данные и методы. Все эти объекты обычно называются окнами.

Такие объекты, как пиктограмма системного меню, кнопка минимизации, отдельные фрагменты толстой рамки, предназначенной для изменения размера основного окна, заголовок окна с надписью "Media Player - CD Audio (stopped)", а также полоса меню, - не что иное, как различные окна, создаваемые самой операционной системой Windows. Приложение не принимает никакого участия в формировании этих окон, оно просто указывает Windows, какие из перечисленных выше элементов необходимо создать.

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

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

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

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

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



Окна Windows как объекты


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

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

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

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

Что это за действия?

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

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

Этот механизм основан на использовании так называемой функции окна (window function) и сообщений.



Окно и функция окна


В этом разделе вы узнаете, что операционная система Windows является объектно-ориентированной средой.

Как это следует из названия операционной системы, основным объектом в Windows является окно. И это действительно так. Однако давайте уточним, что понимается под окном.



Определение расположения окна


В предыдущем разделе мы научили вас определять размеры окна. Другая важная задача - определение расположения окна на экране видеомонитора.

Когда вы перемещаете окно (например, при помощи мыши), функция окна получает сообщение WM_MOVE. Вместе с этим сообщением функция окна получает новые координаты внутренней области окна:

Параметр Описание
wParam Не используется
LOWORD(lParam) Новая X-координата верхнего левого угла внутренней области окна
HIWORD(lParam) Новая Y-координата верхнего левого угла внутренней области окна

Для перекрывающихся (overlapped) и временных (pop-up) окон координаты отсчитываются от верхнего левого угла экрана. Для дочерних (child) окон эти координаты отсчитываются от верхнего левого угла внутренней области родительского окна.

В любой момент времени приложение может определить расположение и размеры окна, вызвав функцию GetWindowRect. Эта функция имеет следующий прототип:

void WINAPI GetWindowRect(HWND, RECT FAR*);

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

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

Для демонстрации использования сообщения WM_MOVE и функции GetWindowRect мы подготовили приложение WPOS. Это приложение по своей структуре аналогично приложению WSIZE, которое было рассмотрено в предыдущем разделе.

В файле wpos.cpp (листинг 4.12) расположена функция WinMain, создающая главное окно приложения и цикл обработки сообщений.

Листинг 4.12. Файл wpos\wpos.cpp

// ---------------------------------------- // Определение расположения окна // ----------------------------------------

#define STRICT #include <windows.h> #include <mem.h>

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);


char const szClassName[] = "WPOSAppClass"; char const szWindowTitle[] = "WPOSApplication";

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

if(!InitApp(hInstance)) return FALSE;

hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры if(!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна

memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;

aWndClass = RegisterClass(&wc); return (aWndClass != 0); }

Функция окна (листинг 4.13) обрабатывает сообщения WM_PAINT, WM_MOVE, WM_SIZE, WM_LBUTTONDOWN и, конечно, WM_DESTROY.

Листинг 4.13. Файл wpos\wndproc.cpp

// ===================================== // Функция WndProc // =====================================

#define STRICT #include <windows.h> #include <stdio.h>



LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования

// Координаты окна static WORD xPos; static WORD yPos;

switch (msg) { case WM_PAINT: { char buf[80];

hdc = BeginPaint(hwnd, &ps);

sprintf(buf, "%4.4dx%4.4d ", xPos, yPos);

// Выводим координаты окна TextOut(hdc, 0, 0, buf, 12);

EndPaint(hwnd, &ps); return 0; }

case WM_MOVE: { // X-координата левого верхнего угла // внутренней области окна xPos = LOWORD(lParam);

// Y-координата левого верхнего угла // внутренней области окна yPos = HIWORD(lParam);

// Отмечаем все окно как требующее // обновления InvalidateRect(hwnd, NULL, TRUE);

// Посылаем себе сообщение WM_PAINT UpdateWindow(hwnd); return 0; }

case WM_SIZE: case WM_LBUTTONDOWN: { RECT rc; char buf[80];

hdc = GetDC(hwnd);

// Определяем размеры прямоугольника, // ограничивающего окно GetWindowRect(hwnd, &rc);

sprintf(buf, "%4.4dx%4.4d %4.4dx%4.4d ", rc.top, rc.left, rc.right, rc.bottom);

// Выводим размеры внутренней области окна TextOut(hdc, 0, 20, buf, 25);

ReleaseDC(hwnd, hdc); return 0; }

case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

Сообщение WM_MOVE передается окну, когда оно отображается функцией ShowWindow, или при изменении размера окна. Наш обработчик сообщения WM_MOVE сохраняет экранные координаты внутренней области окна в переменных с именами xPos и yPos. После сохранения координат функция окна объявляет все окно как требующее обновления и посылает само себе сообщение WM_PAINT, для чего вызывает функцию UpdateWindow:

case WM_MOVE: { xPos = LOWORD(lParam); yPos = HIWORD(lParam); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); return 0; }

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



Для сообщений WM_SIZE и WM_LBUTTONDOWN используется общий обработчик. Он вызывает функцию GetWindowRect, с помощью которой определяет координаты и размер прямоугольной области, ограничивающей окно. Координаты левого верхнего угла и правого нижнего угла этой области выводятся во второй строке внутренней области окна (рис. 4.2).



Рис. 4.2. Главное окно приложения WPOS

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

Для создания приложения был использован файл определения модуля, приведенный в листинге 4.14.

Листинг 4.14. Файл wpos\wpos.def

; ============================= ; Файл определения модуля ; ============================= NAME WPOS DESCRIPTION 'Приложение WPOS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple

Программный интерфейс операционной системы Windows версии 3.1 содержит еще одну функцию, полезную при определении расположения на экране и размеров окна. Эта функция имеет имя GetWindowPlacement и следующий прототип:

BOOL WINAPI GetWindowPlacement (HWND hwnd, WINDOWPLACEMENT FAR* lpwndpl);

Первый параметр функции (hwnd) задает идентификатор окна, для которого нужно определить расположение и размеры.

Второй параметр (lpwndpl) является дальним указателем на структуру типа WINDOWPLACEMENT, определенную в файле windows.h:

typedef struct tagWINDOWPLACEMENT { UINT length; UINT flags; UINT showCmd; POINT ptMinPosition; POINT ptMaxPosition; RECT rcNormalPosition; } WINDOWPLACEMENT;

Поле length определяет размер структуры WINDOWPLACEMENT в байтах.

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

Поле showCmd после вызова функции GetWindowPlacement имеет значение SW_SHOWMAXIMIZED для максимизированного окна, SW_SHOWMINIMIZED для минимизированного окна и SW_SHOWNORMAL в остальных случаях.

Поле ptMinPosition определяет положение верхнего левого угла окна, когда оно минимизировано.

Поле ptMaxPosition определяет положение верхнего левого угла окна, когда оно максимизировано.

Поле rcNormalPosition определяет положение верхнего левого угла окна, размеры которого восстановлены до нормальных.

Для указания положения верхнего левого угла максимизированного и минимизированного окна используется структура POINT, определенная в файле windows.h:

typedef struct tagPOINT { int x; int y; } POINT;


Определение размера окна


Программы MS-DOS работали с экраном, который имел фиксированные размеры. В зависимости от видеорежима они использовали либо текстовый экран размером 80 х 25 символов, либо графический экран размером от 640х200 для видеоконтроллеров CGA, до 1024 х 768 или больше для SVGA.

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

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

Приложение WSIZE состоит из трех файлов. Первый файл с именем wsize.cpp (листинг 4.9) создает главное окно приложения и запускает цикл обработки сообщений. Файл wndproc.cpp (листинг 4.10) содержит исходный текст функции окна, выполняющий всю интересную для нас работу по определению и отображению текущих размеров главного окна приложения. И наконец, файл wsize.def (листинг 4.11) является файлом определения модуля для нашего приложения.

Листинг 4.9. Файл wsize\wsize.cpp

// ---------------------------------------- // Определение размеров окна // ----------------------------------------

#define STRICT #include <windows.h> #include <mem.h>

BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "WSIZEAppClass"; char const szWindowTitle[] = "WSIZE Application";


// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Выполняем инициализацию приложения if(!InitApp(hInstance)) return FALSE;

// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }

// ===================================== // Функция InitApp // Вызывается из функции WinMain для // инициализации приложения. // Выполняет регистрацию класса окна // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна

memset(&wc, 0, sizeof(wc)); wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;

aWndClass = RegisterClass(&wc); return (aWndClass != 0); }

Файл wsize.cpp не имеет никаких дополнительных особенностей по сравнению с аналогичными файлами предыдущих приложений.


Поэтому мы не будем его описывать, а перейдем сразу к функции окна (листинг 4.10).

Листинг 4.10. Файл wsize\wndproc.cpp

// ===================================== // Функция WndProc // Функция выполняет обработку сообщений // главного окна приложения // =====================================

#define STRICT #include <windows.h> #include <stdio.h>

LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; // индекс контекста устройства PAINTSTRUCT ps; // структура для рисования

static WPARAM fwSizeType; static WORD nWidth; static WORD nHeight;

switch (msg) { case WM_PAINT: { char buf[80];

hdc = BeginPaint(hwnd, &ps);

sprintf(buf, "%4.4dx%4.4d (%1.1d)", nWidth, nHeight, fwSizeType);

// Выводим размеры окна TextOut(hdc, 0, 0, buf, 13);

EndPaint(hwnd, &ps); return 0; }

case WM_SIZE: { // Способ изменения размера окна fwSizeType = wParam;

// Ширина внутренней области окна nWidth = LOWORD(lParam);

// Высота внутренней области окна nHeight = HIWORD(lParam);

// Отмечаем все окно как требующее // обновления InvalidateRect(hwnd, NULL, TRUE);

// Посылаем себе сообщение WM_PAINT UpdateWindow(hwnd); return 0; }

case WM_LBUTTONDOWN: { RECT rcWndSize; char buf[80];

hdc = GetDC(hwnd);

// Определяем размеры внутренней // области окна GetClientRect(hwnd, &rcWndSize);

sprintf(buf, "%4.4dx%4.4d", rcWndSize.right, rcWndSize.bottom);

// Выводим размеры внутренней области окна TextOut(hdc, 0, 20, buf, 9);

ReleaseDC(hwnd, hdc); return 0; }

case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }

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

static WPARAM fwSizeType; static WORD nWidth; static WORD nHeight;

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


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

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

Окно создается функцией CreateWindow. При этом функция окна получает следующие сообщения:

Код сообщения Описание сообщения
WM_GETMINMAXINFO При обработке этого сообщения приложение может изменить заданные по умолчанию размеры, расположение, а также минимальные и максимальные размеры окна, которые могут быть получены при изменении этих размеров с помощью мыши
WM_NCCREATE Сообщение оповещает функцию окна о начале процесса создания окна
WM_NCCALCSIZE При обработке этого сообщения приложение может определить размеры внутренней области окна (client area)
WM_CREATE Это сообщение оповещает приложение о том, что окно создано и что приложение может выполнять дополнительные действия по инициализации данных, связанных с окном
Обычно все эти сообщения (кроме последнего) передаются функции DefWindowProc. В некоторых случаях, когда с окном связаны структуры данных, требующие инициализации при создании окна, функция окна перехватывает сообщение WM_CREATE, определяя по нему момент завершения создания окна и выполняя собственные инициализирующие действия.

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

Код сообщения Описание сообщения
WM_SHOWWINDOW Сообщение оповещает функцию окна о том, что окно будет отображено или скрыто
WM_WINDOWPOSCHANGING Это сообщение посылается окну при изменении его размеров, расположения на экране или взаимного расположения вдоль оси Z (то есть когда окно перекрывается другими окнами или само перекрывает другие окна; воображаемая ось Z направлена перпендикулярно к плоскости экрана, окна могут перекрывать друг друга, при этом считается, что каждое окно имеет свою "Z-координату")
WM_ACTIVATEAPP Это сообщение посылается окну верхнего уровня (то есть главному окну приложения) и говорит о создании в Windows нового приложения (нового процесса)
WM_NCACTIVATE Сообщение посылается окну, которое должно перерисовать свою внешнюю область (non client area), включающую заголовок, рамку, кнопки изменения размера и т. п.)
WM_GETTEXT Копирование текста заголовка окна
WM_ACTIVATE Сообщение посылается окну, которое изменяет свое состояние из неактивного в активное или наоборот
WM_SETFOCUS Окно получает фокус ввода (все сообщения от клавиатуры направляются в это окно)
WM_NCPAINT Сообщение посылается окну, требующему перерисовки рамки. Приложение может перехватить это сообщение и нарисовать вокруг окна собственную рамку
WM_GETTEXT Копирование текста заголовка окна
WM_ERASEBKGND Сообщение посылается окну при стирании фона его внутренней области
WM_WINDOWPOSCHANGED Это сообщение посылается окну, изменившему свои размеры, расположение на экране или расположение вдоль оси Z
WM_SIZE Сообщение посылается окну после изменения размеров окна
WM_MOVE Сообщение посылается окну после его перемещения
<


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

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

Так как при отображении окна функцией ShowWindow функция окна получает в числе прочих сообщение WM_SIZE, и наше приложение WSIZE его обрабатывает, мы можем инициализировать переменные fwSizeType, nWidth и nHeight до прихода сообщения WM_PAINT, что мы и делаем в нашем обработчике сообщения WM_SIZE:

case WM_SIZE: { fwSizeType = wParam; nWidth = LOWORD(lParam); nHeight = HIWORD(lParam); InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd); return 0; }

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

В операционной системе Windows версии 3.1 тип WPARAM соответствует 16-разрядному слову, а тип LPARAM - 32-разрядному двойному слову. Однако не следует думать, что так будет и в других версиях Windows. Операционная система Windows NT, например, использует для типа WPARAM 32-разрядное слово.

Через параметр lParam передается два значения. В операционной системе Windows версии 3.1 эти значения соответствуют младшему и старшему слову lParam. Для обеспечения совместимости со следующими версиями Windows не следует самостоятельно извлекать эти два значения. Нужно пользоваться специально предназначенными для этого макросами LOWORD и HIWORD. Эти макросы определены в файле windows.h:

#define LOWORD(l) ((WORD)(l)) #define HIWORD(l) ((WORD)((DWORD)(l) >> 16))

Параметры сообщения WM_SIZE описывают новый размер окна и способ, которым окно изменило свой размер:



Параметр Описание
wParam Способ, при помощи которого окно изменило свой размер
LOWORD(lParam) Новая ширина окна
HIWORD(lParam) Новая высота окна
Параметр wParam может принимать одно из нескольких значений, символические имена которых начинаются с префикса SIZE_ и определены в файле windows.h следующим образом:

Параметр Значение Описание
SIZE_RESTORED 0 Окно изменило свои размеры, но оно не было максимизировано или минимизировано
SIZE_MINIMIZED 1 Размеры окна уменьшены до предела (окно минимизировано)
SIZE_MAXIMIZED 2 Размеры окна увеличены до предела (окно максимизировано)
SIZE_MAXSHOW 3 Сообщение WM_SIZE с этим значением парамера wParam посылается всем временным (pop-up) окнам, когда восстанавливается размер других окон
SIZE_MAXHIDE 4 Сообщение WM_SIZE с этим значением парамера wParam посылается всем временным (pop-up) окнам, когда размер других окон увеличивается до предела
После сохранения параметров сообщения WM_SIZE функция окна нашего приложения посылает сама себе сообщение WM_PAINT. Для этого при помощи функции InvalidateRect она объявляет все окно как требующее обновления и затем вызывает функцию UpdateWindow:

InvalidateRect(hwnd, NULL, TRUE); UpdateWindow(hwnd);

Функция InvalidateRect добавляет прямоугольную область, заданную в качестве параметра, к областям окна, требующим обновления (перерисовки). Эта функция имеет следующий прототип:

void WINAPI InvalidateRect(HWND, const RECT FAR*, BOOL);

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

Второй параметр - дальний указатель на структуру типа RECT, описывающую координаты области. Если в качестве этого параметра использовать значение NULL (как в нашем примере), вся внутренняя область окна (client area) будет объявлена как требующая обновления.

Третий параметр указывает, нужно ли при обновлении стирать фон окна. Если указать значение TRUE, фон окна будет стерт. Для того чтобы оставить фон окна без изменения, укажите в качестве третьего параметра значение FALSE.



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

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

case WM_LBUTTONDOWN: { RECT rcWndSize; char buf[80];

hdc = GetDC(hwnd); GetClientRect(hwnd, &rcWndSize); sprintf(buf, "%4.4dx%4.4d", rcWndSize.right, rcWndSize.bottom); TextOut(hdc, 0, 20, buf, 9); ReleaseDC(hwnd, hdc); return 0; }

Функция GetClientRect предназначена для определения координат внутренней области окна и имеет следующий прототип:

void WINAPI GetClientRect(HWND hwnd, RECT FAR* lprc);

Первый параметр функции (hwnd) определяет идентификатор окна, для которого требуется определить координаты внутренней области.

Второй параметр (lprc) является дальним указателем на структуру типа RECT, в которую записываются координаты внутренней области окна. Эти координаты вычисляются относительно левого верхнего угла внутренней области окна, поэтому в полях left и top структуры RECT всегда записываются нулевые значения. Поля right и bottom содержат соответственно ширину и высоту внутренней области окна.

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



Рис. 4.1. Главное окно приложения WSIZE

Из рисунка видно, что оба способа определения размера окна (при обработке сообщения WM_SIZE и при помощи функции GetClientRect) дали одинаковые результаты.

Файл определения модуля, который был использован при создании приложения WSIZE, не имеет никаких особенностей и приведен в листинге 4.11.

Листинг 4.11. Файл wsize\wsize.def

; ============================= ; Файл определения модуля ; ============================= NAME WSIZE DESCRIPTION 'Приложение WSIZE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple


Определение возможностей устройств ввода/вывода


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

Функция GetDeviceCaps имеет следующий прототип:

int WINAPI GetDeviceCaps(HDC hdc, int iCapability);

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

Второй параметр (iCapability) определяет параметр устройства, значение которого необходимо получить.

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

Имя константы Описание
ASPECTX Относительная ширина отдельного пиксела, который используется при рисовании линий
ASPECTXY Относительная длина диагонали отдельного пиксела, который используется при рисовании линий
ASPECTY Относительная высота отдельного пиксела, который используется при рисовании линий
BITSPIXEL Количество бит, используемых для представления цвета в одном пикселе
CLIPCAPS Возможности устройства по ограничению области вывода:

CP_NONE вывод не ограничивается;

CP_RECTANGLE вывод ограничивается прямоугольной областью;CP_REGION вывод ограничивается произвольной областью

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

Данную константу можно использовать только для драйверов версии 3.0 и более поздних версий

CURVECAPS Способность устройства рисовать различные кривые линии и геометрические фигуры. Возвращаемое значение представляет собой набор битовых масок, установленных в 1, если устройство может само рисовать различные фигуры:

CC_CIRCLES окружности;

CC_CHORD сегмент эллипса;

CC_ELLIPSES эллипсы;

CC_INTERIORS устройство может закрашивать внутреннюю область геометрических фигур;

CC_NONE устройство не может рисовать кривые линии и геометрические фигуры;

CC_PIE секторы эллипса;

CC_ROUNDRECT прямоугольники со скругленными углами;

CC_STYLED устройство может рисовать рамки с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т.д.);

CC_WIDE широкие рамки;

CC_WIDESTYLED устройство может рисовать широкие рамки с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т. д.)

DRIVERVERSION Номер версии драйвера устройства. Значение 0x300 соответствует версии 3.0, значение 0x30a - версии 3.1
HORZRES Ширина экрана в пикселах. Для принтеров - ширина рабочей области, в пределах которой может выполняться печать
HORZSIZE Стандартная для данного разрешения ширина дисплея в миллиметрах
LINECAPS Способности устройства рисовать линии. Возвращаемое значение представляет собой набор битовых масок, установленных в 1, если устройство может само рисовать линии различного типа:

LC_INTERIORS устройство может закрашивать внутреннюю область;

LC_MARKER маркеры;

LC_NONE устройство не может рисовать линии;

LC_POLYLINE ломаные линии;

LC_POLYMARKER линии polymarker;

LC_STYLED устройство может рисовать линии с использованием различных стилей (штриховые, пунктирные, штрих пунктирные и т.д.);

LC_WIDE широкие линии;

LC_WIDESTILED устройство может рисовать широкие линии с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т. д.)

LOGPIXELSX Количество пикселов на один логический дюйм по горизонтали
LOGPIXELSY Количество пикселов на один логический дюйм по вертикали
NUMBRUSHES Количество кистей, поддерживаемых устройством
NUMCOLORS Количество цветов, зарезервированных Windows для использования в цветовых палитрах устройства, то есть количество чистых цветов, которые может использовать устройство. Для драйверов монохромных устройств возвращается значение 2. Для плоттеров это значение соответствует количеству цветных перьев
NUMFONTS Количество шрифтов, поддерживаемых устройством
NUMMARKERS Количество маркеров, поддерживаемых устройством
NUMPENS Количество перьев, поддерживаемых устройством
NUMRESERVED Количество зарезервированных элементов в системной палитре. Это значение определено только для устройств, использующих цветовые палитры, что можно выяснить при помощи константы RASTERCAPS.

Данную константу можно использовать только для драйверов версии 3.0 и более поздних версий

PDEVICESIZE Размер внутренней структуры данных PDEVICE
PLANES Количество цветовых слоев
POLYGONALCAPS Способности устройства рисовать многоугольники. Возвращаемое значение представляет собой набор битовых масок, установленных в 1, если устройство может само рисовать многоугольники различного типа:

PC_INTERIORS устройство может закрашивать внутреннюю область;

PC_NONE устройство не может рисовать многоугольники;

PC_RECTANGLE прямоугольники;

PC_SCANLINES устройство может выполнять сканирование линий растра;

PC_STYLED устройство может рисовать рамки с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т. д.);

PC_WIDE широкие рамки;

PC_WIDESTILED устройство может рисовать широкие рамки с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т. д.)

PC_WINDPOLYGON многоугольники с заполнением в режиме WINDING

RASTERCAPS Набор битовых масок, определяющих способность устройства выполнять растровые операции:

RC_BANDING для устройства требуется поддержка операции banding - функции GDI должны выводить данные небольшими сегментами, формирующими изображение (используется устройствами печати);

RC_BIGFONT устройство поддерживает шрифты, размером большем чем 64 Кбайт;

RC_BITBLT устройство может выполнять перемещение участков изображения в виде битовых образов (bitmap);

RC_BITMAP64 устройство может работать с битовыми образами большого размера (больше 64 Кбайт);

RC_DEVBITS есть поддержка битовых образов со стороны устройства;

RC_DI_BITMAP устройство поддерживает выполнение функций SetDIBits и GetDIBits;

RC_DIBTODEV устройство поддерживает выполнение функции SetDIBitsToDevice;

RC_FLOODFILL устройство может выполнять заливку фигур;

RC_GDI20_OUTPUT драйвер устройства поддерживает особенности Windows версии 2.0;

RC_GDI20_STATE контекст устройства содержит блок состояния устройства;

RC_NONE устройство не выполняет растровых операций;

RC_OP_DX_OUTPUT устройство поддерживает режим непрозрачности и массив DX;

RC_PALETTE устройство использует палитры цветов;

RC_SAVEBITMAP устройство может локально сохранять битовые образы (bitmap);

RC_SCALING поддерживается операция масштабирования;

RC_STRETCHBLT устройство поддерживает функцию StretchBlt;

RC_STRETCHDIB устройство поддерживает функцию StretchDIBits

SIZEPALETTE Размер таблицы палитры. Это значение можно использовать только для устройств, использующих цветовые палитры, что можно определить при помощи константы RASTERCAPS
TECHNOLOGY Тип устройства или технология, с использованием которой сделано устройство:

DT_CHARSTREAM устройство работает с потоком символов;

DT_DISPFILE файл отображения;

DT_METAFILE метафайл;

DT_PLOTTER векторный плоттер;

DT_RASDISPLAY растровый дисплей;

DT_RASPRINTER растровый принтер;

DT_RASCAMERA растровая камера

TEXTCAPS Набор битовых масок, определяющих способность устройства выполнять операции с текстом:

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

TC_CP_STROKE точность вывода допускает обрезание символов шрифта для того, чтобы текст появился только внутри заданной области ограничения;

TC_CR_90 устройство может поворачивать символы только на угол, кратный 90 градусам;

TC_CR_ANY устройство может поворачивать символы на любой угол;

TC_SF_X_YINDEP устройство может масштабировать свой шрифт по вертикальной и горизонтальной оси;

TC_SA_DOUBLE устройство может удваивать размер своего шрифта;

TC_SA_INTEGER устройство может увеличивать размер своего шрифта в любое целое количество раз;

TC_SA_CONTIN устройство может выполнять произвольное масштабирование своего шрифта, сохраняя отношение между вертикальным и горизонтальным размером шрифта;

TC_EA_DOUBLE устройство может увеличивать жирность своего шрифта в два раза;

TC_IA_ABLE устройство может делать свой шрифт наклонным (italic);

TC_UA_ABLE устройство может делать свой шрифт подчеркнутым;

TC_SO_ABLE устройство может делать свой шрифт перечеркнутым;

TC_RA_ABLE устройство способно перечислять растровые шрифты или шрифты TrueType при вызове функций EnumFonts или EnumFontFamilies;

TC_VA_ABLE устройство способно перечислять векторные шрифтов при вызове функций EnumFonts или EnumFontFamilies;

TC_RESERVED не используется

VERTRES Высота дисплея в пикселах. Для принтеров - высота рабочей области, в которой принтер способен выполнять печать
VERTSIZE Стандартная высота дисплея в миллиметрах
<
Не все перечисленные выше значения будут сразу вам нужны, поэтому мы остановимся только на некоторых из них.

Значения ASPECTX, ASPECTY, ASPECTXY определяют размеры пиксела. Зачем вам могут понадобиться размеры пиксела? Дело в том, что пикселы не всегда квадратные (или круглые). Поэтому в некоторых режимах работы видеоадаптера масштаб изображения по оси x может отличаться от масштаба по оси y. Размеры пиксела позволят вам вычислить отношение сторон пиксела и выполнить правильное масштабирование. В этом случае отображаемые вами окружности будут круглыми, а квадраты - квадратными.

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

Некоторые устройства работают с цветовыми плоскостями. Количество этих плоскостей можно определить, пользуясь значением PLANES. Об использовании цветовых плоскостей можно прочитать в третьем томе "Библиотеки системного программиста", который называется "Программирование видеоадаптеров CGA, EGA и VGA". Отметим, что количество цветов, которые могут быть представлены устройством с цветовыми плоскостями, равно 2n, где n - количество цветовых плоскостей.

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

nColors = 2(nPixel * nPlanes),

где nPixel - количество битов, используемых для представления цвета пиксела (значение BITSPIXEL); nPlanes - количество цветовых плоскостей (значение PLANES).

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


Определения типов, констант и функций


Первые две строки используются для определения типов данных и констант:

#define STRICT #include <windows.h>

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

Далее в программе приведены прототипы двух функций - InitApp и WndProc.

После прототипов определены две строки символов, содержащие имя класса окна (szClassName) и заголовок окна (szWindowTitle). Имя класса окна используется при создании класса окна, а заголовок нужен, разумеется, для того, чтобы озаглавить создаваемое главное окно приложения. Так как мы в приложении не собираемся изменять эти строки, они описаны как const.

Функция WinMain определена так же, как и в нашем самом первом приложении (листинг 1.1.). В области стека функции созданы две переменные с именами msg и hwnd:

MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

Переменная msg представляет собой структуру типа MSG, описанную в файле windows.h следующим образом:

typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG;

Эта переменная предназначена для временного хранения сообщений и используется в цикле обработки сообщений.

Переменная hwnd имеет тип HWND, также описанный в файле windows.h, и используется для хранения идентификатора (handle) главного окна приложения. Заметьте, что в структуре MSG также присутствует поле с типом HWND. Это поле используется для хранения идентификатора окна, к которому относится сообщение.

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

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



Основы Microsoft Windows


1.1.

1.2.

1.3.

1.4.

1.5.

1.6.

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

Операционная система Microsoft Windows представляет собой однопользовательскую многозадачную операционную систему с графическим интерфейсом, в отличие от MS-DOS, которая является однопользовательской однозадачной операционной системой с текстовым интерфейсом. Многозадачность и графический интерфейс оказывают значительное влияние на структуру и принципы работы программ, созданных специально для Windows. И если программа, работающая под управлением MS-DOS, могла "единолично" использовать все аппаратные ресурсы компьютера (видеомонитор, клавиатуру, мышь, диски и т. д.), то программа для Windows должна разделять эти и другие ресурсы с остальными, работающими параллельно, программами.

Вы, наверное, знаете, что операционная система Microsoft Windows версии 3.1 может работать в двух режимах - стандартном и расширенном. Если в компьютере установлен процессор 80386 и имеется по крайней мере 2 Мбайт оперативной памяти, по умолчанию Windows запускается в расширенном режиме. Это основной режим работы, который обеспечивает использование всех возможностей Windows.

Стандартный режим работы Windows используется при работе на процессоре 80286. Вы можете запустить Windows в стандартном режиме и на процессоре 80386, если для запуска введете команду "win /s".