Приложения также могут определять собственные сообщения.
Когда приложение обрабатывает сообщения, оно обычно делает это посредством вызова функции DispatchMessage для каждого принятого сообщения - это происходит в так называемом цикле сообщений. В свою очередь, функция DispatchMessage вызывает соответствующую оконную процедуру, проверяя, для какого класса окна предназначено сообщение. Именно эта оконная процедура (функция окна) действительно обрабатывает сообщение, переданное окну.
Основное поведение окна определяется классом окон. Класс окна (не путать с понятием “класс” языка программирования C++) несет информацию о начальном внешнем виде окна, пиктограмме по умолчанию, курсоре и ресурсе меню, связанном с окном; и, что, возможно, более важно – об адресе функции, называемой оконной процедурой.
Функция окна анализирует это сообщение и его параметры, а затем, при необходимости, выполняет некоторые действия.
Можно считать, что единственная для каждого окна функция окна реализует все методы окна как объекта. В языке C++, напротив, каждый метод объекта (класс) реализуется отдельной функцией, называемой обычно функцией-членом. Для реализации методов функция окна анализирует сообщение и однозначно определяет нужный метол.
Рассмотрим, какие типы классов окон существуют.
Существует множество стандартных классов окон, предусмотренных самой операционной системой Windows. Эти системные глобальные классы реализуют функциональные возможности общих элементов управления.
Любое приложение может использовать эти классы в своих окнах; например, реализовать элемент управления – поле ввода, используя класс окна Edit.
Приложение также может определять собственные классы окон с помощью функции RegisterClass. Эта функция позволяет программисту реализовать поведение окна, не являющегося частью ни одного из поддерживаемых системой глобальных классов.
Например, таким образом, типичное приложение реализует функциональные возможности своего главного окна, регистрирует пиктограмму главного окна и ресурса меню.
В Windows предусмотрен механизм наследования. Он реализуется с использованием так называемых классов окна. Перед созданием окна, необходима регистрация класса окна.
Для каждого класса окна определяется функция окна. При создании окна необходимо указать, к какому классу окна оно будет принадлежать и, соответственно, какую функцию окна будет использовать для обработки сообщений. Приложения могут создавать собственные оконные классы окна и свои функции окна (и следовательно свои методы), или использовать стандартные, определенные в Windows классы окна.
Например, можно создать окно изображающее кнопку и для него определить функцию окна, которая будет обрабатывать сообщения от мыши. Эта функция будет обрабатывать сообщения, и рисовать кнопку в нажатом и отжатом состоянии. Но в Windows уже определен класс окна, соответствующий кнопке.
Windows также позволяет разбивать существующие окна на подклассы (субклассы) и суперклассы.
Разбиение на подклассы замещает оконную процедуру для класса окна на другую процедуру. Это разбиение осуществляется путем изменения адреса процедуры через функцию SetWindowLong (в случае простого разбиения на подклассы) или SetClassLong (в случае глобального разбиения на подклассы). При этом в первом случае изменится только поведение определенного окна, а во втором – поведение всех окон указанного класса, создаваемых данным приложением.
Операция создания суперкласса создает новый класс на основе существующего, сохраняя его оконную процедуру. Чтобы создать суперкласс из класса окна, приложение извлекает информацию о классе с помощью функции GetClassInfo, изменяет полученную таким образом структуру WNDCLASS и использует измененную структуру при вызове RegisterClass.
Через функцию GetClassInfo приложение также получает адрес оригинальной оконной процедуры, который остается тот же; сообщения, которая не обработала новая оконная функция, должны быть переданы оригинальной процедуре.
Замечание.Несмотря на то, что используемая выше терминология напоминает терминологию объектно-ориентированного программирования, понятие класса окна не следует путать с понятиями C++. Понятие класса окна возникло на несколько лет раньше начала использования в Windows объектно-ориентированных языков.
Типы сообщений
Существует много разновидностей сообщений, представляющих события на разных уровнях. Каждое простое событие, каждое простое действие посылается в виде сообщения окну для обработки.
Приложению приходит множество сообщений, однако приложение не должно заботиться о смысле каждого отдельного сообщения. Вместо обработки всех возможных сообщений приложение имеет свободу выбора; необработанные сообщения передаются в функции обработки сообщений операционной системы по умолчанию.
Приходящее приложению Windows-сообщение состоит из нескольких частей, для его представления используется структура MSG.Приведем описаниеструктуры, применяющейся для представления сообщения, т.е. описывающей тип сообщения:
typedef struct tagMSG { HWND hwnd; // окно, которому посылается это сообщение UINT message; // значение самого сообщение WPARAM wParam; // для передачи дополнительной информации LPARAM lParam; // для передачи дополнительной информации DWORD time; // время, когда произошло событие POINT pt; // точка, где произошло события (для мыши) } MSG;
Элемент структуры hwnd однозначно идентифицирует окно, которому посылается это сообщение. Каждое окно в Windows имеет такой идентификатор. Элемент message идентифицирует само сообщение. Этот элемент может иметь сотни различных значений, означающих одно из многих сотен различных сообщений, которые могут получать приложения Windows.
Для идентификаторов сообщений обычно используются символьные представления (WM_PAINT, WM_TIMER), а не числовые значения. Эти символьные значения определены в стандартных файлах заголовков Windows (приложению необходимо включать в свой исходный текст только файл windows.h – он содержит директивы #include для остальных файлов).
Сообщения можно разделить на несколько групп в зависимости от их функций. Самой насыщенной группой сообщений является группа сообщений управления окнами. Символьные идентификаторы для этих сообщений начинаются с WM_.Эта группа настолько велика, что ее уместно еще раз разбить на категории. Эти категории включают:
сообщения DDE (dynamic data exchange),
сообщения буфера обмена (clipboard),
сообщения мыши,
сообщения клавиатуры,
сообщения неклиентской (non-client) области окна,
сообщения MDI (multiple-document interface),
и многие другие типы.
Перечисленные категории несколько неточны, не всегда строго определены; они просто служат для удобства программистов, чтобы можно было представить картину множества событий управления окнами. Множество сообщений WM_ также не фиксировано, оно растет по мере добавления новых возможностей операционной системы.
Другие группы сообщений связаны с определенными типами окон. Существуют сообщения, определенные для полей ввода, кнопок, списков, комбинированных списков, полос прокрутки, элементов просмотра списка деревьев и т.д. Эти сообщения за редким исключением обычно обрабатываются оконной процедурой окна элемента управления и редко представляют интерес для программистов приложений.
Первый способ пригоден только для случая, если сообщение пересылается между окнами (частями) одного и того же приложения. Для этого необходимо определить символическое имя нового сообщения при помощи директивы #define, например:
Вторым способом определения собственного сообщения является использование функции RegisterWindowMessage, которая возвращает уникальный идентификатор для сообщения. Использование собственных типов сообщений, полученных таким способом, позволяет частям приложения связываться между собой; разные приложения также могут обмениваться информацией таким способом. Однако для взаимодействия приложений доступны более мощные механизмы, например, отображаемые в память файлы.
Вызов функций Windows
Существование цикла сообщений – не единственный механизм, посредством которого приложение взаимодействует с Windows. Windows предлагает огромное количество системных вызовов для выполнения большого разнообразия задач, включая управление потоками, работу с окнами, обработку файлов, управление памятью, графический сервис, коммуникацию и много других функций.
Замечание.Основное множество системных вызовов Windows можно разделить на три главных категории. Службы GDI (Graphics Device Interface – интерфейс графических устройств) предлагают аппаратно-независимые функции графического вывода. Службы пользователя включают в себя системные вызовы для управления элементами пользовательского интерфейса, такими как окна, элементы управления, диалоговые окна или сообщения. Службы ядра включают системные вызовы для управления процессами и потоками, управления ресурсами, файлами и памятью. Windows также включает разносторонние пользовательские интерфейсы программирования приложений (API), вкратце рассматриваемые ниже.
Службы GDI. Функции GDI используются для выполнения основных независимых от устройства графических операций над контекстом устройства. Контекст устройства – это интерфейс для определенного графического устройства. Его можно использовать для получения информации об устройстве и выполнения графического вывода на это устройство. Информация, которая может быть получена через контекст устройства, содержит его подробное описание. Технология устройства (векторная или растровая), его тип, имя, разрещающая способность, цветовые возможности, возможности шрифтов – все это можно получить, вызвав контекст соответствующего устройства.
Службы пользователя. Модуль User (модуль пользователя) поддерживает системные вызовы для работы с элементами пользовательского интерфейса. Они включают в себя функции обработки окон, диалоговых панелей, меню, текстовых и графических курсоров, элементов управления, буфера обмена и многих других понятий. Именно благодаря функциям модуля User становятся доступными высокоуровневые компоненты пользовательского интерфейса.
Службы ядра системы. Службы ядра обычно относятся к категории управления файлами, памятью, процессами, потоками и ресурсами. Это не полный список, но все же эти категории описывают самые часто используемые функции модуля Kernel.
Другие API. Windows – это намного больше, чем возможности, реализованные в рассматриваемых выше трех основных модулях. Существует множество модулей, других API, каждый реализующий свое определенное множество функциональных возможностей. Рассмотрим кратко некоторые из наиболее широко используемых API.
Функции общих элементов управленияиспользуются для работы с новыми элементами управления (расширяющими множество стандартных элементов управления).
Общие диалоговые окна содержат поддерживаемые системой диалоговые окна для открытия файла, выбора цвета из цветовой палитры, выбора шрифта из множества инсталлированных в системе, определения операции поиска или поиска с заменой. Эти диалоговые окна можно использовать по умолчанию или их функции можно изменять через новые шаблоны диалоговых окон и оконных процедур.
MCI (multimedia control interface) предоставляет собой интерфейс управления средствами мультимедиа. Через функции MCI приложения легко могут получить доступ к видео-, аудио- и MIDI-возможностям Windows. Существует несколькогрупп функций, связанных с сетью: например, WinSock – библиотека Windows Sockets, WinInet – библиотека Windows Internet API и др.
Оконная функция – функция обратного вызова
Программисты хорошо знакомы с понятием вызова операционной системы для выполнения каких-то действий. Например, программисты на C используют функцию fopen для открытия файла. Библиотечные функции, поставляемые компилятором, содержат код, который фактически вызывает для открытия файла операционную систему. Здесь все просто. Но операционная система Windows ведет себя иначе.
Хотя в Windows имеется очень много доступных программисту функций, Windows также и сама посылает вызовы программе, особенно оконной процедуре. Оконная процедура связана с классом окна, который программа регистрирует с помощью вызова функции RegisterClass. Окно, создаваемое на основе этого класса, использует оконную процедуру для обработки всех сообщений окна. Windows посылает сообщения окну, вызывая оконную процедуру.
Рассмотрим примеры, когда Windows вызывает оконную процедуру:
первый раз при создании окна;
при последующем удалении окна;
при изменении размеров окна, при его перемещении, при его свертывании;
при выборе пункта меню;
при манипуляции с полосами прокрутки или с мышью;
чтобы сообщить ей о необходимости перерисовать рабочую область.
Все эти вызовы имеют форму сообщений. В большинстве Windows-программ основная часть программы направлена на обработку этих сообщений. Свыше 200 различных сообщений, которые Windows может отправить оконной процедуре, идентифицируются именами, которые начинаются с cимволов WM_ и определяются в заголовочных файлах Windows.
Фактически, идея функции, находящейся в программе, но которая вызывается не из самой программы, не является абсолютно новой в традиционном программировании. Функции signal в языке С может перехватить нажатие комбинации клавиш <Ctrl+Break>. В Windows эта идея расширена и пронизывает всю систему.
Любое событие, относящееся к окну, передается оконной процедуре в виде сообщения. Затем оконная процедура соответствующим образом реагирует на это сообщение или передает сообщение в функцию DefWindowProcдля обработки его по умолчанию.
Синхронные и асинхронные сообщения
Итак, передача окну сообщений означает вызов операционной системой Windows оконной процедуры. Но в программах для Windows имеется и цикл обработки сообщений, который берет сообщения из очереди сообщений, вызывая функцию GetMessage, и отправляет их оконной процедуре, вызывая функцию DispatchMessage.
Одни и те же сообщения могут быть и синхронные (queued), и асинхронные (nonqueued). Синхронными сообщениями называются сообщения, которые Windows помещает в очередь сообщений программы, и которые извлекаются и диспетчеризируются в цикле обработки сообщений. Асинхронные сообщения передаются непосредственно окну, когда Windows вызывает оконную процедуру.
Говорят, чтосинхронные сообщения помещаются (отправляются) в очередь сообщений (post), а асинхронные посылаются прямо в оконную процедуру (send).
Структура программ для Windows очень проста, поскольку у них имеется только одно центральное место обработки сообщений. В результатеоконная процедура получает все предназначенные для окна сообщения, как синхронные, так и асинхронные.
Синхронными становятся сообщения, в основном, тогда, когда они являются результатом пользовательского вводапутем нажатия клавиш (например, WM_KEYDOWN или WM_KEYUP), это символы, введенные с клавиатуры (WM_CHAR), результат движения мыши (WM_MOUSEMOVE) и щелчков кнопки мыши (WM_LBUTTONDOWN). Кроме этого, синхронные сообщения включают в себя сообщения таймера (WM_TIMER), сообщение о выходе из программы(WM_QUIT), а также все сообщения, которые помещаются в очередь сообщений посредством вызова приложением функции PostMessage.
Сообщения становятся асинхронными во всех остальных случаях. Часто асинхронные сообщения являются результатом вызова определенных функций Windows или непосредственным результатом вызова функции SendMessage.
Очевидно, что процесс этот сложен, но, большая часть сложностей ложится на Windows, а не пользовательские программы.
С позиции оконной процедуры совершенно не важно, для обработки какого сообщения и кем она вызвана. Оконная процедура может что-то сделать с этими сообщениями, а может и проигнорировать их. По этой причине оконную процедуру назвали конечным пунктом обработки (ultimate hook). Сообщения извещают оконную процедуру почти обо всем, что влияет на окно.
Например, когда WinMain вызывает функцию CreateWindow, Windows отправляет оконной процедуре асинхронное сообщение WM_CREATE. КогдаWinMain вызывает UpdateWindow, Windows отправляет оконной процедуре асинхронное сообщение WM_PAINT.
Сообщения не похожи на аппаратные прерывания. Во время обработки в оконной процедуре одного сообщения программа не может быть прервана другим сообщением. Только в этом случае, если оконная процедура вызвала функцию, которая сама стала источником нового сообщения, то оконная процедура начнет обрабатывать это новое сообщение еще до того, как функция вернет управление программе.
Цикл обработки сообщений и оконная процедура работают не параллельно. Когда оконная процедура обрабатывает сообщение, то это результат вызова функции DispatchMessage в WinMain. DispatchMessage не завершается до тех пор, пока оконная процедура не обработала сообщение.
Часто возникает необходимость того, чтобыоконная функция сохраняла информацию, полученную в сообщении, и использовала ее при обработке другого сообщения. Тогда эту информацию следует описывать в оконной процедуре в виде статических переменных, либо хранить в глобальных переменных.
Разделение аппаратных ресурсов
Одним из наиболее существенных свойств Windows является то, что под его управлением можно одновременно запускать на выполнение несколько программ. Различные приложения делят между собой эти ограниченные ресурсы, распределение которых осуществляет операционная оболочка Windows.
Программируя для Windows, нельзя забывать о том, что все аппаратные ресурсы являются разделяемыми, и для их правильного использования следует придерживаться определенных правил.
Так, если программа для MS-DOS имеет доступ ко всей свободной оперативной памяти и может обращаться к этой памяти так, как захочется программисту, то в Windows, где одновременно может выполняться несколько программ, память является разделяемым ресурсом.
Другим примером разделяемого ресурса является дисплей. Программируя в MS-DOS, можно в любой момент переключить режим видеоадаптера, изменить цвет или даже напрямую обратится контроллеру и что-то записать в видеопамять. Такая техника полностью исключается при программировании в Windows. Дисплей - разделяемый системный ресурс, и обращаться к нему можно только так, как позволяет Windows.
Одной из основных целей создателей Windows являлось обеспечение визуального доступа к большинству приложениям одновременно. Для этого каждому приложению выделяется часть экрана для взаимодействия с пользователем. В Windows каждое приложение в любой момент времени имеет доступ к определенной части экрана. Таким образом, экран в Windows - разделяемый ресурс, средством разделения которого является окно приложения.
Итак, два важных вывода.
Во-первых, загруженная на выполнение программа не забирает на себя полностью все ресурсы- эти ресурсы выделяются приложению при помощи специальных механизмов.
Во-вторых, в каждый момент времени программа не знает, какая именно область экрана принадлежит окну приложения, поскольку пользователь в любой момент может переместить окно и изменить его размеры (для определения координат окна нужно обратиться к специальной функции Windows).