Загрузка...
 
Печать
ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  Создание приложений (Cpp, Win32)

Создание приложений (С++, Win32)


Под платформой Win32 подразумеваются все 32-разрядные (32-битные) операционные системы семейства MS Windows (9x/NT/2000/XP/Vista/7). На тему программирования под MS Windows написана не одна сотня книг (многие из которых на русском языке). Настоятельно рекомендуется прочитать хотя бы пару из них. Не факт, что всё усвоишь, но основные моменты всё равно запомнишь.

Для создания базового приложения в MS Visual C++ (все версии) существуют специальные мастера (wizards). Для вызова списка этих мастеров достаточно создать новый проект, выбрав в Главном меню IDE Файл->Создать->Проект. После ввода имени Проекта и нажатия <ОК>, Мастер приложений последовательно покажет окно приветствия и окно Параметры приложения, в котором (для автоматической генерации исходного кода) в разделе Дополнительные параметры необходимо снять флажок с пункта "Пустой проект". Вся процедура (на примере создания консольного приложения) неплохо описана здесь(external link). После этого созданный Проект можно просмотреть, отредактировать или сразу отправить на компиляцию (F5).

Закрыть
noteПримечание

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


Базовое приложение Windows

Базовое приложение:

  • Проект, содержащий в себе минимальную, необходимую для работы (простого отображения окна) функциональность.
  • Имеет минимальный объём исходного кода. Часто весь исходный код содержится в единственном файле (традиционно он называется Main.cpp или WinMain.cpp).
  • Служит "отправной точкой" для создания любых других приложений путём добавления функций, переменных, классов и т.д.
  • Иногда в литературе его также называют шаблоном Windows-приложения.


Все приложения Windows делятся на 2 основных типа:

  • Консольные приложения(external link). Внешне они выглядят как программы с текстовым интерфейсом, но способны обращаться к большинству функций Windows. Практически не изменились со времён MS-DOS: всё те же серые строки символов на чёрном фоне. В OS Windows консольные приложения обычно запускают из Командной строки (Пуск->Все программы->Стандартные->Командная строка), которая тоже является консольным приложением. В MS Windows 98 Командная строка называется Сеанс MS-DOS.
  • Оконные приложения. Собственно, для чего и задумывалась MS Windows, начиная с её первой версии. Окна могут изменять свои размеры, приобретать и терять фокус, перекрывать друг друга. Также они имеют стандартные атрибуты: кнопки "Свернуть", "Развернуть", "Закрыть"; строку заголовка; строку состояния и т.д. Все современные игры для ОС MS Windows также являются оконными приложениями (как правило, развёрнутыми на весь экран).

Консольное базовое приложение

Исходный код простейшего консольного приложения содержится в одном файле (в нашем случае это Main.cpp) и выглядит так:

Листинг 1. Main.cpp
int main()		// Главная функция программы
{
return 0;		// Функция типа int обязана вернуть какое-либо значение
}

Да, это весь код.)) Приложение, ведь, ничего не делает. Нам даже не понадобилось подключать заголовочные файлы с помощью директивы #include.
Скомпилируем его. Для этого:

  • Запустим MS Visual C++ 2010 (подойдут и другие версии этой IDE) и cоздадим новый проект, выбрав в Главном меню IDE Файл->Создать->Проект.

О том, как установить MS Visual C++ 2010 и как создавать ней проекты читаем здесь.

  • В окне "Создать проект" выбираем мастер: "Консольное приложение Win32", в строке "Имя" пишем название проекта (в нашем случае Test01). Строки "Расположение" и "Имя Решения" заполняются автоматически (при необходимости изменяем). Жмём "OK", "Далее".

Image

  • На странице "Параметры приложения": оставляем "Консольное приложение" и отмечаем пункт "Пустой проект". Жмём "Готово".

Image
Проект создан. Так как это "Пустой проект", он не содержит в себе никаких файлов. В левой части расположен "Обозреватель решений". Если его нет, в главном меню выбираем: Вид->Другие окна->Обозреватель решений. Или комбинация горячих клавиш Ctrl+Alt+L. В Обозревателе решений видна древовидная структура Проектов, входящих в данное Решение. Чуть ниже названия Проекта видим специально заготовленные папки (в MSVC++2010 они называются "фильтры") для файлов Проекта:
Таблица 1

Внешние зависимости Чаще всего здесь размещаются заголовочные файлы различных сторонних библиотек. Причём, они представлены здесь в виде ссылок. "Физически" файлы, как правило, находятся за пределами каталога Проекта и не являются его частью. Содержимое каталога "Внешние зависимости" (если внимательно посмотреть, то он тоже является своеобразным ярлыком или ссылкой) генерируется автоматически в процессе линковки и поиска так называемых внешних "зависимых" библиотек.
Заголовочные файлы Содержит заголовочные файлы Проекта (.h).
Файлы исходного кода Содержит исходные файлы Проекта (.cpp).
Файлы ресурсов Содержит так называемые "бинарные" ресурсы (формы, иконки, звуки и т.д.)
  • Правой кнопкой мыши по фильтру "Файлы исходного кода" -> во всплывающем меню выбираем: Добавить -> Создать элемент.

Image

  • В окне "Добавление нового элемента" отмечаем "Файл C++ (.cpp)" и в строке "Имя" вводим Main. Жмём "Добавить".

Image
Добавленный файл Main.cpp сразу же открывается в редакторе кода.

  • Последовательно вводим все строки исходного кода из Листинга 1, представленного чуть выше (без номеров строк на сером фоне). Настоятельно рекомендуется вводить код вручную (не с помощью копировать/вставить), - только так можно научиться программировать (лучше запоминаешь структуру исходного кода, ключевые слова C++ и, конечно, привыкаешь ставить символ ";" в конце каждой строки).
  • Запускаем компиляцию (кнопка с зелёным треугольником на Панели инструментов или <F5> на клавиатуре).

Image
Скомпилированная программа при запуске в среде Windows на долю секунды покажет чёрное окно консоли и сразу закроется.

Немного изменим нашу программу, добавив возможность выводить на экран текстовое сообщение:

Листинг 2. Main.cpp
// Базовое консольное приложение Windows
// Выводит строку текста на экран

#include <iostream>	// Подключаем заголовочный файл iostream.h из недр MS Visual C++.
//Без него функция std::cout не будет работать.

int main()		// Главная функция программы
{
	std::cout << "Hello, world!";	// выводим сообщение на экран

	return 0;		// Функция типа int обязана вернуть какое-либо значение
}

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

  • Снова компилируем Проект.

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

  • Пуск->Все программы->Стандартные->Командная строка.
  • Проводником открываем каталог с исполняемым (Test01.exe) файлом программы, расположенном в нашем случае по адресу: C:\Users\<Имя пользователя>\Documents\Visual Studio 2010\Projects\Test01\Debug.
  • Перетаскиваем мышью Test01.exe в открытое окно Командной строки и жмём <Enter>.

Image
В итоге: Консольные приложения очень просты в программировании, но не стоит их недооценивать. Вывод текста на экран - это лишь верхушка айсберга тех возможностей, которыми они обладают. Консольные приложения могут содержать в себе функции, классы, переменные. Их часто используют в математических расчётах и как программное "ядро", в дополнение к которому создают оконное приложение, которое пользуется его функционалом.

Оконное базовое приложение (WinAPI)

Исходный код простейшего оконного приложения:

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

Что мы и сделаем. Один из вариантов исходного кода выглядит так (реально компилируется в MSVC++2010 (при указании в настройках Проекта пункта "Набор символов: Использовать многобайтовую кодировку") и других версиях этой IDE):

Листинг 3. Main.cpp
// Файл: Main.cpp 
// Описание: Шаблон оконного приложения Windows. Простейшее (базовое) приложение Windows,
// выводящее на экран окно.
// www.igrocoder.ru 2015
// При использовании просьба указывать ссылку на источник.

#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
#include <windowsx.h>

// Объявление оконной процедуры
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения
	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_HREDRAW|CS_VREDRAW;	// Перерисовываем при перетаскивании
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=LoadCursor(NULL, IDC_ARROW);		// Курсор при наведении на окно
	wndClass.hbrBackground=GetStockBrush(BLACK_BRUSH);	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;							// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
	
	// Если класс не зарегистрируется, досрочно прерываем выполнение программы
	if(!RegisterClassEx(&wndClass)) return FALSE;

	// Создание окна на основе зарегистрированного класса
	hWnd=CreateWindowEx(
		WS_EX_TOPMOST,		// Дополнительный стиль окна
		"GameClass",		// Класс окна.
		"My game title",			// Текст заголовка (на верхнем тулбаре).
		WS_OVERLAPPEDWINDOW,		// Обычное окно с кнопками в правом верхнем углу.
		0, 0,					// Координаты X и Y
		GetSystemMetrics(SM_CXSCREEN),	// Ширина окна
		GetSystemMetrics(SM_CYSCREEN),	// Высота окна
		NULL,							// Дескриптор родительского окна
		NULL,							// Дескриптор меню
		hInstance,						// Дескриптор экземпляра приложения
		NULL);							// Дополнительные данные

	if(hWnd==NULL) return(FALSE);

	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).
	while(msg.message != WM_QUIT)
	{	// Просмотрим очередь: вдруг там ожидает сообщение...
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		// Есть сообщение! Обработаем его как обычно...
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
		else
		{
			// Нет сообщений, ожидающих обработки.
			// Значит приступим к выполнению критичных ко времени операций.
			// Например, займёмся рендерингом.
		}
	}

	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
}

// Оконная процедура
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg)
	{
	case WM_DESTROY:			// В случае этого сообщения...
		PostQuitMessage(0);		// Говорим Windows закрыть приложение
		break;

		// В случае любых других сообщений...
	default: return DefWindowProc(hWnd, msg, wParam, lParam);
	}
	return 0;
}


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

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

  • Запустим MS Visual C++ 2010 (подойдут и другие версии этой IDE) и cоздадим новый проект, выбрав в Главном меню IDE Файл->Создать->Проект.

О том, как установить MS Visual C++ 2010 и как создавать ней проекты читаем здесь.

  • В окне "Создать проект" выбираем: "Проект Win32", в строке "Имя" пишем название проекта (в нашем случае Test01). Строки "Расположение" и "Имя Решения" заполняются автоматически (при необходимости изменяем). Жмём "OK", "Далее".

Image

  • На странице "Параметры приложения": оставляем "Приложение Windows" и отмечаем пункт "Пустой проект". Жмём "Готово".

Image
Проект создан. Так как это "Пустой проект", он не содержит в себе никаких файлов. В левой части главного окна MS Visual C++ 2010 расположен "Обозреватель решений". (Если его нет, в главном меню выбираем: Вид->Другие окна->Обозреватель решений. Или комбинация горячих клавиш Ctrl+Alt+L.) В Обозревателе решений видна древовидная структура Проектов, входящих в данное Решение. Чуть ниже названия Проекта видим специально заготовленные папки (в MSVC++2010 они называются "фильтры") для файлов Проекта (см Таблицу 1).

Закрыть
noteПримечание

В MS Visual C++ 2010 в настройках по умолчанию стоит набор (кодировка) символов UNICODE. В MS Visual C++ 6.0 - напротив, по умолчанию стоит кодировка ANSI (многобайтовая). Данная настройка сильно влияет на типы используемых переменных, что приводит к заметным различиям в исходном коде.
Несмотря на то, что во всех случаях рекомендуется использовать кодировку UNICODE, поддерживаемую во всех современных ОС семейства MS Windows (начиная с Win 2000/XP), большинство книг по программированию игр на классическом C++ придерживаются именно многобайтовой кодировки. Чтобы сильно не переделывать исходные коды под UNICODE, все наши игровые Проекты мы настроим под многобайтовую кодировку. Для этого...

  • В Обозревателе решений щёлкаем по названию только что созданного Проекта Test01.
  • Во всплывающем меню выбираем "Свойства"
  • В появившемся окне установки свойств Проекта жмём "Свойства конфигурации", в правой части в строке "Набор символов" выставляем значение "Использовать многобайтовую кодировку".
  • Жмём ОК.

Image

  • В фильтр "Файлы исходного кода" добавляем файл Main.cpp, точно так, как это описано выше для консольного приложения.

Добавленный файл Main.cpp сразу же открывается в редакторе кода.

  • Последовательно вводим все строки исходного кода Листинга 3, показанного выше (без номеров строк на сером фоне). Настоятельно рекомендуется вводить код вручную (не с помощью копировать/вставить), - только так можно научиться программировать (лучше запоминаешь структуру исходного кода, ключевые слова C++ и, конечно, привыкаешь ставить символ ";" в конце каждой строки).
  • Запускаем компиляцию (кнопка с зелёным треугольником на Панели инструментов MSVC++ или <F5> на клавиатуре).

Image
Если весь код был введён без ошибок, после компиляции запустится приложение, отображающее окно с чёрным фоном. Большинство современных игр начинается на чёрном фоне.

Исследуем код

В исходном коде из Листинга 3 многие параметры можно измененять. Результаты изменения будут видны сразу после перекомпиляции (кнопка с зелёным треугольником на Панели инструментов MSVC++ или <F5> на клавиатуре). Например, изменим цвет фона окна с чёрного на серый. Для этого найди в исходном коде фрагмент, где мы заполняем структуру оконного класса:

<...>
	wndClass.hCursor=LoadCursor(NULL, IDC_ARROW);		// Курсор при наведении на окно
	wndClass.hbrBackground=GetStockBrush(BLACK_BRUSH);	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;							// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass";
<...>

Согласно справочной системе MSDN(external link), параметр BLACK_BRUSH в строке wndClass.hbrBackground=GetStockBrush(BLACK_BRUSH) может принимать следующие значения:
Таблица 2

WHITE_BRUSH Белый фон окна.
BLACK_BRUSH Чёрный фон окна.
DKGRAY_BRUSH Тёмно-серый фон окна.
GRAY_BRUSH Серый фон окна.
HOLLOW_BRUSH Прозрачный цвет фона.
LTGRAY_BRUSH Светло-серый фон окна.

"Поиграй" с исходным кодом, подставляя разные значения параметра wndClass.hbrBackground и наблюдай за результатом после каждой перекомпиляции Проекта.
Как вариант, можно определить практически любой цвет, заменив эту строку кода на wndClass.hbrBackground=СreateSolidBrush(RGB(0,255,255)). Таким образом мы создали собственную "кисть" для закрашивания, определив параметры цвета в палитре RGB (Red, Green, Blue). Каждое из значений составного цвета (который получается путём смешивания красного, зелёного и красного компонентов в разных пропорциях) изменяется от 0 до 255.
Смотри-ка, ты уже без пяти минут Windows-программист! Дальше будет ещё интереснее.

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

  • WinMain - аналог функции Main, обязательной для каждого консольного приложения. Является точкой входа в программу.
  • WindowProc - называется оконной процедурой(external link) (название WindowProc, в принципе, выбрано произвольно, но желательно чтобы оно было "говорящим"). Обеспечивает реальную функциональность приложения.

На эту тему неплохо написано здесь: http://gamesmaker.ru/programming/c/vvedenie-v-winapi-chast-pervaya-sozdanie-okna/(external link).
Остальные небольшие функции реализуют реакцию программы на различные сообщения.
Весь процесс создания оконного Windows-приложения состоит из нескольких шагов:

  1. Регистрируем класс окна в ОС MS Windows. Для этого объявляем структуру типа WNDCLASSEX, заполняем должным образом её поля, после чего вызываем функцию RegisterClassEx.
  2. Создаём на базе зарегистрированного класса окна конкретный экземпляр окна (в нашем случае "GameClass") при помощи функции CreateWindowEx.
  3. Выводим окно на экран при помощи функции ShowWindow. При необходимости вызываем оконную процедуру для обновления рабочей области функцией UpdateWindow.
  4. Запускаем цикл выборки сообщений.

Но обо всём по порядку.
В начале Листинга 3 первое, что мы делаем - это указываем пару макроопределений и подключаем заголовочные файлы:

Листинг 3. Main.cpp
...
#define STRICT
#define WIN32_LEAN_AND_MEAN	// Уменьшаем кол-во используемых компонентов в программе.

#include <windows.h>
#include <windowsx.h>
...

Определения (define - от англ. "определить") специальных макросов перед включением файла windows.h служат для:

  • STRICT обеспечивает более строгую проверку типов. Например, при определённом STRICT компилятор выдаст сообщение об ошибке при присваивании объекта типа HBRUSH объекту типа HCURSOR. Если же STRICT не определён, то никакой ошибки компилятор не увидит. Таким образом можно обезопасить себя от некоторых распространённых ошибок, связанных с присваиванием объекту одного типа значения объекта другого типа (обычно вследствие невнимательности).
  • WIN32_LEAN_AND_MEAN уменьшает количество используемых компонентов и тем самым сокращает время на компиляцию и конечный размер исполняемого файла.

Заголовочный файл windows.h является обязательным для любой Windows-программы. Для предыдущих версий ОС MS Windows он содержал огромное количество объявлений типов, прототипов функций и т.д. Сейчас он включает в себя лишь ссылки на другие заголовочные файлы (их достаточно много). Налицо децентрализация и разбиение исходного кода на несколько независимых файлов-модулей.
windowsx.h является его расширенной версией. В нём также содержится множество полезных макросов и, в частности, распаковщики сообщений.
Оба этих "заголовка" расположены в одном из подкаталогов установленной MS Visual C++. Так, например, если открыть windows.h с помощью блокнота или любого другого текстового редактора, в нём можно обнаружить ссылку на другой заголовочный файл windef.h. В нём определены практически все специальные типы Windows, многие из которых встречаются в Листинге 3. Там также видим определения:

windef.h
...
#define CALLBACK		__stdcall
#define WINAPI				__stdcall
...

Функции CALLBACK и WINAPI будут вызываться с помощью __stdcall - стандартного метода вызова функций Win32, представляющего собой нечто среднее между __cdecl и __pascal (традиционные методы для MS-DOS-программ).
Часто типы, определённые в windef.h являются просто новыми именами для старых, хорошо знакомых типов. Например:

windef.h
...
typedef		unsigned long		DWORD;
typedef		int								BOOL;
typedef		int								INT;
typedef		unsigned int			UINT;
...

С другими, специфичными для Windows типами, такими как HWND, HCURSOR, HBRUSH и многими другими, всё сложнее.

Функция WinMain

  • Её прототип (шаблон с указанием типов аргументов) выглядит так:
int WINAPI WinMain(hINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpzCmdLine, int nCmdShow);
  • Вызывается операционной системой и является точкой входа в программу (Вообще она вызывается некоей стартовой функцией из стандартной библиотеки C/C++, но сейчас это неважно).
  • Представляет собой аналог функции main, которую используют при создани консольных приложений.
  • Возвращает целое значение. В случае наличия цикла выборки сообщений(external link) это должно быть поле структуры msg.wParam. Если его нет, то возвращается 0 (или не 0).
  • Спецификатор WINAPI в заголовке WinMain указывает на то, что используется способ вызова, принятый в Win32 API. Способы вызова отличаются, например, порядком передачи аргументов (их соответствие принятым в ОС соглашениям очень важно).

''Таблица 3. Параметры функции WinMain"

Параметр Значение
HINSTANCE hInst Дескриптор текущего экземпляра приложения. Имеет тип HINSTANCE.
HINSTANCE hPrevInst Дескриптор предыдущего экземпляра приложения (если есть). Имеет тип HINSTANCE. В приложениях Win32 всегда равен NULL.
LPSTR lpzCmdLine Содержит командную строку запущенного приложения (исключая название этого приложения), со списком дополнительных аргументов (если есть). Имеет тип LPSTR (в конце всегда должен стоять символ "0").
int nCmdShow Указывает на способ отображения главного окна программы.

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

Значение Описание
SW_SHOW Окно активируется, используя текущие значения размера и положения.
SW_SHOWMINIMIZED Окно сворачивается (отображается в виде кнопки на Панели задач).
SW_SHOWMAXIMIZED Окно разворачивается на весь экран.
SW_HIDE Окно скрывается и активизируется другое окно.
SW_SHOWNORMAL Окно активизируется и отображается на экране. Если оно было развёрнуто или свёрнуто, то восстанавливается прежнее состояние.


Регистрация окна
В ОС Windows при создании любого окна необходимо указать так называемый класс окна - специальную структуру, хранящуюся внутри ОС и задающую основные характеристики поведения всех окон своего класса. В то же время отличительные особенности каждого конкретного окна будут определены позднее, в момент его создания.
В Листинге 3 WNDCLASSEX характеризует класс приложения, на базе которого будет создано окно программы:

Фрагмент Листинга 3. Main.cpp
...
// Реализация главной функции программы
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HWND hWnd;			// Дескриптор окна
	MSG msg;			// Структура сообщения
	WNDCLASSEX wndClass;	// Объявляем экземпляр класса программы на базе WNDCLASSEX
	wndClass.cbSize=sizeof(wndClass);
	wndClass.style=CS_HREDRAW|CS_VREDRAW;	// Перерисовываем при перетаскивании
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
	wndClass.cbClsExtra=0;
	wndClass.cbWndExtra=0;
	wndClass.hInstance=hInstance;			// Экземпляр окна
	wndClass.hIcon=LoadIcon(NULL, IDI_APPLICATION);		// Пиктограмма приложения
	wndClass.hIconSm=LoadIcon(NULL, IDI_APPLICATION);	// Малая пиктограмма приложения
	wndClass.hCursor=LoadCursor(NULL, IDC_ARROW);		// Курсор при наведении на окно
	wndClass.hbrBackground=GetStockBrush(BLACK_BRUSH);	// Закрашиваем окно чёрным цветом
	wndClass.lpszMenuName=NULL;							// Дескриптор Главного меню окна. Сейчас оно не нужно
	wndClass.lpszClassName="GameClass"; // Обзываем оконный класс.
...
}
...

Сразу после объявления структуры (WNDCLASSEX wndClass;), начинаем заполнять все 12 её полей. Вот их описание:
Таблица 5

Поле структуры Назначение поля
cbSize Беззнаковое целое, в котором содержится размер структуры в байтах. В нашем случае в значении стоит служебная функция sizeof, самостоятельно подсчитывающая размер всей структуры wndClass.
style Беззнаковое целое, указывающее на стиль класса окна. Несколько стилей могут объединяться логической операцией ИЛИ (символ прямой вертикакльной черты "|"). Два самых распространённых стиля - CS_HREDRAW и CS_VREDRAW указывают на необходимость перерисовки окна при изменении его ширины и высоты. Стиль CS_DBLCLKS позволяет окну, созданному на базе этого класса, обрабатывать двойные щелчки мыши.
lpfnWndProc Указатель на оконную процедуру (в нашем случае она называется WindowProc). Окна, созданные на базе одного класса, используют одну и ту же оконную процедуру. Именно поэтому прототип оконной процедуры был объявлен ПЕРЕД функцией WinMain.
cbClsExtra, cbWndExtra Дополнительные параметры оконного класса и окна соответственно. Используются редко. В нашем случае оба параметра имеют значение 0.
hInstance Дескриптор окна приложения, внутри которого располагается оконная процедура для оконного класса. Значение берётся из первого параметра функции WinMain.
hIcon, hIconSm Дескрипторы значков класса окна (большого и малого). Малый значок отображается в заголовке окна и на кнопке в панели задач. Для загрузки ресурсов в обоих случаях используется функция LoadIcon, где первый параметр - дескриптор приложения, второй - имя ресурса, содержащего значок (иконку). В нашем случае дескриптор равен NULL. Значит в этом случае будут использоваться стандартные значки. Возможные значения второго праметра для стандартных ресурсов: IDI_APPLICATION, IDI_ASTERISK, IDI_WARNING, IDI_WINLOGO и др.
hCursor Дескриптор курсора (указателя) мыши класса окна. Для загрузки соответствующего ресурса используется функция LoadCursor с параметрами, аналогичными LoadIcon. Если первый параметр равен NULL, то в качестве второго параметра могут использоваться стандартные ресурсы указателей мыши - IDC_ARROW, IDC_CROSS, IDC_IBEAM, IDC_APPSTARTING, IDC_WAIT и др.
hbrBackground Дескриптор кисти для заливки фона окна. О его применении мы рассказали чуть раньше в этой главе.
lpszMenuName Указатель на строку с именем ресурса меню (lpsz означает, что эта строка всегда должна оканчиваться нулём). В нашем случае он равен NULL, то есть окно будет без меню.
lpszClassName Указатель на имя вновь создаваемого класса окна (lpsz означает, что эта строка всегда должна оканчиваться нулём). В нашем случае GameClass. Его мы передадим в качестве второго параметра функции CreateWindowEx.

Закрыть
noteПримечание

Если запутался во всех этих классах - не беда. Читай доп. литературу и ещё раз внимательно просмотри исходный код в Листинге 3.Позднее сам до всего дойдёшь.


После заполнения полей структуры WNDCLASSEX происходит её регистрация путём вызова функции RegisterClassEx, где в качестве параметра стоит указатель на имя уже заполненной структуры (wndClass). Сама функция "обрамлена" условным переходом if, которая возвращает FALSE в случае неудачи и досрочно завершает работу программы.

Закрыть
noteПроверка с условным переходом if

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

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

Создание окна
Создание окна осуществляется с помощью функции CreateWindowEx. Вот её прототип:

Прототип функции CreateWindowEx
HWND	CreateWindowEx(
		DWORD	dwExStyle,					// Дополнительный стиль окна
		LPCSTR lpClassName,				// Класс окна (в нашем случае GameClass)
		LPCSTR	lpWindowName,		// Имя окна. В принципе, любое. Пишется в заголовке окна
		DWORD dwStyle,					// Основной стиль окна
		int x,	int y,								// Координаты расположения окна
		int nWidth, int nHeight,		// Ширина и высота
		HWND	hWndParent,			// Дескриптор родительского окна (если есть)
		HMENU hMenu,					// Дескриптор меню (если есть)
		HINSTANCE hInstance,		// Дескриптор экземпляра окна
		LPVOID lpParam						// Дополнительные параметры
);

Сравним этот прототип с реальной функцией CreateWindowEx из Листинга 3:

Фрагмент Листинга 3. Main.cpp
...
	// Создание окна на основе зарегистрированного класса
hWnd=CreateWindowEx(
	WS_EX_TOPMOST,		// Дополнительный стиль окна
	"GameClass",				// Класс окна.
	"My game title",			// Текст заголовка (на верхнем тулбаре).
	WS_OVERLAPPEDWINDOW,		// Обычное окно с кнопками в правом верхнем углу.
	0, 0,							// Координаты X и Y
	GetSystemMetrics(SM_CXSCREEN),	// Ширина окна
	GetSystemMetrics(SM_CYSCREEN),	// Высота окна
	NULL,							// Дескриптор родительского окна
	NULL,							// Дескриптор меню
	hInstance,						// Дескриптор экземпляра приложения
	NULL);							// Дополнительные данные

	if(hWnd==NULL) return(FALSE);
...

В комментариях, в принципе, всё доступно объясняется. Но отметим ещё несколько моментов:

  • Четвёртый параметр DWORD dwStyle (в нашем случае он имеет значение WS_OVERLAPPEDWINDOW) указывает на основной стиль окна. Значений может быть несколько. В этом случае их разделяют знаком логического ИЛИ (символ прямой черты "|"). Так вот, этот параметр может принимать следующие значения:

Таблица 6

Значение параметра Описание
WS_BORDER Создание окна с рамкой.
WS_CAPTION Создание окна с заголовком (невозможно использовать одновременно со стилем WS_DLGFRAME).
WS_CHILD ...
WS_CLIPSIBLINGS Используется совместно со стилем WS_CHILD для отрисовки в дочернем окне областей клипа, перекрываемых другими окнами.
WS_CHILDWINDOW Создание дочернего окна (невозможно использовать одновременно со стилем WS_POPUP).
WS_POPUP Создает popup-окно (невозможно использовать совместно со стилем WS_CHILD.
WS_POPUPWINDOW Создает popup-окно, имеющее стили WS_BORDER, WS_POPUP, WS_SYSMENU.
WS_CLIPCHILDREN Исключает область, занятую дочерним окном, при выводе в родительское окно.
WS_DISABLED Создает окно, которое недоступно.
WS_DLGFRAME Создает окно с двойной рамкой, без заголовка.
WS_GROUP Позволяет объединять элементы управления в группы.
WS_HSCROLL Создает окно с горизонтальной полосой прокрутки.
WS_MAXIMIZE Создает окно максимального размера.
WS_MAXIMIZEBOX Создает окно с кнопкой развертывания окна.
WS_MINIMIZE ...
WS_OVERLAPPED Создает перекрывающееся окно (которое, как правило, имеет заголовок и WS_TILED рамку).
WS_OVERLAPPEDWINDOW Создает перекрывающееся окно, имеющее стили WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX.
WS_ICONIC Создает первоначально свернутое окно (используется только со стилем WS_OWERLAPPED).
WS_MINIMIZEBOX Создает окно с кнопкой свертывания.
WS_SYSMENU Создает окно с кнопкой системного меню (можно использовать только с окнами имеющими строку заголовка).
WS_TABSTOP Определяет элементы управления, переход к которым может быть выполнен по клавише TAB.
WS_THICKFRAME Создает окно с рамкой, используемой для изменения размера окна.
WS_SIZEBOX Используется совместно с WS_THICKFRAME.
WS_VISIBLE Создает первоначально неотображаемое окно.
WS_VSCROLL Создает окно с вертикальной полосой прокрутки.

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

  • Первый параметр DWORD dwExStyle (в нашем случае он имеет значение WS_EX_TOPMOST) указывет на дополнительный (расширенный) стиль окна. Значений может быть несколько. В этом случае их разделяют знаком логического ИЛИ (символ прямой черты "|"). Так вот, согласно MSDN(external link), этот параметр может принимать следующие значения:

Таблица 7

Значение параметра Описание
WS_EX_ACCEPTFILES Окно поддерживает Drag'n'Drop (перетаскивание на него файлов).
WS_EX_CAPTIONOKBTN Добавляет кнопку OK на строку заголовка окна.
WS_EX_CLIENTEDGE Окно имеет бордер (рамку) с "утопленными" границами.
WS_EX_CONTEXTMENU Добавляет кнопку "Помощь" на строку заголовка окна.
WS_EX_DLGMODALFRAME Создаёт диалогове окно с двойной рамкой. Опционально окно может также иметь строку заголовка в том случае, если в параметре dwStyle указать значение WS_CAPTION.
WS_EX_INK Окно не издаёт звуков (например щелчков) когда по нему щёлкаешь мышью.
WS_EX_LAYOUTRTL Создаёт окно, где начало отсчёта горизонтальных координат расположено справа и будет увеличиваться справа налево (как в арабском письме).
WS_EX_LTRREADING Текст в окне будет отображаться слева направо. В принципе, по умолчанию во всех европейских дистрибутивах Windows.
WS_EX_NOACTIVATE Главное окно не может быть активировано. Если дочернее окно имеет этот стиль, щелчок по нему не активирует главное окно программы. Окно с эти стилем получает команды от мыши или стилуса, но активируются только дочерние окна. Поддерживается в Windows CE 2.0 и более поздних версиях.
WS_EX_NOANIMATION Окно не поддерживает плавного изменения размеров (с анимацией), и не имеет кнопки на панели задач. Поддерживается в Windows CE 2.0 и более поздних версиях.
WS_EX_NODRAG Окно не "подцепляется" (не перемещается) с помощью мыши или стилуса.
WS_EX_NOINHERITLAYOUT Окно с этим стилем не передаёт свою разметку дочерним окнам.
WS_EX_OVERLAPPEDWINDOW Сочетает стили WS_EX_CLIENTEDGE и WS_EX_WINDOWEDGE.
WS_EX_PALETTEWINDOW Сочетает стили WS_EX_WINDOWEDGE, WS_EX_TOOLWINDOW и WS_EX_TOPMOST.
WS_EX_RTLREADING Если язык ОС арабский, иврит или любой другой с написанием справа налево, текст в окне будет показываться справа налево. Для других языков ОС данный стиль будет игнорироваться.
WS_EX_STATICEDGE Создаёт окно с трёхмерной рамкой, предполагающее использование на нём элементов БЕЗ поддержки пользовательского ввода.
WS_EX_TOOLWINDOW Создаёт окно инструментов. Изначально задумывался для создания свободно перемещаемых ("плавающих") панелей инструментов в различных приложениях (яркий пример - Borland Delphi 5). Окно инструментов не отображается в Панели задач или в списке запущенных приложений, возникающем при нажатии ALT+TAB. Если окно инструментов имеет системное меню, оно не отображается в строке заголовка.
WS_EX_TOPMOST Окно с этим стилем должно располагаться поверх всех остальных окон, не имеющих такого статуса. Оно должно оставаться там даже когда неактивно. Чтобы добавить или удалить этот стиль, используй функцию SetWindowPos.
WS_EX_WINDOWEDGE Окно имеет рамку с выпуклым краем (выглядит как чуть приподнятое).

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

  • В пятом и шестом параметрах (int x, int y) в качестве координаты левого верхнего угла указывается точка (0,0). Это необходимо для того, чтобы окно занимало весь экран.
  • В седьмом и восьмом параметрах (int nWidth, int nHeight) указывается ширина и высота окна. В нашем случае эти размеры совпадают с шириной и высотой экрана (т.к. у нас окно развёрнуто на весь экран). Узнаём ширину и высоту экрана с помощью вспомогательной функции int GetSystemMetrics(int nIndex), получающей в качестве параметра специальные макросы SM_CXSCREEN (для ширины) или SM_CYSCREEN (для высоты), указывающие, что нужно узнать ширину и высоту в точках экрана первичного монитора (Primary display).

В результате успешного выполнения функция CreateWindowEx возвращает дескриптор типа HWND. В случае неудачи возвращается NULL.
Именно поэтому, сразу после описания функции CreateWindowEx, идёт проверка с условным оператором if. Если по окончании выполнения дескриптор приложения равен NULL, то досрочно завершаем выполнение программы методом RETURN.

Окно создано, но на экране оно ещё не появилось. Для этого используется функция ShowWindow (HWND hWnd, int nCmdShow):

Фрагмент Листинга 3. Main.cpp
...
	// Собственно, показываем окно
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
...

В первом параметре она получает дескриптор вновь созданного окна (hWnd), а во втором - одну из констант с префиксом SW_ из Таблицы 6, указывающую на способ отображения окна. Но самый обычный способ - просто указать параметр nCmdShow.
Функция UpdateWindow заставляет окно обновить свою клиентскую область всякий раз, когда происходит перетаскиевание окна или изменяются его размеры.

Цикл выборки сообщений

  • Является "сердцем" любой Windows-программы.
  • Может строиться по-разному (часто на основе всевозможных ветвящихся алгоритмов).
  • В нашей программе выглядит так:
Фрагмент Листинга 3. Main.cpp
...
	// Очищаем структуру сообщения
	ZeroMemory(&msg, sizeof(MSG));

	// Процедура выборки сообщений
	// Продолжаем бесконечно, пока не получим сообщение WM_QUIT (завершение).
	while(msg.message != WM_QUIT)
	{	// Просмотрим очередь: вдруг там ожидает сообщение...
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
	{
		// Есть сообщение! Обработаем его как обычно...
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
		else
		{
			// Нет сообщений, ожидающих обработки.
			// Значит приступим к выполнению критичных ко времени операций.
			// Например, займёмся рендерингом.
		}
	}

	//Удаляем регистрацию класса
	UnregisterClass("GameClass", hInstance);
	
	// Выходим из приложения
	return (msg.wParam);
}
...

MS Windows является событийно-ориентированной операционной системой. Она устроена так, что на протяжении всего времени "жизни" запущенного приложения оно постоянно "бомбардируюется" всевозможными сообщениями, информирующими обо всех изменениях, происходящих в рабочем окружении ОС (сдвинулся курсор мыши, запустили другое приложение и т.д.). Эти сообщения выбираются из очереди сообщений(external link) (Message queue), которую ОС организует для каждой программы, а затем передаются в оконную процедуру (в нашем случае это функция WindowProc) для обработки. БОльшая часть из этих сообщений абсолютно "не интересна" нашему приложению и они передаются обратно Windows для так называемой обработки по умолчанию.
Чтобы сообщение было получено программой, в наличии должны иметься 2 компонента:

  • Точка назначения (участок кода, которому собственно и передаются сообщения). В нашем случае это оконная процедура WindowProc). Именно она получает в качестве аргумента сообщение, адресованное окну, с которым она связана (+ параметры этого сообщения).
  • Механизм передачи (программа должна сама организовать выборку сообщений из всей очереди). Может быть синхронным и асинхронным:

Таблица 8. Виды механизмов (способов) передачи сообщений

Синхронный Реализуется в случае вызова оконной процедуры непосредственно операционной системой Windows. Яркий пример - функция UpdateWindow, вызывающей оконную процедуру, и передавая ей в качестве параметра сообщение WM_PAINT (оно требует прорисовать рабочую область окна). Другой способ послать оконной процедуре синхронное сообщение - воспользоваться функцией SendMessage (см. её прототип сразу под этой таблицей). Она посылает указанное сообщение в окно, определяемое дескриптором hWnd, и НЕ возвращает управление до тех пор, пока не произойдёт возврат из оконной процедуры. Синхронность в данном случае подразумевает возможность предсказать, в какой момент времени (точнее, после какого оператора) произойдёт обработка сообщения.
Асинхронный Реализуется при помощи очереди сообщений, организуемой для каждого приложения (точнее, для каждого потока приложения, который может принимать пользовательский ввод). ОС MS Windows помещает в эту очередь все сообщения. Лишь небольшая их часть может относиться к работающей программе. Приложение выбирает из этой очереди адресованные ему сообщения и обрабатывает их по мере поступления.

Прототип функции SendMessage
LRESULT	SendMessage(
		HWND hWnd,		// Дескриптор окна
		UINT Msg,			// Номер сообщения
		WPARAM wParam,		// Первый параметр
		WPARAM lParam		// Второй параметр
);


БОлбшую часть сообщений в очередь помещает операционная система (например, в ответ на действия пользователя). Но есть функции, позволяющие это сделать и самому приложению. Одна из них PostMessage. Вот её прототип:

Прототип функции PostMessage
LRESULT	PostMessage(
		HWND hWnd,		// Дескриптор окна
		UINT Msg,			// Номер сообщения
		WPARAM wParam,		// Первый параметр
		WPARAM lParam		// Второй параметр
);


Разница между SendMessage и PostMessage состоит в том, что:

  • функция SendMessage непосредственно вызывает оконную процедуру, передавая ей нужное сообщение;
  • функция PostMessage помещает это сообщение в очередь, связанную с создавшим окно программным потоком (thread), и, не дожидаясь обработки сообщения, возвращает управление вызывающей функции. А уже само приложение затем извлечёт сообщение из очереди и должным образом обработает. Именно такую схему называют циклом выборки сообщений.

В нашем случае в этом цикле стоит функция PeekMessage.
В конце этого цикла стоят две функции со следующими прототипами:
BOOL TranslateMessage(CONST MSG *lpMsg)
LONG DispatchMessage(CONST MSG *lpMsg)
Обе они получают в качестве параметра адрес структуры типа MSG, поля которой заполнены предварительным вызовом функции PeekMessage.

  • TranslateMessage обычно "транслирует" сообщения, связанные с пользовательским вводом (нажатие клавиш, перемещение мыши).
  • DispatchMessage вызывает оконную процедуру, передавая ей информацию о сообщении, полученную при помощи функции PeekMessage.

Сам цикл выборки сообщений "обрамлён" циклом while do else, где в условии стоит ненаступление события WM_QUIT, прерывающего выполнение цикла и завершающего работу приложения, что и делает оператор return (msg.wParam).

Оконная процедура и обработка сообщений

Если функция WinMain представляет своего рода "мотор" программы, то оконная процедура наделяет приложение настоящей функциональностью. Большиснтво приложений отличаются друг от друга главным образом реакцией на сообщения, которые им посылает ОС MS Windows. Вот прототип оконной процедуры (название произвольное; в нашем случае - WindowProc):

Прототип функции WindowProc
LRESULT CALLBACK WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

При заполнении структуры класса приложения мы произвели присваивание:

Фрагмент Листинга 3. Main.cpp
...
	wndClass.lpfnWndProc=WindowProc;		// Оконная процедура
...

Благодаря этому MS Windows знает оконную процедуру класса и пересылает в неё все адресуемые окну сообщения. Каждое сообщение сопровождается двумя параметрами с типами WPARAM, LPARAM. Они сожержат упакованные в них характеристики сообщений. В Win32 оба эти параметра имеют размер 32 бита. Встроенные в ОС MS Windows распаковщики сообщений избавляют нас от необходимости работать с данными параметрами напрямую.

Оконные сообщения
В ОС MS Windows имеется огромное количество сообщений, которыми она непрерывно "бомбардирует" оконную процедуру. Лишь малая часть из них (иногда всего 2-3) необходима программисту для обработки в каждом конкретном случае. Большинство Windows-сообщений определены в файле winuser.h, расположенном в каталоге с установленной MS VC++. Вот самые распространённые из них:

Таблица 9. Сообщения, связанные с управлением окнами

WM_ACTIVATE Посылается в окно в случае его активации/деактивации.
WM_CREATE Посылается, когда совершается попытка создать окно при помощи функций CreateWindow или CreateWindowEx. Оконная процедура получает это сообщение ПОСЛЕ того, как окно создано, но ДО того, как оно появится на экране. В случае если оконная процедура в ответ на это сообщение возвращает значение -1, окно не создаётся и функция, использованная для создания окна, возвращает NULL.
WM_CLOSE Посылается в окно всякий раз, когда оно должно быть закрыто. Это сообщение может обрабатываться в случае, если приложению нужно выполнить определённые действия перед тем, как закрыться.
WM_DESTROY Посылается в удаляемое окно, когда оно уже убрано с экрана.
WM_MOVE Посылается в окно после того, как изменилось его положение на экране.
WM_SIZE Посылается в окно при изменении его размеров. Также посылается в окно всякий раз при его создании.
WM_QUIT Помещается в очередь сообщений после вызова функции PostQuitMessage и является командой для завершения приложения. Функция PeekMessage (иногда вместо неё применяют схожую функцию GetMessage), выбрав из очереди это сообщение, возвращает ноль. После этого цикл выборки сообщений прекращается, а вместе с ним завершается и работа программы.
WM_PAINT Посылается в окно при перерисовке всего его содержимого (либо участка этого окна). Является частью библиотеки GDI.


Таблица 10. Сообщения клавиатуры

WM_KEYDOWN Посылается в очередь сообщений, когда окно имеет клавиатурный фокус и несистемная клавиша была отпущена (отжата). Передаёт в оконную процедуру виртуальный код клавиши. Список виртуальных кодов клавиш содержится в файле winuser.h .
WM_KEYUP Посылается в очередт сообщений, когда окно имеет клавиатурный фокус и была нажата какая-либо несистемная клавиша. Передаёт в оконную процедуру виртуальный код клавиши.
WM_CHAR Помещается в очередь сообщений как результат выполнения функции TranslateMessage после распаковки сообщения о нажатой несистемной клавише WM_KEYDOWN.
WM_MOUSEMOVE Помещается в очередь сообщений, при перемещении указателя (курсора) мыши над окном.
WM_LBUTTONDOWN Помещается в очередь сообщений, когда нажимается левая кнопка мыши и курсор в это время находится над клиентской областью окна.
WM_LBUTTONUP Помещается в очередь сообщений, если левая кнопка мыши отпущена (отжата) и курсор в это время находится над клиентской областью окна.
WM_LBUTTONDBLCLK Помещается в очередь сообщений, когда произведён двойной щелчок левой кнопкой мыши и курсор в это время находится над клиентской областью окна.
WM_RBUTTONDOWN Помещается в очередь сообщений, когда нажимается правая кнопка мыши и курсор в это время находится над клиентской областью окна.
WM_RBUTTONUP Помещается в очередь сообщений, если правая кнопка мыши отпущена (отжата) и курсор в это время находится над клиентской областью окна.
WM_RBUTTONDBLCLK Помещается в очередь сообщений, когда произведён двойной щелчок правой кнопкой мыши и курсор в это время находится над клиентской областью окна.
WM_MBUTTONDOWN Помещается в очередь сообщений, когда нажимается средняя кнопка мыши и курсор в это время находится над клиентской областью окна.
WM_MBUTTONUP Помещается в очередь сообщений, если средняя кнопка мыши отпущена (отжата) и курсор в это время находится над клиентской областью окна.
WM_MBUTTONDBLCLK Помещается в очередь сообщений, когда произведён двойной щелчок средней кнопкой мыши и курсор в это время находится над клиентской областью окна.
WM_SETCURSOR Посылается в окно, если указатель мыши перемещается над окном и при этом мышь не была захвачена никаким другим окном. Часто используется для задания вида указателя мыши.


Таблица 11. Другие сообщения

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


Код сообщения передаётся в оконную процедуру в качестве параметра uMsg. Обычная (созданная нами) оконная процедура обрабатывает отдельные сообщения. А те, что не обрабатывает сама, передаёт в оконную процедуру по умолчанию, которая имеет прототип:

Прототип функции DefWindowProc
LRESULT DefWindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

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

Распаковщики сообщений

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

Рассмотрим пример оконной процедуры без применения распаковщиков сообщеий:

...
LRESULT WindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
{
	switch(msg)
	{
	case WM_CREATE:
		if(!SetTimer(hWnd,TIMER_ID, TIMER_RATE, NULL))
return (-1L);
	return (0L);
	case WM_TIMER:
		MessageBeep(0xFFFFFFFF);
	return (0L);
	case WM_DESTROY:
		KillTimer(hWnd, TIMER_ID);
		PostQuitMessage(0);
		Return (0L);
	default:
		return DefWindowProc(hWnd, msg, wParam, lParam);
	}
}
...

Как видим, даже при обработке 3-4 сообщений листинг выглядит громоздко. Более того:

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

Для решения этих проблем и предназначены распаковщики сообщений (message crackers).
При применении распаковщиков сообщений фрагмент исходного кода

...
	case WM_CREATE:
		if(!SetTimer(hWnd,TIMER_ID, TIMER_RATE, NULL))
return (-1L);
	return (0L);
...

можно переписать так:

...
case WM_CREATE:
	return HANDLE_WM_CREATE(hWnd, wParam, lParam, FirstApp_OnCreate);
...

Здесь распаковщик преобразует lParam к типу LPCREATESTRUCT и передаёт переменную этого типа в функцию FirstApp_OnCreate (заранее подготовленную программистом).
Тот же оператор можно записать ещё проще:

...
HANDLE_MSG(hWnd, WM_CREATE, FirstApp_OnCreate);
...

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


ИГРОКОДИНГ  »  ИГРОКОДИНГ: Учебный курс  »  Программируем 3D-шутер от первого лица (FPS) (Win32, Cpp, DirectX9)  »  Часть 1. Создание движка  »  Создание приложений (Cpp, Win32)

Contributors to this page: slymentat .
Последнее изменение страницы Вторник 29 / Ноябрь, 2016 13:37:25 MSK автор slymentat.

Последние комментарии

No records to display