1.1. Компоненты и подсистемы Windows
1.2. Простейшее приложение Windows
1.3. Окно и функция окна
1.4. Обработка сообщений
1.5. Приложение с обработкой сообщений
1.6. О сложности программирования для MS-DOS и Windows
В этой главе мы рассмотрим структуру операционной системы 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".
Как и любая другая операционная система, Microsoft Windows содержит
в себе ядро, подсистему управления оперативной памятью, подсистему
управления программами, файловую систему, драйверы для работы
с устройствами ввода/вывода и другие системы.
Рассмотрим кратко основные особенности отдельных подсистем Microsoft
Windows.
Файловая система
Версия 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 символов).
Управление программами
Подсистема управления программами в Windows обеспечивает запуск
и одновременную работу нескольких программ. Программы, созданные
специально для Windows, называются приложениями Windows (Windows
application). В среде операционной системы Windows версии 3.1
одновременно может быть запущено несколько приложений Windows
и несколько программ, созданных для MS-DOS.
Если Windows работает в расширенном режиме, для каждой программы
MS-DOS при запуске создается отдельная виртуальная машина. При
выполнении программы MS-DOS процессор работает в режиме виртуального
процессора 8086, поэтому все работающие параллельно на разных
виртуальных машинах программы MS-DOS изолированы друг от друга.
Если Windows работает в стандартном режиме, запуск программы MS-DOS
приводит к приостановке выполнения приложений Windows и выгрузке
содержимого оперативной памяти на диск. Процессор переходит в
реальный режим работы. Соответствующей настройкой системы запуска
программ MS-DOS можно добиться этого и в расширенном режиме работы
Windows.
Для всех приложений Windows в расширенном режиме работы создается
одна виртуальная машина, причем процессор работает в защищенном
режиме. Все приложения Windows используют для адресации памяти
одну локальную таблицу дескрипторов LDT, что может привести к
взаимному влиянию приложений друг на друга. В этом смысле программы
MS-DOS, работающие в среде Windows, лучше защищены друг от друга,
чем приложения Windows, так как адресные пространства виртуальных
машин MS-DOS изолированы друг от друга (за исключением начала
адресного пространства, занятого резидентными программами и драйверами,
загруженными до запуска Windows).
Подсистема управления программами не обеспечивает квантования
времени между приложениями Windows, но делает это для запущенных
одновременно программ MS-DOS. Приложения Windows сделаны таким
образом, что они сами "добровольно" отдают друг другу
процессорное время, обеспечивая так называемую невытесняющую мультизадачность
(nonpreemptive multitasking).
Операционная система Windows NT может выполнять обычные программы
MS-DOS, приложения Windows версии 3.1 и новые, 32-разрядные приложения,
созданные специально для Windows NT. Для каждого 32-разрядного
приложения Windows NT создает отдельную виртуальную машину. Благодаря
этому приложения Windows NT изолированы друг от друга. Использование
отдельных виртуальных машин позволяет реализовать для 32-разрядных
приложений Windows NT вытесняющую мультизадачность (preemptive
multitasking) с выделением каждому приложению квантов времени.
Управление оперативной памятью
Если вы помните, подсистема управления оперативной памятью в MS-DOS
базируется на использовании блоков управления памятью MCB (см.
первый том "Библиотеки системного программиста"). Такое
"управление" памятью полностью основано на джентльменском
соглашении между программами о сохранении целостности операционной
системы, так как любая программа может выполнить запись данных
по любому адресу. Программа может легко разрушить системные области
MS-DOS или векторную таблицу прерываний.
Приложение Windows выполняется в защищенном режиме, поэтому оно
не может адресоваться к любым областям памяти. Это сильно повышает
надежность операционной системы в целом - мультизадачная система
не должна завершаться аварийно при аварийном завершении одного
из приложений.
Пусть вас не смущает, что вы не сможете изменить векторную таблицу
прерываний. Во-первых, в защищенном режиме используется не векторная
таблица прерываний, а дескрипторная таблица прерываний. Во-вторых,
при создании обычных приложений у вас никогда не возникнет необходимость
в изменении этой таблицы. В-третьих, если вам и в самом деле нужно
изменить дескрипторную таблицу прерываний, мы расскажем, как это
можно сделать, не прибегая к непосредственной записи новых значений
селекторов в оперативную память, отведенную для таблицы.
Использование защищенного режима работы процессора обеспечивает
приложениям Windows непосредственный доступ к расширенной памяти
компьютера. С помощью системы управления памятью приложение может
заказать для себя буфер очень большого размера. Физически этот
буфер может находиться либо в расширенной памяти, либо в виртуальной.
Можно также заказать небольшой буфер в стандартной памяти (ниже
границы 1 Мбайт).
Виртуальная память располагается на жестком диске компьютера.
При первоначальной установке Windows вы должны определить расположение
и размер файла, который будет использоваться для виртуальной памяти
(только при работе Windows в расширенном режиме). При необходимости
Windows выполняет чтение в оперативную память отдельных страниц
виртуальной памяти или запись страниц из оперативной памяти на
диск. Все это создает иллюзию наличия оперативной памяти очень
большого размера. В архитектуру процессора 80386 и 80486 заложена
поддержка виртуальной памяти, так что процесс виртуализации выполняется
достаточно эффективно.
Другая особенность системы управления памятью в операционной системе
Windows связана с управлением сегментами памяти, выделенными приложению.
Как вы знаете, программа MS-DOS в зависимости от используемой
модели памяти может состоять из одного или нескольких сегментов
кода, а также из одного или нескольких сегментов данных. При загрузке
программы MS-DOS все нужные сегменты загружаются в первый мегабайт
оперативной памяти, после чего управление передается в точку входа,
расположенную в сегменте кода.
Приложение Windows устроено сложнее и загружается по-другому.
Как и программы MS-DOS, приложения Windows состоят из сегментов
кода и сегментов данных. В зависимости от модели памяти приложение
может иметь один или несколько сегментов кода и один или несколько
сегментов данных.
Сегменты приложения Windows получают дополнительный атрибут -
тип сегмента. Существуют сегменты с фиксированным расположением
в оперативной памяти (fixed), перемещаемые (moveable) и удаляемые
(discardable). В операционной системе MS-DOS нет аналога перемещаемым
и сбрасываемым сегментам, так как при загрузке все сегменты располагаются
по фиксированным (на время работы программы) адресам. Перемещаемые
сегменты могут менять свое расположение в адресном пространстве.
Управляет этим, незаметным для приложений, процессом операционная
система Windows.
Для чего понадобились перемещаемые сегменты?
Вспомним, что Windows - многозадачная операционная система. Поэтому
такой ресурс, как оперативная память, используется совместно всеми
работающими параллельно приложениями или различными копиями одного
и того же приложения, запущенного несколько раз. В процессе работы
вы запускаете и завершаете различные приложения, что приводит
к фрагментации непрерывного адресного пространства. Используя
механизм перемещения сегментов, основанный на использовании схемы
адресации процессора 80286, операционная система Windows по мере
необходимости "уплотняет" оперативную память, высвобождая
непрерывное адресное пространство для запуска новых приложений.
Удаляемые (discardable) сегменты обычно используются для хранения
выполняемых сегментов или сегментов констант. Если операционной
системе Windows требуется получить в свое распоряжение область
памяти, она может уничтожить удаляемый сегмент и забрать распределенную
для него память. Если впоследствии потребуется восстановить содержимое
удаляемого сегмента, Windows выполняет чтение данных сегмента
из соответствующего файла.
Помимо описанных выше атрибутов сегменты могут иметь еще два.
Можно создать сегменты, загружаемые при запуске приложения (preload)
и загружаемые при обращении к ним (loadoncall). Сегменты типа
loadoncall не загромождают оперативную память, так как после запуска
приложения они остаются на диске и загружаются в память только
при необходимости. Причем при составлении программы вам достаточно
описать сегмент как loadoncall, после чего Windows будет сама
его загружать при обращении к сегменту со стороны приложения.
Механизм управления сегментами достаточно сложен, и пока еще не
настало время для его детального рассмотрения. Однако уже сейчас
видно, что требование обеспечения разделения адресных пространств
приложений и обеспечения мультизадачности привело к значительному
усложнению системы управления памятью по сравнению с используемой
в MS-DOS.
Драйверы устройств ввода/вывода
Для работы с устройствами ввода/вывода Windows содержит комплект
драйверов. Основное требование к этим драйверам заключается в
способности работать в мультизадачном режиме, обеспечивая совместное
использование устройств ввода/вывода всеми одновременно работающими
приложениями.
Необходимо отметить, что задача создания собственного драйвера
для Windows значительно сложнее задачи разработки драйвера для
MS-DOS. Драйверы Windows - тема для отдельной книги (возможно,
не одной). Однако для стандартных устройств, таких, как принтер,
мышь, порт последовательной передачи данных и т. п. драйверы уже
имеются и поставляются в составе Windows либо в комплекте с устройствами
ввода/вывода.
Если же вам необходимо создать драйвер собственного нестандартного
устройства, придется приобрести такой программный продукт, как
Driver Development Kit (DDK), поставляемый фирмой Microsoft. В
состав DDK входит вся необходимая документация, средства разработки
и готовые примеры драйверов.
Библиотеки динамической загрузки 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 вы можете забыть о программных прерываниях
вообще и о переназначении аппаратных и программных прерываний
в частности.
Интерфейс графических устройств GDI
Так как Windows является операционной системой с графическим интерфейсом,
одно из важнейших мест в Windows занимает система графического
ввода/вывода.
В Windows реализована концепция графического интерфейса, независимого
от аппаратной реализации используемого устройства ввода/вывода.
Этот интерфейс называется GDI (Graphics Device Interface). В рамках
этого интерфейса определены все функции для работы с графикой.
Независимость от аппаратной реализации позволяет вам использовать
одни и те же функции для рисования графических объектов (таких,
как линии, окружности, прямоугольники и т. д.) как на экране видеомонитора,
так и на бумаге, вставленной в матричный или лазерный принтер,
в плоттер или другое устройство графического вывода, которое имеет
драйвер для Windows.
Очереди сообщений
Иногда говорят, что работа операционной системы 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 обеспечивает обработку ситуаций,
при которых одно окно перекрывает другое. Приложения не следят
за тем, чтобы их окна не перекрывались окнами других приложений.
Но они все должны быть способны обработать специальное сообщение,
по которому необходимо перерисовать содержимое всего своего окна
или части окна.
Управление шрифтами
Операционная система MS-DOS не содержит никакой серьезной поддержки
для работы со шрифтами. В то же время Windows версии 3.1 использует
передовую технологию масштабируемых шрифтов TrueType. Из руководства
пользователя Windows вы знаете, что шрифты TrueType сохраняют
свой внешний вид при изменении высоты букв. Поэтому они и называются
масштабируемыми.
Любое приложение Windows может использовать шрифты, зарегистрированные
(или, иными словами, установленные) в операционной системе Windows.
Для работы со шрифтами Windows предоставляет приложениям богатый
набор функций, позволяющий выполнять все необходимые операции.
Система управления шрифтами, встроенная в Windows, позволяет реализовать
на практике режим редактирования документов, который носит название
WYSIWYG - What You See Is What You Get (что вы видите, то и получите).
Например, если вы используете для редактирования документов такой
текстовый процессор, как Microsoft Word for Windows, вы можете
выбрать для оформления любой из масштабируемых шрифтов. Система
управления шрифтами обеспечит соответствие изображения текста
на экране распечатке, полученной на принтере (особенно хорошие
результаты получатся при использовании видеомонитора с высоким
разрешением, а также струйного или лазерного принтера).
Ресурсы
Как мы говорили, приложения Windows используют новый формат файла
загрузочного модуля. Одно из новшеств заключается в том, что файл
загрузочного модуля кроме кода и обычных данных содержит так называемые
ресурсы. В качестве ресурсов используются текстовые строки, пиктограммы,
графические изображения, меню, диалоговые панели, шрифты или любые
произвольные данные.
Для создания ресурсов используются специальные программы, которые
называются редакторами ресурсов. Такие программы поставляются
в комплекте с компилятором. Они позволяют редактировать ресурсы
без изменения кода, и в этом смысле можно говорить об относительной
независимости ресурсов от программного кода приложения.
Приложение Windows при необходимости может загрузить ресурс в
оперативную память и использовать его, например, для вывода на
экран.
В качестве ресурса удобно оформить, например, все текстовые сообщения
или меню. В этом случае вы сможете редактировать текст сообщений
или строк меню без повторной сборки всего приложения. Это удобно,
если вам нужно перевести все сообщения или меню на другой язык
(например, с русского на английский или наоборот).
Включение в ресурсы пиктограмм и других графических изображений
позволит вам редактировать их отдельно, без повторной сборки приложения.
Ресурсы могут загружаться в оперативную память сразу после запуска
приложения или при необходимости. В последнем случае после запуска
приложения ресурсы хранятся на диске. Если ресурсы не используются,
то они не занимают места в оперативной памяти.
Динамический обмен данными DDE
Любая мультизадачная операционная система содержит систему, обеспечивающую
взаимодействие работающих параллельно процессов. Операционная
система Windows имеет механизм динамического обмена данными DDE,
с помощью которого приложения Windows могут обмениваться различной
информацией, такой, как текстовые строки, числа или блоки оперативной
памяти.
Сетевая операционная система Microsoft Windows for Workgroups
использует DDE для организации взаимодействия и передачи данных
между приложениями, работающими в сети на разных рабочих станциях.
Вставка и привязка объектов OLE
Операционная система Windows содержит сложный механизм вставки
и привязки объектов OLE (Object Linking and Embedding), обеспечивающий
интеграцию приложений на уровне объектов, таких, как документы,
графические изображения или электронные таблицы.
При использовании OLE документ, подготовленный, например, текстовым
процессором Microsoft Word for Windows, может содержать в себе
как объект изображение, созданное графическим редактором Paint
Brush. Для редактирования такого объекта из среды текстового процессора
Microsoft Word for Windows вызывается приложение Paint Brush,
причем результат редактирования записывается обратно в тело документа.
Другие компоненты и подсистемы
Кроме перечисленных выше, операционная система Windows содержит
другие компоненты и подсистемы. Некоторые из них входят в комплект
Windows версии 3.1, некоторые нужно приобретать отдельно.
В последнее время получают все более широкое распространение системы
мультимедиа, обеспечивающие звуковой интерфейс между человеком
и компьютером. Самые лучшие из таких систем способны (при наличии
соответствующей аппаратуры) работать не только со звуком, но и
с видеосигналом, поступающим с телевизионной камеры или видеомагнитофона.
С точки зрения программиста системы мультимедиа реализованы в
виде dll-библиотек, содержащих наборы функций для работы с устройствами
ввода/вывода звука и изображения.
Другая подсистема, устанавливаемая дополнительно, имеет отношение
к управлению памятью и называется Win32s. Это подмножество 32-разрядного
программного интерфейса операционной системы Windows NT, использующее
сплошную несегментированную модель памяти. В этой модели памяти
приложения обычно никогда не изменяют содержимого сегментных регистров
процессора, так как они могут непосредственно адресовать огромные
объемы виртуальной оперативной памяти.
Эта подсистема имеет и другие интересные возможности, например
отображение файлов на оперативную память, что значительно ускоряет
и упрощает работу с ними.
Система Win32s поставляется фирмой Microsoft отдельно вместе с
соответствующими средствами разработки, а также входит в комплект
поставки трансляторов Borland C++ версии 4.0 и Symantec C++ версии
6.0.
В этом разделе мы создадим простейшее приложение Windows. Оно
будет мало напоминать "настоящие" приложения, которые
поставляются вместе с Windows, но на данном этапе наша основная
задача - научиться создавать файл загрузочного модуля приложения
Windows с использованием системы разработки Borland C++ версии
3.1.
Функция 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.
Программа "Hello, world!" для Windows
Давайте попробуем создать для Windows вариант известной всем программы,
приведенной в книге Кернигана и Риччи, посвященной программированию
на языке C:
main()
{
printf("Hello, world!");
}
Задачей этой программы, как следует из исходного текста, является
вывод на экран строки "Hello, world!".
Как мы уже говорили, вместо функции main приложение Windows использует
функцию WinMain, причем необходимо указать все четыре параметра
этой функции.
К сожалению, вы не сможете воспользоваться функцией printf, так
как ни эта, ни другие аналогичные функции консольного ввода/вывода
в обычных приложениях Windows использовать нельзя. Для вывода
текстовой строки "Hello, world!" мы воспользуемся функцией
из программного интерфейса Windows с именем MessageBox.
Создайте на диске каталог с именем hello и скопируйте в него файлы
hello.cpp и hello.prj из одноименного каталога, расположенного
на дискете, которую вы купили вместе с книгой. Если вы приобрели
книгу без дискеты, воспользуйтесь исходным текстом программы,
приведенным в листинге 1.1.
Листинг 1.1. Файл hello\hello.cpp
// ----------------------------------------
// Простейшее приложение Windows
// "Hello, world!"
// ----------------------------------------
#define STRICT
#include <windows.h>
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow)
{
MessageBox(NULL, "Hello, world!", "Main Window", MB_OK);
return 0;
}
Первой строкой в программе является определение символа STRICT:
#define STRICT
Это определение влияет на обработку файла windows.h, обеспечивая
более строгую проверку типов данных. Такая проверка облегчит вам
в дальнейшем преобразование исходных текстов программ для 32-разрядных
приложений Win32s или Windows NT. И хотя в нашем простом примере
проверять почти нечего, мы включили определение STRICT для сохранения
единого стиля во всех примерах программ.
Следующая строка сообщает компилятору, что не нужно выводить предупреждающее
сообщение о том, что расположенная следом функция не пользуется
своими параметрами:
#pragma argsused
В нашем первом примере мы игнорировали все четыре параметра, однако
из-за использования соглашения о передаче параметров PASCAL все
параметры функции WinMain должны быть описаны.
Для вывода строки "Hello, world!" мы использовали функцию
MessageBox:
MessageBox(NULL, "Hello, world!", "Main Window", MB_OK);
Прототип функции MessageBox определен в файле windows.h:
int WINAPI MessageBox(HWND, LPCSTR, LPCSTR, UINT);
Мы в нашей книге будем использовать немного измененную форму прототипа,
отражающую не только типы параметров, но и назначение параметров.
Прототип функции MessageBox будет выглядеть следующим образом:
int WINAPI MessageBox(HWND hwndParent, LPCSTR lpszText,
LPCSTR lpszTitle, UINT fuStyle);
Не вдаваясь в подробности, скажем, что эта функция создает на
экране диалоговую панель с текстом, заданным вторым параметром
lpszText (в нашем случае - с текстом "Hello, world!"),
и заголовком, заданным третьим параметром lpszTitle ("Main
Window").
Параметр hwndParent указывает так называемый идентификатор родительского
окна, создающего диалоговую панель (его мы рассмотрим позже).
Этот параметр можно указывать как NULL, в этом случае у диалоговой
панели не будет родительского окна.
Первый параметр в нашем примере необходимо указать как NULL.
Последний параметр fuStyle - константа MB_OK, значение которой
определено в файле windows.h. Использование в качестве последнего
параметра значения MB_OK приводит к появлению в диалоговой панели
одной кнопки с надписью "OK". Когда вы нажмете на эту
кнопку, функция MessageBox возвратит управление в функцию WinMain.
Обратите внимание на то, что имена параметров функции MessageBox
имеют префиксы. Эти префиксы используются для того, чтобы включить
в имя параметра информацию о его типе. При создании приложений
Windows приходится использовать очень много типов данных. Префиксы
позволяют избежать ошибок, связанных с неправильным использованием
параметров. Подробнее о префиксах в именах параметров и переменных
вы можете узнать из приложения с названием "Имена параметров
функций".
Для завершения работы приложение использует функцию return:
return 0;
Значение, возвращаемое этой функцией, не используется Windows,
однако может быть проанализировано отладчиком.
Теперь займемся созданием приложения, для чего воспользуемся интегрированной
средой разработки Borland C++ for Windows версии 3.1 или Borland
Turbo C++ for Windows.
Для создания приложения прежде всего вам нужно образовать новый
prj-файл. Запустите среду разработки и из меню "Project"
выберите строку "Open Project...". На экране появится
диалоговая панель "Open Project File" (рис. 1.1).
Рис. 1.1. Диалоговая панель "Open Project File"
С помощью меню "Directories" выберите и сделайте текущим
созданный вами каталог с именем hello. Если в этом каталоге уже
имеется файл hello.prj, выберите его и нажмите кнопку "OK".
Если файла нет (вы не купили дискету с примерами программ), наберите
в поле "File Name" имя hello.prj и нажмите кнопку "OK".
В этом случае будет создан новый файл проекта.
При создании нового проекта в нижней части основного окна Borland
C++ появится окно "Project:hello" (рис. 1.2).
Рис. 1.2. Окно "Project: hello"
В этом окне отображается список файлов, входящих в проект hello.prj.
При создании нового проекта этот список пуст. Нам надо добавить
в проект файл с именем hello.cpp. Для добавления файла нажмите
клавишу <Insert>. На экране появится диалоговая панель "Add
To Project List" (рис. 1.3), с помощью которой можно добавить
к проекту файл с программой, объектным модулем, библиотекой объектных
модулей и т. п.
Рис. 1.3. Диалоговая панель "Add To Project List"
Выберите при помощи меню или наберите в поле "File Name"
имя hello.cpp, затем нажмите кнопку "Add". В списке
файлов проекта появится имя добавленного вами файла. Так как проект
нашего первого приложения состоит из одного файла, после добавления
файла hello.cpp нажмите кнопку "Done".
Для того чтобы открыть окно редактирования файла сделайте в окне
"Project:hello" двойной щелчок левой клавишей мыши по
имени файла (установите курсор мыши на имя файла и нажмите с небольшим
интервалом два раза левую клавишу мыши), в нашем случае по имени
hello.cpp. В главном окне появится окно редактирования (рис. 1.4).
Рис. 1.4. Окно редактирования
Если вы добавили к проекту пустой файл hello.cpp и затем открыли
его двойным щелчком по имени файла, окно редактирования не будет
содержать никакого текста. Наберите в нем текст программы, приведенный
в листинге 1.1.
Учтите, что транслятор Borland C++ версии 3.1 для отображения
текста программы использует шрифт BorlandTE, в котором нет русских
букв. Если вы будете работать с русскими буквами, замените этот
шрифт на другой, например Courier Cyrillic. Для этого выберите
в меню "Options" строку "Environment". Затем
в появившемся подменю выберите строку "Preferences".
На экране появится диалоговая панель "Preferences" (рис.
1.5).
Рис. 1.5. Диалоговая панель "Preferences"
В этой диалоговой панели с помощью меню "Font" вы можете
выбрать шрифт для окна редактирования. Мы рекомендуем вам также
в группе переключателей "Auto Save" включить все переключатели
(как это показано на рис 1.5). После внесения всех изменений нажмите
кнопку "OK".
Кроме этого в меню "Options" выберите строку "Application..."
и в появившейся диалоговой панели нажмите мышью пиктограмму с
надписью "Windows App" и затем кнопку "OK".
При этом транслятор будет настроен на создание обычных приложений
Windows.
Для сохранения внесенных изменений выберите в меню "Options"
строку "Save..." и в появившейся диалоговой панели нажмите
кнопку "OK", предварительно убедившись, что все три
имеющихся там переключателя находятся во включенном состоянии
(отмечены галочкой).
Подготовив файл hello.cpp, выберите из меню "Compile"
строку "Build all". На экране появится диалоговая панель
"Compile Status", в которой будет отображаться ход трансляции
файлов проекта и сборки файла загрузочного модуля (рис. 1.6)
Рис. 1.6. Диалоговая панель "Compile Status"
Вам надо следить за сообщениями об ошибках (Errors) и предупреждениями
(Warnings). Если вы не допустили ошибок при наборе содержимого
файла hello.cpp, после редактирования вы должны получить только
одно предупреждающее сообщение:
Linker Warning: No module definition file specified: using defaults
Это сообщение говорит о том, что в проект не включен так называемый
файл определения модуля (module definition file) и поэтому используется
файл, принятый по умолчанию. Для простоты мы сознательно не стали
включать в проект этот файл, но проекты всех наших следующих приложений
будут содержать файл определения модуля.
После завершения процесса редактирования в поле "Status"
диалоговой панели "Compile Status" появится слово Success
(успех). Для продолжения работы вы должны нажать кнопку "OK".
Теперь попробуем запустить созданное нами приложение. Для этого
из меню "Run" выберите строку "Run". После
проверки файлов проекта на экране появится диалоговая панель,
озаглавленная "Main Window" (рис. 1.7).
Рис. 1.7. Диалоговая панель "Main Window"
Она содержит текстовую строку "Hello, world!", прочитав
которую, вы можете нажать кнопку "OK". Это приведет
к завершению работы нашего первого приложения.
Не следует думать, что все приложения 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 формирует
сообщение, описывающее событие, и затем направляет его в нужную
функцию окна. В качестве параметров, передаваемых функции окна,
используются отдельные компоненты сообщения, соответствующего
событию (или, иными словами, созданному в результате появления
события). Поэтому основная задача функции окна - обработка сообщений,
распределяемых окну операционной системой Windows.
Можно считать, что единственная для каждого окна функция окна
реализует все методы окна как объекта. В языке программирования
C++, напротив, каждый метод объекта (класса) реализуется отдельной
функцией, называемой обычно функцией-членом. Для реализации всех
методов функция окна анализирует код сообщения, однозначно идентифицирующий
событие и, следовательно, определяющий нужный метод.
В объектно-ориентированных языках программирования используется
такое понятие, как наследование. Объекты могут наследовать методы
других объектов. В операционной системе Windows также предусмотрен
механизм наследования методов. Он реализуется с использованием
так называемых классов окна.
Для каждого класса окна определяется функция окна. При создании
окна необходимо указать, к какому классу оно будет принадлежать
и, соответственно, какую функцию окна будет использовать для обработки
сообщений. Приложения могут создавать собственные классы окна,
определяя свои функции окна (и следовательно, свои методы), либо
использовать стандартные, определенные в Windows классы окна.
Приведем пример. Пусть, например, нам надо создать окно, выполняющее
функцию кнопки. Мы можем создать собственный класс окна и для
него определить собственную функцию окна. Эта функция будет обрабатывать
сообщения и при необходимости изображать в окне нажатую или отжатую
кнопку, а также выполнять другие действия. Однако в Windows уже
определен класс окна, соответствующий кнопкам. Если вы воспользуетесь
этим классом, вам не придется создавать свою функцию окна, так
как будет использоваться функция, уже имеющаяся в Windows и выполняющая
все необходимые действия.
Любое создаваемое вами окно может наследовать свойства уже созданных
ранее окон, добавляя свои или переопределяя уже имеющиеся в базовом
классе методы. В этом и заключается механизм наследования Windows.
Использование механизма наследования значительно упрощает процесс
создания приложений, так как для большинства стандартных органов
управления, таких, как кнопки, меню, полосы просмотра и т. п.,
в операционной системе Windows уже определены классы окон и все
необходимые методы.
Откуда берутся сообщения?
Большинство сообщений создают драйверы устройств ввода/вывода,
таких, как клавиатура, мышь или таймер. Драйверы создают сообщения
при поступлении аппаратных прерываний. Например, когда вы нажимаете
и затем отпускаете клавишу, драйвер обрабатывает прерывания от
клавиатуры и создает несколько сообщений. Аналогично сообщения
создаются при перемещении мыши или в том случае, когда вы нажимаете
кнопки на корпусе мыши. Можно сказать, что драйверы устройств
ввода/вывода транслируют аппаратные прерывания в сообщения.
Куда направляются сообщения, созданные драйверами?
Прежде всего сообщения попадают в системную очередь сообщений
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.
В этом разделе мы рассмотрим простейшее приложение Windows, содержащее
цикл обработки сообщений. Это приложение имеет только одно окно
и одну функцию окна, однако это только начало.
Алгоритм работы приложения
Если вы, забегая вперед, посмотрите листинги файлов нашего приложения,
то обратите внимание на то, что оно имеет несколько необычную
(с точки зрения программиста, составляющего программы для 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.
Листинги файлов приложения
В отличие от первого приложения (листинг 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);
}
Определения типов, констант и функций
Первые две строки используются для определения типов данных и
констант:
#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.
Инициализация приложения
Первое, что делает функция 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. Сообщение об ошибке
Регистрация класса окна
Задача функции InitApp - регистрация класса окна. На базе этого
класса будет создано главное окно приложения.
Если допускается одновременная работа нескольких копий одного
приложения, регистрация класса окна должна выполняться только
один раз первой копией приложения.
В области локальных переменных функции определены две переменные
- aWndClass и wc:
ATOM aWndClass; // атом для кода возврата
WNDCLASS wc; // структура для регистрации
// класса окна
Переменная aWndClass используется для временного хранения кода
возврата функции RegisterClass. Эта функция относится к функциям
программного интерфейса Windows, она и выполняет регистрацию класса.
В качестве единственного параметра функции необходимо указать
адрес соответствующим образом подготовленной структуры типа WNDCLASS:
aWndClass = RegisterClass(&wc);
Приведем прототип функции RegisterClass:
ATOM WINAPI RegisterClass(const WNDCLASS FAR* lpwc);
Таким образом, процедура регистрации класса окна является несложной.
Вам достаточно подготовить одну структуру и вызвать функцию RegisterClass.
Для вас, возможно, непривычно использование переменной специального
типа ATOM для передачи результата выполнения функции. Однако такое
использование не создает никаких дополнительных трудностей. Тип
ATOM отображается на тип UINT, который, в свою очередь, отображается
на тип unsigned int (см. файл windows.h):
typedef UINT ATOM;
typedef unsigned int UINT;
Переменные типа ATOM используются как идентификаторы текстовых
строк (атомы), хранящихся в области памяти, принадлежащей операционной
системе Windows. Существует набор функций для работы с этими идентификаторами
(для работы с атомами), который мы сейчас не будем рассматривать.
Отметим только, что в этом наборе есть функции для получения адреса
строки, соответствующей идентификатору, для создания и удаления,
а также поиска идентификаторов.
В нашем приложении функция InitApp использует переменную типа
ATOM для формирования кода возврата:
return (aWndClass != 0);
Если регистрация класса произошла успешно, функция RegisterClass
возвращает атом с ненулевым значением, при этом функция InitApp
возвращает значение TRUE. Последнее означает, что инициализация
приложения выполнена без ошибок.
Теперь займемся структурой WNDCLASS, которая используется для
регистрации класса окна. Эта структура определена в файле windows.h:
typedef struct tagWNDCLASS
{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
} WNDCLASS;
Перед регистрацией вам необходимо заполнить все поля в этой структуре.
Поле style определяет стиль класса и задается в виде констант
(описанных, как всегда, в файле windows.h), имя которых начинается
с префикса CS_, например CS_HREDRAW, CS_VREDRAW. Стиль задает
реакцию окна на изменение его размера, на выполнение в окне операции
двойного щелчка мышью (double click), а также позволяет определить
другие характеристики окна, создаваемого на базе данного класса.
Например, если для стиля задать значение CS_HREDRAW | CS_VREDRAW,
при изменении вертикального или горизонтального размера окна приложение
должно его перерисовать, то есть нарисовать заново все или часть
того, что было изображено в окне до изменения размера.
В нашем приложении стиль класса не используется, поэтому для него
задается нулевое значение:
wc.style = 0;
В поле lpfnWndProc необходимо записать адрес функции окна, которая
будет выполнять обработку сообщений, поступающих во все окна,
созданные на базе данного класса. Имя функции окна можно выбрать
любое. В нашем случае используется имя WndProc, хотя вы можете
использовать другое имя. Запись адреса функции окна должна выполняться
следующим образом:
wc.lpfnWndProc = (WNDPROC) WndProc;
Поле lpfnWndProc имеет тип WNDPROC (дальний указатель на функцию),
который мы рассмотрим чуть позже, при описании функции окна. Для
того чтобы избежать получения от компилятора предупреждающего
сообщения о несоответствии типов, вы должны использовать явное
преобразование типа.
Поле cbClsExtra используется для резервирования дополнительной
памяти, общей и доступной для всех окон, создаваемых на базе данного
класса.
Чтобы это было понятно, отметим, что при регистрации класса окна
в памяти, принадлежащей операционной системе Windows, резервируется
и заполняется некоторая область (структура данных). В этой области
хранится вся информация о классе, необходимая для создания окон
на базе этого класса. Вы можете увеличить размер области описания
класса для хранения своей собственной информации, предназначенной
для всех создаваемых на базе этого класса окон. Поле cbClsExtra
определяет размер дополнительной памяти в байтах. В программном
интерфейсе Windows имеются специальные функции, предназначенные
для работы с дополнительной областью памяти.
Наше приложение не создает в описании класса никаких дополнительных
областей, поэтому для заполнения поля cbClsExtra используется
нулевое значение:
wc.cbClsExtra = 0;
Так же как и при создании нового класса, при создании нового окна
Windows резервирует в своей памяти область, описывающую окно.
С помощью параметра cbWndExtra вы можете увеличить размер этой
области для хранения информации, имеющей отношение к создаваемому
окну. В нашем случае размер области описания окна не увеличивается,
поэтому для заполнения поля cbWndExtra используется нулевое значение:
wc.cbWndExtra = 0;
Поле hInstance перед регистрацией класса окна должно содержать
идентификатор приложения, создающего класс окна. В качестве этого
идентификатора следует использовать значение, полученное функцией
WinMain в параметре hInstance:
wc.hInstance = hInstance;
Следующее поле имеет имя hIcon и тип HICON. Это идентификатор
пиктограммы, в которую превращается окно при уменьшении его размеров
до предела (при минимизации окна).
В нашем приложении мы указываем пиктограмму, используемую Windows
по умолчанию. Для Microsoft Windows версии 3.1 вид этой пиктограммы
приведен на рис. 1.11.
Рис. 1.11. Пиктограмма приложения
Для загрузки пиктограммы в приложении вызывается функция программного
интерфейса Windows с именем LoadIcon:
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
Прототип функции LoadIcon:
HICON WINAPI LoadIcon(HINSTANCE hinst, LPCSTR pszicon);
Первый параметр функции (hinst) содержит идентификатор приложения,
второй (pszicon) - имя ресурса-пиктограммы.
Позже мы научим вас определять для окон собственные пиктограммы,
нарисованные с помощью приложения Resource Workshop, входящего
в комплект поставки Borland C++ for Windows.
В поле hCursor (имеющем тип HCURSOR) вы можете задать вид курсора
мыши при его прохождении над окном. Вы знаете, что курсор мыши
меняет свою форму при перемещении над различными окнами приложений
Windows. При регистрации класса окна вы можете указать форму курсора,
для чего и используется поле hCursor.
В нашем приложении мы задаем курсор в виде стандартной стрелки,
для чего вызываем функцию LoadCursor и указываем в качестве второго
параметра константу IDC_ARROW:
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
Прототип функции LoadCursor:
HCURSOR WINAPI LoadCursor(HINSTANCE hinst, LPCSTR pszCursor);
Вы можете определить для окна свой курсор, нарисовав его аналогично
пиктограмме при помощи такого приложения, как Resource Workshop
или Microsoft SDKPaint. Однако пока для простоты мы будем использовать
стандартный курсор.
Далее нам необходимо заполнить поле hbrBackground, имеющее тип
HBRUSH. Это поле позволяет определить кисть (brush), которая будет
использована для закрашивания фона окна. В качестве кисти можно
использовать "чистые" цвета или небольшую пиктограмму
размером 8 х 8 точек.
В нашем приложении мы использовали системный цвет, который Windows
использует для закрашивания фона окон:
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
Системный цвет можно изменять при помощи приложения Control Panel.
Позже мы научим вас задавать для фона окна другие цвета и раскрашивать
окно при помощи пиктограмм.
Поле lpszMenuName (указатель на строку типа LPCSTR) определяет
меню, располагающееся в верхней части окна. Если меню не используется,
при заполнении этого поля необходимо использовать значение NULL:
wc.lpszMenuName = (LPSTR)NULL;
Тип LPCSTR определяется как константный дальний указатель на строку
символов:
typedef const char FAR* LPCSTR;
Очень важно поле lpszClassName. В это поле необходимо записать
указатель на текстовую строку, содержащую имя регистрируемого
класса окон:
wc.lpszClassName = (LPSTR)szClassName;
На этом подготовку структуры wc к регистрации класса окна можно
считать законченной. Можно вызывать функцию RegisterClass.
После регистрации функция InitApp возвращает управление обратно
в функцию WinMain.
Создание главного окна приложения
Далее приложение вызывает функцию CreateWindow для того, чтобы
создать главное окно приложения:
hwnd = CreateWindow(
szClassName, // имя класса окна
szWindowTitle, // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, // задаем размеры и расположение
CW_USEDEFAULT, // окна, принятые по умолчанию
CW_USEDEFAULT,
CW_USEDEFAULT,
0, // идентификатор родительского окна
0, // идентификатор меню
hInstance, // идентификатор приложения
NULL); // указатель на дополнительные
// параметры
В случае успеха функция CreateWindow возвращает идентификатор
окна (типа HWND). Если окно создать не удалось, функция возвращает
нулевое значение.
Приведем прототип функции CreateWindow:
HWND CreateWindow(LPCSTR lpszClassName,
LPCSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hwndParent, HMENU hmenu, HINSTANCE hinst,
void FAR* lpvParam);
Многочисленные параметры функции CreateWindow дополняют описание
окна, сделанное при создании класса окна.
Первый параметр функции (lpszClassName) - указатель на строку,
содержащую имя класса, на базе которого создается окно. В процессе
инициализации приложения мы зарегистрировали класс с именем "WindowAppClass"
(переменная szClassName).
Второй параметр функции (lpszWindowName) - указатель на строку,
содержащую заголовок окна (Title Bar). В нашем случае окно будет
иметь заголовок "Window Application" (переменная szWindowTitle).
Третий параметр (dwStyle) - стиль создаваемого окна. Этот параметр
задается как логическая комбинация отдельных битов. Константа
WS_OVERLAPPEDWINDOW соответствует окну, которое может перекрывать
другие окна, имеет заголовок, системное меню, кнопки для минимизации
и максимизации окна, а также рамку вокруг окна, с помощью которой
можно изменять размер окна. Операционная система Windows позволяет
задавать различные стили для создаваемых окон. Мы их рассмотрим
в дальнейшем.
Четвертый и пятый параметры функции CreateWindow для окна данного
стиля определяют горизонтальную (x) и вертикальную (y) координату
относительно верхнего левого угла экрана видеомонитора.
Шестой и седьмой параметры определяют ширину (nWidth) и высоту
(nHeight) создаваемого окна.
Наше приложение в качестве координат окна и его размеров использует
константу CW_USEDEFAULT. При этом операционная система Windows
сама определяет положение и размеры создаваемого окна.
Восьмой параметр (hwndParent) определяет индекс родительского
окна. Для нашего приложения в качестве значения используется нуль,
так как в приложении создается только одно окно.
Девятый параметр (hmenu) - идентификатор меню или идентификатор
порожденного (child) окна. В нашем случае никакого меню или порожденного
окна нет, поэтому в качестве значения используется нуль.
Десятый параметр (hinst) - идентификатор приложения, которое создает
окно. Необходимо использовать значение, передаваемое функции WinMain
через параметр hInstance.
Одиннадцатый, последний параметр функции (lpvParam) представляет
собой дальний указатель на область данных, определяемых приложением.
Этот параметр передается в функцию окна вместе с сообщением WM_CREATE
при создании окна. Наше приложение не пользуется этим параметром.
Отображение окна на экране
Итак, окно создано. Однако на экране оно еще не появилось, в чем
вы можете убедиться, запустив приложение под управлением отладчика.
Поэтому, проверив, что создание окна выполнено успешно (функция
CreateWindow вернула ненулевое значение), необходимо сделать окно
видимым (нарисовать его на экране). Это можно сделать с помощью
функции ShowWindow:
ShowWindow(hwnd, nCmdShow);
Прототип функции:
BOOL ShowWindow(HWND hwnd, int nCmdShow);
Функция отображает окно, идентификатор которого задан первым параметром
(hwnd), в нормальном, максимально увеличенном или уменьшенном
до пиктограммы виде, в зависимости от значения второго параметра
(nCmdShow). Наше приложение использует в качестве второго параметра
значение, передаваемое функции WinMain через параметр nCmdShow.
После отображения окна в нормальном или максимально увеличенном
виде внутренняя поверхность окна закрашивается кистью, определенной
при регистрации класса.
Внешний вид окна, создаваемого нашим приложением, показан на рис.
1.12.
Рис. 1.12. Главное окно приложения
Сразу после функции ShowWindow в приложении вызывается функция
UpdateWindow.
UpdateWindow(hwnd);
Прототип функции:
void UpdateWindow(HWND hwnd);
Функция UpdateWindow вызывает функцию окна, заданного идентификатором,
передаваемым в качестве параметра hwnd, и передает ей сообщение
WM_PAINT. Получив это сообщение, функция окна должна перерисовать
все окно или его часть. Наше приложение не обрабатывает это сообщение,
передавая его функции DefWindowProc. Сообщение WM_PAINT и способ
его обработки будут описаны позже, когда мы займемся рисованием
в окне.
Цикл обработки сообщений
После отображения окна функция 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, так как она для каждого сообщения вызывает именно
ту функцию окна, которой это сообщение предназначено.
Завершение работы приложения
Приложение обычно завершает свою работу, когда вы нажимаете комбинацию
клавиш <Alt+F4> или выбираете строку "Close" в
системном меню главного окна приложения. При этом в его функцию
окна попадает сообщение WM_DESTROY. В ответ на это сообщение функция
окна помещает в очередь сообщение WM_QUIT, вызывая для этого функцию
PostQuitMessage. Как вы уже знаете, выборка этого сообщения приводит
к завершению цикла обработки сообщений и, следовательно, к завершению
работы приложения.
Если операционная система Windows завершает свою работу, функциям
окна каждого работающего приложения передается сообщение WM_QUERYENDSESSION.
Обрабатывая это сообщение соответствующим образом, приложение
может завершить свою работу, предварительно сохранив все необходимые
данные. Если, например, вы создали документ в текстовом процессоре,
а затем, не сохранив его, попытаетесь завершить работу Windows,
текстовый процессор получит сообщение WM_QUERYENDSESSION, в ответ
на которое он может попросить вас сохранить документ, отказаться
от сохранения или от завершения работы 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, которое используется
в определении функции окна. Если описать функцию с этим ключевым
словом, ее имя станет экспортируемым. При этом функция будет иметь
специальный пролог и эпилог.
Пролог и эпилог экспортируемых функций представляет собой небольшое
добавление в начало и конец функции, обеспечивающее возможность
вызова функции из ядра операционной системы Windows. В частности,
пролог обеспечивает экспортируемой функции доступ к сегменту данных
приложения при вызове этой функции из модулей операционной системы
Windows.
В отличие от обычных функций, вызываемых вашим приложением, функция
окна вызывается не приложением, а операционной системой Windows.
Для обеспечения возможности такого вызова функция должна быть
определена с ключевым словом _export или описана специальным образом
в модуле определения файла, который будет рассмотрен позже.
Займемся теперь параметрами функции окна.
Первый параметр является индексом окна, для которого предназначено
сообщение. Напомним, что адрес функции окна указывается при регистрации
класса окна:
wc.lpfnWndProc = (WNDPROC) WndProc;
На базе одного класса может быть создано несколько окон, каждое
из которых имеет собственный идентификатор. Для обработки сообщений,
поступающих в окна, созданные на базе одного класса, используется
общая функция окна. Функция окна может определить окно, для которого
предназначено сообщение, анализируя свой первый параметр. В нашем
случае имеется только одно окно, поэтому идентификатор окна не
используется.
Следующие три параметра функции окна соответствуют полям msg,
wParam и lParam структуры MSG. В поле msg записывается код сообщения,
поля wParam и lParam описывают дополнительную информацию, передаваемую
в функцию окна вместе с сообщением. Формат этой информации зависит
от кода сообщения.
В нашем приложении функция окна представляет собой переключатель,
выполняющий различные действия для сообщений с разными кодами.
Сообщения WM_LBUTTONDOWN, WM_RBUTTONDOWN и WM_DESTROY обрабатываются
функцией окна, остальные передаются функции DefWindowProc.
Сообщение WM_LBUTTONDOWN попадает в функцию окна, когда вы устанавливаете
курсор мыши в окно приложения и нажимаете левую клавишу мыши.
При этом функция окна вызывает функцию MessageBox и на экране
появляется диалоговая панель с сообщением о том, что была нажата
левая клавиша мыши (рис. 1.13).
Рис. 1.13. Диалоговое окно с сообщением
Аналогично сообщение WM_RBUTTONDOWN попадает в функцию окна, когда
вы устанавливаете курсор мыши в окно приложения и нажимаете правую
клавишу мыши. В этом случае функция окна вызывает функцию MessageBeep
с параметром -1 и затем функцию MessageBox для вывода сообщения
о том, что была нажата правая клавиша мыши.
Приведем прототип функции MessageBeep:
void WINAPI MessageBeep(UINT uAlert);
Параметр функции MessageBeep позволяет выбрать один из нескольких
системных звуковых сигналов:
Параметр | Описание |
-1 | Проигрывание стандартного звукового сигнала с помощью громкоговорителя, установленного в корпусе компьютера
|
MB_ICONASTERISK | Воспроизведение звука, указанного в строке SystemAsterisk раздела [sounds] файла win.ini
|
MB_ICONEXCLAMATION | Воспроизведение звука, указанного в строке SystemExclamation раздела [sounds] файла win.ini
|
MB_ICONHAND | Воспроизведение звука, указанного в строке SystemHand раздела [sounds] файла win.ini
|
MB_ICONQUESTION | Воспроизведение звука, указанного в строке SystemQuestion раздела [sounds] файла win.ini
|
MB_OK (значение этой константы равно 0) |
Воспроизведение звука, указанного в строке SystemDefault раздела [sounds] файла win.ini
|
Функцию MessageBeep удобно использовать для отладки, задавая ей
в качестве параметра значение -1.
При завершении работы приложения функции окна передается сообщение
WM_DESTROY, в ответ на которое функция окна помещает в очередь
приложения сообщение WM_QUIT, вызывая функцию PostQuitMessage.
При выборке сообщения WM_QUIT завершается цикл обработки сообщений
и работа приложения.
Все остальные сообщения передаются без изменения функции DefWindowProc
для дальнейшей обработки.
Обратим ваше внимание на тот факт, что в ответ на сообщение WM_DESTROY
приложение помещает в свою собственную очередь сообщение WM_QUIT.
При этом получается, что одно сообщение, появившееся в очереди
сообщений, порождает другое. Такая практика широко используется
приложениями Windows. Через функцию окна вашего простейшего приложения
проходят многочисленные сообщения, некоторые из которых порождают
новые сообщения после того, как достигают функции DefWindowProc.
Весь этот поток сообщений незаметен для вашего приложения, однако
оно может перехватить любое сообщение и обработать его самостоятельно.
В этом заключается сила механизма обработки сообщения. Фактически
приложение может подменить частично или полностью любой метод,
используемый Windows для реализации той или иной операции с окном
или приложением.
Файл определения модуля
Для того чтобы создать программу 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, обрабатывающим
сообщения. У вас могло сложиться впечатление, что создание приложений
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-экстендеров).
|