Для чего необходима функция defwindowproc
Программирование для окон
Вам еще не надоело создавать консольные приложения? Я так и знал: Ну что ж, тогда я не зря старался при написании данного материала, который обязан пролить свет на программирование в среде Windows, и научить вас создавать полноценные оконные приложения, в зависть вашему соседу, который еще этого не умеет.
Система, основанная на сообщениях
Windows можно назвать объектно-ориентированной системой, хотя формально она таковой не является. Представьте себе систему в виде набора объектов, основным из которых является окно. В процессе работы, Windows «общается» с каждым объектом посредством системных сообщений. При возникновении определенных событий, Windows сообщает об этом окну приложения, посылая ему соответствующее сообщение. Окно, после получения сообщения, должно обработать его и возвратить результат обратно в систему.
Как программа узнает о том, что ее окну послано сообщение? Ответ напрашивается сам. На протяжении всей работы программы необходимо проверять, имеются ли в очереди сообщения. Делается это, конечно-же, с помощью цикла, который выполняется в течении всей работы программы. При каждом проходе цикла проверяется наличие сообщений в очереди, и если таковые имеются, тогда программа поочередно получает их и обрабатывает.
Win32 API
Программисты, пишущие на С++ уже привыкли, что точкой входа в программу является функция main(). Но в системе Windows это не так. Все Win32-приложения используют в качестве точки входа в программу функцию WinMain. Ее объявление можно найти в заголовочном файле winbase.h:
Давайте рассмотрим все ее аргументы:
Типы данных в Windows
При первом взгляде на исходный код Windows-приложения, начинающих программистов начинают пугать «странные» типы данных, используемые в программе. Но это только на первый взгляд. На самом, деле разобраться в них вам не составит особого труда. Основные типы данных Win32 API приведены в таблице 1.
BOOL, BOOLEAN | Булев. Имеет только 2 значения: TRUE или FALSE |
CHAR | 8-битный символ (ANSI Windows) |
WCHAR | 16-битный символ (Unicode) |
TCHAR | CHAR или WCHAR (если используется Unicode) |
USHORT, WORD | Целое беззнаковое 16-битное число |
DWORD, DWORD32, UINT32 | Целое беззнаковое 32-битное число |
DWORD64, UINT64, ULONGLONG | Целое беззнаковое 64-битное число |
FLOAT | Число с плавающей точкой |
SHORT | Целое знаковое 16-битное число |
INT, INT32, LONG, LONG32 | Целое знаковое 32-битное число |
INT64, LONG64, LONGLONG | Целое знаковое 64-битное число |
VOID | Пустой тип |
Структура Windows-программ
Каждая Windows-программа состоит как минимум из двух основных функций. Это WinMain и функция окна.
Давайте напишем простую программу, создающую пустое окно. Для этого в Visual C++ создайте пустой проект Win32 Application, добавьте новый файл (например, myprog.cpp), и вставьте туда следующий код:
Теперь давайте рассмотрим приведенный код подробнее.
Для того чтобы создать окно, сначала необходимо зарегистрировать в системе новый оконный класс. Для этого нужно заполнить структуру WNDCLASS и передать указатель на область памяти, содержащую эту стуктуру, функции RegisterClass(). Данная функция создает и регистрирует новый оконный класс, используя значения элементов переданной ей структуры для определения характеристик класса. Ниже приведены описания всех элементов структуры WNDCLASS.
Как вы, наверное, заметили, в приведенном выше коде явно заполняются не все поля структуры, а только те, которые необходимы в для данного класса. Все остальные поля инициализируются нулевыми значениями с помощью функции ZeroMemory(), которая выполняет аналогичное ее имени действие.
После регистрации класса окна можно приступать к созданию самого окна. Для этого используется функция CreateWindow(), которая возвращает хендл создаваемого окна.
Создав окно, нам необходимо отобразить его на экране. Этим занимается функция ShowWindow(), которая принимает в качестве аргументов идентификатор окна, и флаг, указывающий на способ отображения окна (в данном случае SW_SHOW, определяющий, что окно необходимо показать на экране). Затем, с помощью функции UpdateWindow() мы посылаем нашему окну сообщение WM_PAINT, указывающее на необходимость перерисовать клиентскую область окна.
Далее следует цикл обработки сообщений. Он состоит из управляющей структуры while, которая при каждом проходе цикла получает очередное сообщение из очереди, посредством функции GetMessage(), затем переводит все сообщения от виртуальных клавиш в символьные сообщения с помощью функции TranslateMessage() (о предназначении данной операции мы поговорим позже), и после этого отсылает полученное сообщение на обработку оконной процедуре, используя функцию DispatchMessage().
Функция GetMessage() возвращает ненулевое значение, поэтому цикл не завершается до момента завершения программы. При завершении программы окну посылается сообщение WM_QUIT, которое является единственным сообщением, при получении которого функция GetMessage() возвращает ноль, и цикл обработки сообщений завершается, а код выхода из программы, хранящийся в элементе wParam структуры MSG, возвращается функцией WinMain.
И наконец, пора разобраться каким же образом оконная процедура обрабатывает переданные ей сообщения. У функции окна имеется четыре аргумента:
В Windows существует более тысячи стандартных сообщений. Конечно же, программист не должен обрабатывать их все. Для этого существует функция DefWindowProc(), которая обрабатывает переданное ей сообщение по умолчанию. Таким образом, вы должны обрабатывать только те сообщения, обработка по умолчанию которых вас не устраивает. Также, функция DefWindowProc() не обрабатывает сообщение WM_DESTROY, поэтому вы должны предусмотреть его обработку самостоятельно. В приведенном примере, при получении окном сообщения WM_DESTROY, мы, с помощью функции PostQuitMessage(), ставим в очередь сообщение WM_QUIT, чтобы завершить работу программы.
Заметьте, каким образом сообщения обрабатываются по умолчанию. В структуре switch оконной процедуры предусмотрена метка default, которая пересылает все необрабатываемые нашей программой сообщения функции DefWindowProc() и возвращает результат этой функции. А если сообщение обрабатывается нашей программой, тогда возвращается ноль.
Вот оно, чудо
Примечание: в элементе style, структуры WNDCLASS, определяется общий стиль для всех окон данного класса. Следует заметить, что стиль класса это не тоже самое что и стиль окна, указанный в вызове функции CreateWindow(). Тот стиль, который устанавливается посредством функции CreateWindow(), является индивидуальным стилем окна, а не общим стилем, определенным в классе.
Ресурсы программы
Практически в каждой Windows-программе можно увидеть различные элементы управления, меню, и другие ресурсы программы. Создать в окне какой либо элемент управления, например, кнопку, можно двумя способами. Первый, это создать новое окно используя функцию CreateWindow() с предопределенным в системе оконным классом «button». Второй способ, это использовать файлы ресурсов, в которых содержится описания всех ресурсов программы, будь то меню, элементы управления, иконки и даже диалоговые окна. Каждый элемент управления имеет свой уникальный идентификатор (хендл) определяемый программистом. Когда пользователь совершает какие либо действия над элементом управления, сообщение об этом поступают окну, и после этого выполняются соответствующие действия. К примеру, при нажатии на кнопку окно получает сообщение WM_COMMAND, которое в параметре wParam содержит идентификатор кнопки.
Подробнее о ресурсах приложения и работе с сообщениями мы поговорим несколько позже.
melinux.RU
Об авторе
Подписка на Email
Свежие записи
Аватары
Статистика блога
Метки
sirnet1988 08:49 on 21.01.2010 Постоянная ссылка Ответить
Метки: Программирование ( 46 ), курс лекций ( 11 ), обученик, Win-API ( 16 ), Windows ( 15 )
Разбор оконной процедуры(Win-API)
блог-портфолио и прочие шалости
Если же мы такого не напишем, то наша программа никак реагировать на изменение размеров окна не будет. При этом соответствующее сообщение Windows программа все равно получит.
Мы писали минимальную программу, так что у нас обработчик только одного сообщения Windows – WM_DESTROY. Это сообщение окно получает при своем уничтожении. В этом обработчике мы только делаем некоторые действия, связанные с уничтоженим нашего окна.
Обратите внимание на ветку default:
Тут мы вызываем API-функцию DefWindowProc. Основное предназначение этой API-функции – это обработка сообщений Windows, которые не обрабатываются в нашей программе (т. е. тех, для которых нет своего case). При этом ничего особенно это функция не делает, но очередь из сообщений при этом двигается. И это самое важное – если у нас не было обработчика по умолчанию, то сообщения, не обрабатываемые нашей программой, забили бы очередь, и она бы встала – и даже те сообщения, для которых есть обработчики, никогда бы не были обработаны. Таким образом, основное предназначение функции DefWindowProc – это обработка (»проглатываение») тех сообщений, которые не обрабатываются в каком-нибудь case’е.
В оконную процедуру передаются 4 параметра:
Первый из этих параметров – это окно, в которое мы передаем сообщение Windows. Второй – это само сообщение. Третий и четвертый параметры – это дополнительные параметры для конкретного сообщения. Эти параметры будет разными для разных сообщений (и для некотрых сообщений Windows могут вообще не использоваться). Например, для сообщения WM_LBUTTONDOWN в дополнительных параметрах передаваются x и y той точки, в которой мы щелкнули, информация и том, были ли при этом нажати кнопки shift, atl и ctrl и другое, для сообщения WM_SIZE – новые размеры окна и т. д.. Еще обратите внимание, что эти же параметры у нас передаются в функцию DefWindowProc. Конкретно, что означают эти параметры для некоторого сообщения Windows, надо смотреть в справке по этому сообщению.
Операционная система Microsoft Windows 3.1 для программиста
1.5. Приложение с обработкой сообщений
В этом разделе мы рассмотрим простейшее приложение 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 приложения можно представить следующим образом:
Адрес функции окна указывается при создании класса окна. Этот адрес используется операционной системой Windows для вызова функции окна (напомним, что приложение само не вызывает функцию окна). Приведем алгоритм работы функции окна нашего простейшего приложения:
Из приведенного выше алгоритма работы функции окна видно, что наша функция обрабатывает только три сообщения. Все остальные сообщения, которые попадают в очередь приложения и распределяются функции окна (а их очень много), также должны быть обработаны. Для этого необходимо использовать функцию программного интерфейса Windows с именем DefWindowProc.
Если ваша функция окна проигнорирует вызов функции DefWindowProc для тех сообщений, которые она сама не обрабатывает, Windows не сможет обработать такие сообщения. Это может привести к неправильной работе или блокировке как приложения, так и всей операционной системы Windows.
Листинги файлов приложения
В файле window.cpp определены функции WinMain, InitApp и функция окна WndProc, то есть все функции, из которых состоит приложение. Файл window.def содержит инструкции редактору связей. Эти инструкции используются при создании загрузочного exe-файла приложения.
Рассмотрим подробно файл window.cpp.
Листинг 1.2. Файл window\window.cpp
Определения типов, констант и функций
Первые две строки используются для определения типов данных и констант:
Определение символа STRICT, сделанное до включения файла windows.h, позволит выполнить более строгую проверку типов данных. Просмотрев исходный текст файла windows.h (этот файл находится в подкаталоге include каталога, в который вы выполняли установку транслятора), вы обнаружите, что способ определения многих типов и функций сильно зависит от того, был ли ранее определен символ STRICT.
После прототипов определены две строки символов, содержащие имя класса окна (szClassName) и заголовок окна (szWindowTitle). Имя класса окна используется при создании класса окна, а заголовок нужен, разумеется, для того, чтобы озаглавить создаваемое главное окно приложения. Так как мы в приложении не собираемся изменять эти строки, они описаны как const.
Функция WinMain определена так же, как и в нашем самом первом приложении (листинг 1.1.). В области стека функции созданы две переменные с именами msg и hwnd:
Переменная msg представляет собой структуру типа MSG, описанную в файле windows.h следующим образом:
Эта переменная предназначена для временного хранения сообщений и используется в цикле обработки сообщений.
Переменная hwnd имеет тип HWND, также описанный в файле windows.h, и используется для хранения идентификатора (handle) главного окна приложения. Заметьте, что в структуре MSG также присутствует поле с типом HWND. Это поле используется для хранения идентификатора окна, к которому относится сообщение.
В операционной системе Windows очень широко практикуется использование идентификаторов для ссылки на различные ресурсы. При определении символа STRICT все такие идентификаторы имеют различный тип, поэтому вы не сможете по ошибке выполнить присвоение значений идентификаторов, относящихся к разным ресурсам.
Инициализация приложения
Первое, что делает функция WinMain после запуска приложения, это проверяет наличие уже запущенной ранее копии этого же приложения:
Как мы уже говорили, параметр hPrevInstance функции WinMain содержит нуль, если приложение запустили в первый раз, или идентификатор предыдущей копии приложения, запущенной ранее и работающей на момент запуска текущей копии.
Если параметр hPrevInstance равен нулю, функция WinMain вызывает функцию инициализации приложения InitApp. В противном случае на экран выводится сообщение о невозможности запуска второй копии приложения.
Обратите внимание на вызов функции MessageBox. В качестве последнего параметра указано значение MB_OK | MB_ICONSTOP. Последний параметр функции представляет собой набор битовых флагов, которые мы рассмотрим позже. Флаг MB_OK предназначен для создания на диалоговой панели кнопки с надписью «OK», флаг MB_ICONSTOP нужен для изображения пиктограммы с надписью «STOP» (рис. 1. 10).
Рис. 1.10. Сообщение об ошибке
Регистрация класса окна
Если допускается одновременная работа нескольких копий одного приложения, регистрация класса окна должна выполняться только один раз первой копией приложения.
Переменная aWndClass используется для временного хранения кода возврата функции RegisterClass. Эта функция относится к функциям программного интерфейса Windows, она и выполняет регистрацию класса. В качестве единственного параметра функции необходимо указать адрес соответствующим образом подготовленной структуры типа WNDCLASS:
Приведем прототип функции RegisterClass:
Таким образом, процедура регистрации класса окна является несложной. Вам достаточно подготовить одну структуру и вызвать функцию RegisterClass.
Для вас, возможно, непривычно использование переменной специального типа ATOM для передачи результата выполнения функции. Однако такое использование не создает никаких дополнительных трудностей. Тип ATOM отображается на тип UINT, который, в свою очередь, отображается на тип unsigned int (см. файл windows.h):
Переменные типа ATOM используются как идентификаторы текстовых строк (атомы), хранящихся в области памяти, принадлежащей операционной системе Windows. Существует набор функций для работы с этими идентификаторами (для работы с атомами), который мы сейчас не будем рассматривать. Отметим только, что в этом наборе есть функции для получения адреса строки, соответствующей идентификатору, для создания и удаления, а также поиска идентификаторов.
В нашем приложении функция InitApp использует переменную типа ATOM для формирования кода возврата:
Если регистрация класса произошла успешно, функция RegisterClass возвращает атом с ненулевым значением, при этом функция InitApp возвращает значение TRUE. Последнее означает, что инициализация приложения выполнена без ошибок.
Теперь займемся структурой WNDCLASS, которая используется для регистрации класса окна. Эта структура определена в файле windows.h:
Перед регистрацией вам необходимо заполнить все поля в этой структуре.
Поле style определяет стиль класса и задается в виде констант (описанных, как всегда, в файле windows.h), имя которых начинается с префикса CS_, например CS_HREDRAW, CS_VREDRAW. Стиль задает реакцию окна на изменение его размера, на выполнение в окне операции двойного щелчка мышью (double click), а также позволяет определить другие характеристики окна, создаваемого на базе данного класса. Например, если для стиля задать значение CS_HREDRAW | CS_VREDRAW, при изменении вертикального или горизонтального размера окна приложение должно его перерисовать, то есть нарисовать заново все или часть того, что было изображено в окне до изменения размера.
В нашем приложении стиль класса не используется, поэтому для него задается нулевое значение:
В поле lpfnWndProc необходимо записать адрес функции окна, которая будет выполнять обработку сообщений, поступающих во все окна, созданные на базе данного класса. Имя функции окна можно выбрать любое. В нашем случае используется имя WndProc, хотя вы можете использовать другое имя. Запись адреса функции окна должна выполняться следующим образом:
Поле lpfnWndProc имеет тип WNDPROC (дальний указатель на функцию), который мы рассмотрим чуть позже, при описании функции окна. Для того чтобы избежать получения от компилятора предупреждающего сообщения о несоответствии типов, вы должны использовать явное преобразование типа.
Поле cbClsExtra используется для резервирования дополнительной памяти, общей и доступной для всех окон, создаваемых на базе данного класса.
Чтобы это было понятно, отметим, что при регистрации класса окна в памяти, принадлежащей операционной системе Windows, резервируется и заполняется некоторая область (структура данных). В этой области хранится вся информация о классе, необходимая для создания окон на базе этого класса. Вы можете увеличить размер области описания класса для хранения своей собственной информации, предназначенной для всех создаваемых на базе этого класса окон. Поле cbClsExtra определяет размер дополнительной памяти в байтах. В программном интерфейсе Windows имеются специальные функции, предназначенные для работы с дополнительной областью памяти.
Наше приложение не создает в описании класса никаких дополнительных областей, поэтому для заполнения поля cbClsExtra используется нулевое значение:
Так же как и при создании нового класса, при создании нового окна Windows резервирует в своей памяти область, описывающую окно. С помощью параметра cbWndExtra вы можете увеличить размер этой области для хранения информации, имеющей отношение к создаваемому окну. В нашем случае размер области описания окна не увеличивается, поэтому для заполнения поля cbWndExtra используется нулевое значение:
Поле hInstance перед регистрацией класса окна должно содержать идентификатор приложения, создающего класс окна. В качестве этого идентификатора следует использовать значение, полученное функцией WinMain в параметре hInstance:
Следующее поле имеет имя hIcon и тип HICON. Это идентификатор пиктограммы, в которую превращается окно при уменьшении его размеров до предела (при минимизации окна).
В нашем приложении мы указываем пиктограмму, используемую Windows по умолчанию. Для Microsoft Windows версии 3.1 вид этой пиктограммы приведен на рис. 1.11.
Рис. 1.11. Пиктограмма приложения
Для загрузки пиктограммы в приложении вызывается функция программного интерфейса Windows с именем LoadIcon:
Прототип функции LoadIcon:
Позже мы научим вас определять для окон собственные пиктограммы, нарисованные с помощью приложения Resource Workshop, входящего в комплект поставки Borland C++ for Windows.
В поле hCursor (имеющем тип HCURSOR) вы можете задать вид курсора мыши при его прохождении над окном. Вы знаете, что курсор мыши меняет свою форму при перемещении над различными окнами приложений Windows. При регистрации класса окна вы можете указать форму курсора, для чего и используется поле hCursor.
В нашем приложении мы задаем курсор в виде стандартной стрелки, для чего вызываем функцию LoadCursor и указываем в качестве второго параметра константу IDC_ARROW:
Прототип функции LoadCursor:
Вы можете определить для окна свой курсор, нарисовав его аналогично пиктограмме при помощи такого приложения, как Resource Workshop или Microsoft SDKPaint. Однако пока для простоты мы будем использовать стандартный курсор.
Далее нам необходимо заполнить поле hbrBackground, имеющее тип HBRUSH. Это поле позволяет определить кисть (brush), которая будет использована для закрашивания фона окна. В качестве кисти можно использовать «чистые» цвета или небольшую пиктограмму размером 8 х 8 точек.
В нашем приложении мы использовали системный цвет, который Windows использует для закрашивания фона окон:
Системный цвет можно изменять при помощи приложения Control Panel. Позже мы научим вас задавать для фона окна другие цвета и раскрашивать окно при помощи пиктограмм.
Поле lpszMenuName (указатель на строку типа LPCSTR) определяет меню, располагающееся в верхней части окна. Если меню не используется, при заполнении этого поля необходимо использовать значение NULL:
Тип LPCSTR определяется как константный дальний указатель на строку символов:
Очень важно поле lpszClassName. В это поле необходимо записать указатель на текстовую строку, содержащую имя регистрируемого класса окон:
На этом подготовку структуры wc к регистрации класса окна можно считать законченной. Можно вызывать функцию RegisterClass.
После регистрации функция InitApp возвращает управление обратно в функцию WinMain.
Создание главного окна приложения
Далее приложение вызывает функцию CreateWindow для того, чтобы создать главное окно приложения:
В случае успеха функция CreateWindow возвращает идентификатор окна (типа HWND). Если окно создать не удалось, функция возвращает нулевое значение.
Приведем прототип функции CreateWindow:
Многочисленные параметры функции CreateWindow дополняют описание окна, сделанное при создании класса окна.
Четвертый и пятый параметры функции CreateWindow для окна данного стиля определяют горизонтальную (x) и вертикальную (y) координату относительно верхнего левого угла экрана видеомонитора.
Шестой и седьмой параметры определяют ширину (nWidth) и высоту (nHeight) создаваемого окна.
Наше приложение в качестве координат окна и его размеров использует константу CW_USEDEFAULT. При этом операционная система Windows сама определяет положение и размеры создаваемого окна.
Восьмой параметр (hwndParent) определяет индекс родительского окна. Для нашего приложения в качестве значения используется нуль, так как в приложении создается только одно окно.
Одиннадцатый, последний параметр функции (lpvParam) представляет собой дальний указатель на область данных, определяемых приложением. Этот параметр передается в функцию окна вместе с сообщением WM_CREATE при создании окна. Наше приложение не пользуется этим параметром.
Отображение окна на экране
Итак, окно создано. Однако на экране оно еще не появилось, в чем вы можете убедиться, запустив приложение под управлением отладчика. Поэтому, проверив, что создание окна выполнено успешно (функция CreateWindow вернула ненулевое значение), необходимо сделать окно видимым (нарисовать его на экране). Это можно сделать с помощью функции ShowWindow:
Функция отображает окно, идентификатор которого задан первым параметром (hwnd), в нормальном, максимально увеличенном или уменьшенном до пиктограммы виде, в зависимости от значения второго параметра (nCmdShow). Наше приложение использует в качестве второго параметра значение, передаваемое функции WinMain через параметр nCmdShow.
После отображения окна в нормальном или максимально увеличенном виде внутренняя поверхность окна закрашивается кистью, определенной при регистрации класса.
Внешний вид окна, создаваемого нашим приложением, показан на рис. 1.12.
Рис. 1.12. Главное окно приложения
Сразу после функции ShowWindow в приложении вызывается функция UpdateWindow.
Функция UpdateWindow вызывает функцию окна, заданного идентификатором, передаваемым в качестве параметра hwnd, и передает ей сообщение WM_PAINT. Получив это сообщение, функция окна должна перерисовать все окно или его часть. Наше приложение не обрабатывает это сообщение, передавая его функции DefWindowProc. Сообщение WM_PAINT и способ его обработки будут описаны позже, когда мы займемся рисованием в окне.
Цикл обработки сообщений
После отображения окна функция WinMain запускает цикл обработки сообщений:
Функция GetMessage предназначена для выборки сообщений из очереди приложения и имеет следующий прототип:
Первый параметр функции (lpmsg) является дальним указателем на структуру типа MSG, в которую будет записано выбранное из очереди сообщение. Тип LPMSG определен в файле windows.h следующим образом:
Второй параметр (hwnd) является идентификатором окна, для которого необходимо выбрать сообщение из очереди приложения.
Очередь приложения содержит сообщения, предназначенные для всех окон, созданных приложением. Если в качестве второго параметра функции GetMessage указать нуль, будет выполняться выборка всех сообщений, предназначенных для всех окон приложения. В нашем случае приложение создает только одно окно, поэтому можно указать либо идентификатор созданного окна, либо нуль.
Третий (uMsgFilterMin) и четвертый (uMsgFilterMax) параметры функции GetMessage позволяют определить диапазон сообщений, выбираемых из очереди приложения, задавая соответственно минимальное и максимальное значение кодов выбираемых сообщений. Если для этих параметров указать нулевые значения (как это сделано в нашем приложении), из очереди приложения будут выбираться все сообщения.
Как работает функция GetMessage?
Эта функция может выбирать сообщения только из очереди того приложения, которое ее вызывает. Если очередь сообщений приложения пуста или содержит только сообщения с низким приоритетом, функция GetMessage передает управление другим работающим приложениям, обеспечивая невытесняющую мультизадачность. Таким образом, проверяя очередь сообщений, приложение может передать управление другим приложениям. Эти приложения, в свою очередь, тоже вызывают функцию GetMessage. Таким образом, приложения распределяют между собой процессорное время.
Выбранное функцией GetMessage сообщение удаляется из очереди сообщений приложения и записывается в структуру, адрес которой задан первым параметром функции.
Если из очереди выбирается сообщение с кодом WM_QUIT, функция GetMessage возвращает значение FALSE. В этом случае приложение должно завершить цикл обработки сообщений. При выборке из очереди любых других сообщений функция GetMessage возвращает значение TRUE.
После выборки сообщения из очереди в цикле обработки сообщений его необходимо распределить функции окна, для которой это сообщение предназначено. Для этого должна быть использована функция программного интерфейса Windows с именем DispatchMessage. Эта функция имеет следующий прототип:
Единственный параметр функции (lpmsg) является указателем на структуру, содержащую сообщение. Функция DispatchMessage возвращает значение, полученное при возврате из функции окна. Обычно это значение игнорируется приложением.
С помощью специальных функций, таких, как SendMessage или CallWindowProc, вы все же можете при необходимости вызвать функцию окна. Однако в цикле обработки сообщений следует использовать именно функцию DispatchMessage, так как она для каждого сообщения вызывает именно ту функцию окна, которой это сообщение предназначено.
Завершение работы приложения
Если операционная система Windows завершает свою работу, функциям окна каждого работающего приложения передается сообщение WM_QUERYENDSESSION. Обрабатывая это сообщение соответствующим образом, приложение может завершить свою работу, предварительно сохранив все необходимые данные. Если, например, вы создали документ в текстовом процессоре, а затем, не сохранив его, попытаетесь завершить работу Windows, текстовый процессор получит сообщение WM_QUERYENDSESSION, в ответ на которое он может попросить вас сохранить документ, отказаться от сохранения или от завершения работы Windows.
Функция окна
В нашем приложении был создан класс окна, в котором определена функция окна с именем WndProc:
Мы уже говорили, что эта функция нестандартна и ее нельзя вызывать напрямую из функции WinMain или из какой-либо другой функции.
Функция окна должна иметь следующий прототип (имя функции окна может быть любым, а не только WndProc):
Тип LRESULT определен в файле windows.h следующим образом:
Таким образом, функция окна возвращает значение с типом LONG, что в текущей реализации Windows (версия 3.1) соответствует двойному слову.
Модификатор CALLBACK указывает на соглашения о передаче параметров _pascal и определяет функцию окна как функцию _far:
Функция окна, так же как и функция WinMain (и все функции программного интерфейса Windows), использует для передачи параметров соглашения языка Паскаль.
Вы могли бы описать функцию окна как long _far _pascal, однако для обеспечения возможности переноса вашей программы в 32-разрядные версии Windows (Windows NT) или для использования 32-разрядного расширения Win32s текущей версии Windows следует пользоваться символами LRESULT и CALLBACK.
Обратите внимание на ключевое слово _export, которое используется в определении функции окна. Если описать функцию с этим ключевым словом, ее имя станет экспортируемым. При этом функция будет иметь специальный пролог и эпилог.
Пролог и эпилог экспортируемых функций представляет собой небольшое добавление в начало и конец функции, обеспечивающее возможность вызова функции из ядра операционной системы Windows. В частности, пролог обеспечивает экспортируемой функции доступ к сегменту данных приложения при вызове этой функции из модулей операционной системы Windows.
В отличие от обычных функций, вызываемых вашим приложением, функция окна вызывается не приложением, а операционной системой Windows. Для обеспечения возможности такого вызова функция должна быть определена с ключевым словом _export или описана специальным образом в модуле определения файла, который будет рассмотрен позже.
Займемся теперь параметрами функции окна.
Первый параметр является индексом окна, для которого предназначено сообщение. Напомним, что адрес функции окна указывается при регистрации класса окна:
На базе одного класса может быть создано несколько окон, каждое из которых имеет собственный идентификатор. Для обработки сообщений, поступающих в окна, созданные на базе одного класса, используется общая функция окна. Функция окна может определить окно, для которого предназначено сообщение, анализируя свой первый параметр. В нашем случае имеется только одно окно, поэтому идентификатор окна не используется.
Следующие три параметра функции окна соответствуют полям msg, wParam и lParam структуры MSG. В поле msg записывается код сообщения, поля wParam и lParam описывают дополнительную информацию, передаваемую в функцию окна вместе с сообщением. Формат этой информации зависит от кода сообщения.
В нашем приложении функция окна представляет собой переключатель, выполняющий различные действия для сообщений с разными кодами. Сообщения WM_LBUTTONDOWN, WM_RBUTTONDOWN и WM_DESTROY обрабатываются функцией окна, остальные передаются функции DefWindowProc.
Сообщение WM_LBUTTONDOWN попадает в функцию окна, когда вы устанавливаете курсор мыши в окно приложения и нажимаете левую клавишу мыши. При этом функция окна вызывает функцию MessageBox и на экране появляется диалоговая панель с сообщением о том, что была нажата левая клавиша мыши (рис. 1.13).
Рис. 1.13. Диалоговое окно с сообщением
Приведем прототип функции MessageBeep:
Параметр функции 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 |
При завершении работы приложения функции окна передается сообщение 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, 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:
Оператор HEAPSIZE определяет размер кучи приложения. Однако размер, указанный в качестве параметра, не является критичным. Можно указать любое число, большее нуля. При попытке заказать дополнительную память из кучи приложения операционная система Windows динамически увеличивает размер кучи до необходимой величины (если это возможно). Для кучи, стека и хранения ближних (описанных с ключевым словом near), глобальных, а также статических переменных может быть использовано максимально 64 Кбайт оперативной памяти.
Оператор CODE определяет атрибуты стандартного сегмента кода с именем _TEXT. Атрибут preloaded используется для обеспечения автоматической загрузки сегмента в оперативную память сразу после запуска приложения. Атрибут movable позволяет Windows перемещать сегмент в памяти, например для освобождения большого сплошного участка памяти. И наконец, атрибут discardable позволяет Windows при необходимости уничтожить сегмент и отдать занимаемую им память другим приложениям. При необходимости данный сегмент будет автоматически прочитан из exe-файла и восстановлен.
Оператор DATA определяет атрибуты стандартного сегмента данных, принадлежащего к группе с именем DGROUP. Наш сегмент данных описан как предварительно загружаемый и перемещаемый. Дополнительно указан атрибут multiple, означающий, что для каждой копии приложения необходимо создать отдельный сегмент данных.
Файл определения модуля может содержать и другие операторы, которые мы опишем позже по мере необходимости.