Введение в программирование трехмерных игр с DX9

         

Цикл сообщений



Цикл сообщений

Успешно завершив инициализацию, мы переходим к сердцу программы — циклу обработки сообщений. В программе Hello World, мы заключили цикл обработки сообщений в функцию с именем Run.

int Run() { MSG msg; ::ZeroMemory(&msg, sizeof(MSG));

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

Начинается функция Run с объявления переменной msg типа MSG, являющейся структурой данных, представляющей сообщения Windows. Ее определение выглядит следующим образом:

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

hwnd — Идентифицирует окно, которому предназначено сообщение.

message — Одна из предопределенных констант, идентифицирующих сообщение (например, WM_QUIT).

wParam — Дополнительная информация о сообщении. Зависит от конкретного сообщения.

lParam — Дополнительная информация о сообщении. Зависит от конкретного сообщения.

time — Время, когда сообщение было помещено в очередь.

pt — Координаты указателя мыши (x, y) в экранном пространстве в тот момент, когда сообщение было помещено в очередь.

Затем мы входим в цикл обработки сообщений. Функция GetMessage будет возвращать true до тех пор, пока из очереди не будет извлечено сообщение WM_QUIT; следовательно цикл будет выполняться, пока не будет получено сообщение WM_QUIT. Функция GetMessage извлекает сообщение из очереди и заполняет члены нашей структуры MSG. Если GetMessage возвращает true, будут вызваны еще две функции: TranslateMessage и DispatchMessage. TranslateMessage выполняет трансляцию сообщений Windows, в частности преобразование виртуальных кодов клавиш в коды символов. DispatchMessage направляет сообщение соответствующей оконной процедуре.



Функция MessageBox



Функция MessageBox

Последняя функция API, которую мы сейчас рассмотрим — это функция MessageBox. Она очень полезна в тех случаях, когда надо показать какую-то информацию пользователю и получить от него ответ. Прототип функции MessageBox выглядит так:

int MessageBox( HWND hWnd, // Дескриптор владельца окна, // можно указать null LPCTSTR lpText, // Текст, выводимый в окне LPCTSTR lpCaption, // Текст, выводимый в заголовке окна UINT uType // Стиль окна сообщения. );

Значение, возвращаемое функцией MessageBox зависит от типа окна сообщения. Стили окон сообщений и соответствующие им возвращаемые значения перечислены в MSDN.



приложениий предоставляют пользователю для работы





GUI

Большинство Windows- приложениий предоставляют пользователю для работы графический интерфейс (GUI, graphical user interface). Обычное Windows-приложение содержит главное окно, меню, панель инструментов и, возможно, ряд других элементов управления. На Рисунок 2 показаны наиболее распространенные элементы графического интерфейса пользователя. Для игр, использующих Direct3D. нам не требуется профессиональный интерфейс пользователя. Фактически нам достатоно главного окна, в клиентской области которого мы будем отображать трехмерные сцены.


Исследование программы Hello World


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



Чтобы использовать Direct3D, мы должны


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

Обзор


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



Оконная процедура



Оконная процедура

Мы уже упоминали ранее, что оконная процедура— это то место, где мы указываем код, который должен выполняться при получении окном приложения каких-либо сообщений. В программе Hello World оконная процедура называется WndProc. Ее прототип выглядит так:

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

Функция возвращает значение типа LRESULT (в действительности это целое число типа long), сообщающее успешно или нет завершена работа функции. Идентификатор CALLBACK сообщает, что это функция обратного вызова (callback function). Это означает, что вызов данной функции осуществляют внутренние механизмы Windows. Посмотрите на исходный код приложения Hello World: вы нигде не найдете явно указанного нами вызова оконной процедуры; Windows вызывает ее за нас, когда окну требуется обработать поступившее сообщение.

В сигнатуре оконной процедуры указано четыре параметра:

hwnd — Идентифицирует окно, которому предназначено сообщение.

uMsg — Предопределенная константа, идентифицирующая конкретное сообщение. Например, сообщению о выходе из приложения соответствует константа WM_QUIT. Префикс WM означает «оконное сообщение» (Window Message). Существуют сотни предопределенных оконных сообщений, описание которых можно найти в MSDN.

wParam — Дополнительная информация о сообщении. Зависит от конкретного сообщения.

lParam — Дополнительная информация о сообщении. Зависит от конкретного сообщения.

Наша оконная процедура обрабатывает три сообщения: WM_LBUTTONDOWN, WM_KEYDOWN и WM_DESTROY. Сообщение WM_LBUTTONDOWN посылается когда пользователь нажимает левую кнопку мыши и при этом указатель мыши находится внутри клиентской области окна. Сообщение WM_KEYDOWN отправляется если нажата какая-нибудь клавиша на клавиатуре. Сообщение WM_DESTROY будет получено в том случае, если окно должно быть уничтожено. Наш код очень простой; получив сообщение WM_LBUTTONDOWN мы выводим окно сообщений с текстом «Hello, World»:

case WM_LBUTTONDOWN: ::MessageBox(0, "Hello, World", "Hello", MB_OK); return 0;

Когда окно получает сообщение WM_KEYDOWN мы проверяем какая именно клавиша была нажата. Параметр wParam, передаваемый в оконную процедуру, содержит виртуальный код клавиши (virtual key kode), указывающий какая клавиша нажата. Вы можете думать о виртуальном коде клавиши как об идентификаторе конкретной клавиши на клавиатуре. В заголовочном файле Windows содержится список виртуальных кодов клавиш, которые мы можем использовать при проверке того, какая именно клавиша была нажата (например, чтобы проверить была ли нажата клавиша Esc, мы используем константу виртуального кода клавиши VK_ESCAPE).

ПРИМЕЧАНИЕ

Помните, что параметры wParam и lParam используются для передачи дополнительной информации, относящейся к конкретному сообщению. Для сообщения WM_KEYDOWN переменная wParam содержит виртуальный код нажатой клавиши. В библиотеке MSDN описано какую именно информацию содержат переменные wParam и lParam для каждого сообщения Windows. case WM_KEYDOWN: if( wParam == VK_ESCAPE ) ::DestroyWindow(MainWindowHandle); return 0;

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

case WM_DESTROY: ::PostQuitMessage(0); return 0;

В самом конце оконной процедуры мы вызываем функцию DefWindowProc. Это стандартная оконная процедура. В приложении Hello World мы обрабатываем только три сообщения; стандартная оконная процедура определяет поведение при получении всех остальных сообщений, которые мы получаем, но решили не обрабатывать самостоятельно. Например, окно приложения Hello World может быть свернуто, развернуто на весь экран, закрыто, может быть изменен его размер. Вся эта функциональность предоставляется нам стандартной оконной процедурой, и нам не надо писать свои обработчики сообщений, реализующие эти функции. Обратите внимание, что функция DefWindowProc является частью Win32 API.


Введение в программирование для Windows


Чтобы использовать интерфейс программирования приложений (API) Direct3D необходимо создать приложение Windows (Win32-приложение) с главным окном в котором мы будем визуализировать наши трехмерные сцены. Данное приложение служит введением в написание приложений для Windows с использованием «чистого» Win32 API. В общих чертах Win32 API представляет собой набор низкоуровневых функций и структур, доступных из языка С и позволяющих нашему приложению и операционной системе Windows взаимодействовать друг с другом. Например, чтобы приказать Windows показать заданное окно, используется функция Win32 API ShowWindow.

Программирование для Windows — огромная тема, и это приложение знакомит только с теми моментами, которые необходимы при работе с Direct3D. Читатели, желающие больше узнать о программировании для Windows с использованием Win32 API, могут обратиться к ставшей классическим трудом по этой теме книге Чарльза Петзольда «Programming Windows» (к ее последнему, пятому изданию). Другим незаменимым ресурсом при работе с технологиями Microsoft является библиотека MSDN, которая обычно входит в Microsoft Visual Studio, но также доступна в Интернете по адресу www.msdn.microsoft.com. Вобщем, если вы встретите функцию или структуру Win32 о которой захотите узнать больше, откройте MSDN и выполните поиск по названию функции или структуры. В этом приложении мы достаточно часто будем отсылать вас за дополнительными сведениями о функциях и структурах к MSDN.

Цели

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

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



Ресурсы



Ресурсы

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



Рисунок Графический интерфейс



Рисунок 2. Графический интерфейс пользователя обычного приложения Windows. Клиентская область — это белое пространство в окне приложения. Обычно она используется для показа пользователю результатов работы программы. Создавая Direct3D-приложения мы используем эту область для визуализации наших трехмерных сцен




Рисунок Экранное пространство



Рисунок 4. Экранное пространство

<
Функция CreateWindow возвращает дескриптор созданного ею окна (значение типа HWND). Если создать окно не удалось, значение дескриптора равно нулю. Помните, что дескриптор— это способ сослаться на конкретное окно, управляемое Windows. Многие вызовы API требуют указания HWND, чтобы знать с каким окном производятся действия.

Последние два обращения к функциям API из функции InitWindowsApp предназначены для отображения окна. Сперва мы вызываем функцию ShowWindow и передаем ей дескриптор только что созданного окна, чтобы Windows знала, какое окно должно быть показано. Мы также передаем число, определяющее в каком виде будет показано окно (обычным, свернутым, развернутым на весь экран и т.д.). Здесь лучше всего указать значение nShowCmd, которое было передано нам в одном из аргументов WinMain. Конечно, вы можете жестко задать значение в коде, но это не рекомендуется. После отображения окна мы должны обновить его. Это делает функция UpdateWindow; она получает один аргумент, являющийся дескриптором обновляемого окна.

::ShowWindow(MainWindowHandle, show); ::UpdateWindow(MainWindowHandle);

Если в функции InitWindowsApp были выполнены все описанные выше действия, значит инициализация завершена; мы возвращаем true, чтобы сообщить об успешном завершении функции.


Рисунок Окно приведенной выше



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




Рисунок Управляемая событиями модель программирования



Рисунок 1. Управляемая событиями модель программирования




События, сообщения, очередь сообщений и цикл обработки сообщений



События, сообщения, очередь сообщений и цикл обработки сообщений

Приложения Windows следуют управляемой событиями модели программирования (event-driving programming model). Обычно приложение Windows просто сидит и ждет пока не произойдет какое-нибудь событие (event) (приложение может выполнять фоновую работу— то есть выполнять какие-то задачи, когда не происходит никаких событий). События генерируются во многих случаях; наиболее общие примеры — нажатие клавиш, щелчки мыши, создание, перемещение, сворачивание, развертывание и закрытие окон, изменение размеров и отображение окна.

Когда происходит событие Windows отправляет приложению сообщение (message), уведомляющее о событии, и помещает его в очередь сообщений (message queue) приложения, которая представляет собой обычную очередь, где хранятся поступившие приложению сообщения. Приложение постоянно проверяет состояние очереди в цикле обработки сообщений (message loop), и, когда обнаруживает в очереди новое сообщение, направляет его оконной процедуре (window procedure) того окна, которому данное сообщение предназначено. (Вспомните, что у приложения может быть несколько окон.) Оконная процедура — это специальная функция, связанная с окном приложения. (У каждого окна должна быть оконная процедура, но несколько окон могут совместно использовать одну оконную процедуру. Следовательно нет необходимости писать для каждого окна отдельную оконную процедуру.) В оконной процедуре мы реализуем обработку различных сообщений. Например, мы можем реализовать завершение работы приложения при нажатии клавиши Esc. Для этого в оконной процедуре следует написать:

case WM_KEYDOWN: if( wParam == VK_ESCAPE ) ::DestroyWindow(MainWindowHandle); return 0;

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

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



Создание и отображение окна



Создание и отображение окна

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

HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HANDLE hInstance, LPVOID lpParam );

lpClassName — Имя класса (завершающаяся нулем строка), которое было указано в зарегистрированной структуре WNDCLASS, описывающей параметры окна, которое мы хотим создать. Передавайте имя класса, указанное в той структуре WNDCLASS, которую вы хотите использовать при создании окна.

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

dwStyle — Описывает стиль создаваемого окна. Используемое в примере Hello World значение WS_OVERLAPPEDWINDOW является комбинацией флагов WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX и WS_MAXIMIZEBOX. Имена флагов описывают соответствующие характеристики окна. Полный список стилей приведен в MSDN.

x — Позиция по горизонтали верхнего левого угла окна в экранной системе координат.

y — Позиция по вертикали верхнего левого угла окна в экранной системе координат.

nWidth — Ширина окна в пикселях.

nHeight — Высота окна в пикселях.

hWndParent — Дескриптор окна, которое является родителем данного. Создаваемое в примере окно не имеет взаимоотношений с другими окнами, поэтому данному параметру присваивается 0.

hMenu — Дескриптор меню. В приложении Hello World нет меню, поэтому данному параметру присваивается 0.

hInstance — Дескриптор экземпляра приложения с которым связано данное окно.

lpParam — указатель на определяемые пользователем данные.

ПРИМЕЧАНИЕ

Когда мы указываем координаты окна (x, y), они отсчитываются относительно верхнего левого угла экрана. При этом положительные значения по оси X отсчитываются, как обычно, вправо, а вот положительные значения по оси Y отсчитываются вниз. Эта система координат, называемая экранными координатами (screen coordinates) или экранным пространством (screen space), показана на Рисунок  4.



Улучшенный цикл сообщений


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

int Run() { MSG msg;

while(true) { if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { if(msg.message == WM_QUIT) break;

::TranslateMessage(&msg); ::DispatchMessage(&msg); } else // выполнение кода игры } return msg.wParam; }

После объявления переменной msg, мы попадаем в бесконечный цикл. Сперва мы вызываем функцию API PeekMessage, которая проверяет наличие сообщений в очереди. Описание аргументов этой функции смотрите в MSDN. Если в очереди есть сообщение, функция возвращает true, после чего мы обрабатываем полученное сообщение. Если PeekMessage возвращает false, мы выполняем код нашей игры.



Включение файлов, глобальные переменные и прототипы



Включение файлов, глобальные переменные и прототипы

Первое, что мы должны сделать— включить заголовочный файл windows.h. Включив этот файл мы получаем доступ к объявлениям структур, типов и функций, необходимых для использования базовых элементов Win32 API.

#include <windows.h>

Вторая инструкция — это объявление глобальной переменной с типом HWND. Это сокращение обозначает «дескриптор окна» (handle to a window). Программируя для Windows вы часто будете пользоваться дескрипторами для ссылок на внутренние объекты Windows. В данном примере мы используем HWND чтобы ссылаться на главное окно приложения, управление которым осуществляют внутренние механизмы Windows. Мы должны сохранить дескриптор нашего окна потому что многие вызовы API требуют, чтобы им был передан дескриптор того окна, над которым они должны произвести действия. Например, функция UpdateWindow получает один параметр типа HWND, используемый для того, чтобы сообщить ей, какое окно должно быть обновлено. Если мы не передадим дескриптор, функция не будет знать какое окно ей обновлять.

HWND MainWindowHandle = 0;

В следующих трех строках находятся объявления функций. Говоря кратко, InitWindowsApp создает и инициализирует главное окно приложения, Run является оберткой для цикла обработки сообщений нашего приложения, а WndProc — это оконная процедура главного окна нашей программы. Мы подробно исследуем эти функции, когда дойдем до того места кода, где они вызываются.

bool InitWindowsApp(HINSTANCE instanceHandle, int show); int Run(); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);



Windows-приложение Hello World


Ниже приведен код полнофункциональной и очень простой программы для Windows. Лучше всего просто следовать за кодом. В следующих разделах мы исследуем его строка за строкой. Мы рекомендуем вам в качестве упражнения создать проект в вашей среде разработки, вручную набрать код, скомпилировать его и запустить. Обратите внимание, что выбирая тип проекта надо указать Win32Application, а не Win32 Console Application.

///////////////////////////////////////////////////////////////////// // // Файл: hello.cpp // // Автор: Фрэнк Д. Луна (C) All Rights Reserved // // Система: AMD Athlon 1800+ XP, 512 DDR, Geforce 3, Windows XP, // MSVC++ 7.0 // // Описание: Демонстрация создания приложения для Windows. // /////////////////////////////////////////////////////////////////////

// Включение заголовочного файла, содержащего все объявления структур, // типов данных и функций Win32 API необходимых для Windows-программы. #include <windows.h>

// Дескриптор главного окна. Используется для идентификации // главного окна, которое мы создадим HWND MainWindowHandle = 0;

// Обертка для кода, необходимого для инициализации приложения Windows. // Функция возвращает true если инициализация произведена успешно // и false в ином случае. bool InitWindowsApp(HINSTANCE instanceHandle, int show);

// Обертка для кода цикла сообщений. int Run();

// Оконная процедура, обрабатывающая получаемые нашим окном // сообщения LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Эквивалент main() для Windows int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nShowCmd) { // Сперва мы создаем и инициализируем наше приложение Windows // Обратите внимание, что значения hInstance и nShowCmd // передаются WinMain в параметрах. if(!InitWindowsApp(hInstance, nShowCmd)) { ::MessageBox(0, "Init - Failed", "Error", MB_OK); return 0; }

// После создания и инициализации приложения мы входим // в цикл обработки сообщений. В нем мы остаемся до тех пор // пока не будет получено сообщение WM_QUIT, говорящее, что // работа приложения должна быть завершена.
return Run(); // вход в цикл сообщений }

bool InitWindowsApp(HINSTANCE instanceHandle, int show) { // Первая задача при создании окна - описать его // характеристики путем заполнения структуры WNDCLASS WNDCLASS wc;

wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = instanceHandle; wc.hIcon = ::LoadIcon(0, IDI_APPLICATION); wc.hCursor = ::LoadCursor(0, IDC_ARROW); wc.hbrBackground = static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)); wc.lpszMenuName = 0; wc.lpszClassName = "Hello";

// Затем мы регистрируем описание класса окна в Windows // чтобы потом мы смогли создать окно с объявленными // характеристиками if(!::RegisterClass(&wc)) { ::MessageBox(0, "RegisterClass - Failed", 0, 0); return false; }

// После регистрации описания нашего класса окна мы можем // создать окно с помощью функции CreateWindow. // Обратите внимание, что функция возвращает значение HWND // для созданного окна, которое мы сохраняем в переменной // MainWindowHandle. В дальнейщем переменная MainWindowHandle // позволит обращаться именно к тому окну, которое мы создали. MainWindowHandle = ::CreateWindow( "Hello", "Hello", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, instanceHandle, 0);

if(MainWindowHandle == 0) { ::MessageBox(0, "CreateWindow - Failed", 0, 0); return false; }

// Теперь мы отображаем и обновляем только что созданное // окно. Обратите внимание, что в качестве параметра обоим // функциям передается значение MainWindowHandle, чтобы они // знали какое именно окно надо отображать и обновлять. ::ShowWindow(MainWindowHandle, show); ::UpdateWindow(MainWindowHandle);

return true; }

int Run() { MSG msg; ::ZeroMemory(&msg, sizeof(MSG));

// Цикл выполняется, пока мы не получим сообщение WM_QUIT. // Функция GetMessage возвращает 0 (false) только когда // получено сообщение WM_QUIT, что приводит к выходу из цикла. while(::GetMessage(&msg, 0, 0, 0)) { // Трансляция сообщения и его перенаправление // соответствующей оконной процедуре. ::TranslateMessage(&msg); ::DispatchMessage(&msg); }



return msg.wParam; }

LRESULT CALLBACK WndProc( HWND windowHandle, UINT msg, WPARAM wParam, LPARAM lParam) { // Обработка заданных сообщений: switch( msg ) { case WM_LBUTTONDOWN: // Если нажата левая кнопка мыши, // отображаем диалоговое окно. ::MessageBox(0, "Hello, World", "Hello", MB_OK); return 0;

case WM_KEYDOWN: // Если нажата клавиша Esc, уничтожаем // главное окно приложения, идентифицируемое // дескриптором MainWindowHandle. if( wParam == VK_ESCAPE ) ::DestroyWindow(MainWindowHandle); return 0;

case WM_DESTROY: // Если получено сообщение о завершении работы, // отправляем сообщение, которое завершит работу // цикла обработки сообщений. ::PostQuitMessage(0); return 0; }

// Переправляем все остальные сообщения, которые // наша программа не обрабатывает сама, системной // процедуре обработки сообщений. return ::DefWindowProc(windowHandle, msg, wParam, lParam); }


WinMain


WinMain

WinMain в мире Windows является аналогом функции main в обычном программировании на C++. Прототип WinMain выглядит так:

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow );

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

hPrevInstance — В 32-разрядных системах не используется и равно 0.

lpCmdLine — Строка с аргументами командной строки, указанными при запуске программы.

nCmdShow — Вариант отображения окна приложения. Наиболее часто используются варианты SW_SHOW (отображение окна указанных размеров в заданной позиции), SW_SHOWMAXIMIZED (отображение окна, развернутого на весь экран) и SW_SHOWMINIMIZED (отображение свернутого окна). Полный список вариантов отображения и соответствующих констант приведен в библиотеке MSDN.

Если работа функции WinMain завершается успешно, она должна вернуть член wParam сообщения WM_QUIT. Если работа функции завершена до входа в цикл обработки сообщений, она должна вернуть 0. Идентификатор WINAPI определен следующим образом:

#define WINAPI __stdcall

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

ПРИМЕЧАНИЕ

В прототипе функции WinMain в примере Hello World, для третьего параметра мы указали тип PSTR, а не LPSTR. Это объясняется тем, что в 32-разрядных системах Windows больше нет «дальних указателей». PSTR — это просто указатель на строку символов (т.е., char*).

WNDCLASS и регистрация



WNDCLASS и регистрация

Из WinMain мы обращаемся к функции InitWindowsApp. Как уже сообщалось, эта функция выполняет действия, необходимые для инициализации программы. Давайте перейдем к ее коду и исследуем его. InitWindowsApp возвращает true или false— true, если инициализация успешно завершена, false если что-то не получилось. Как видно из кода WinMain, мы передаем функции InitWindowsApp копию дескриптора экземпляра нашего приложения и переменную, задающую режим отображения окна. Сама функция WinMain получает эти два значения в своих параметрах.

if(!InitWindowsApp(hInstance, nShowCmd))

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

typedef struct _WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS;

style — Задает стиль окна. В нашем примере мы используем комбинацию флагов CS_HREDRAW и CS_VREDRAW. Она означает, что окно будет перерисовываться при изменении его размеров по горизонтали или по вертикали. Полный список стилей с их описанием приведен в библиотеке MSDN.

wc.style = CS_HREDRAW | CS_VREDRAW;

lpfnWndProc — Указатель на оконную процедуру. Именно здесь устанавливается связь оконной процедуры с окном. Таким образом окна, созданные на основе одного и того же экземпляра структуры WNDCLASS будут совместно использовать одну и ту же оконную процедуру. Сама оконная процедура будет рассмотрена чуть позже в разделе «Оконная процедура».

wc.lpfnWndProc = WndProc;

cbClsExtra и cbWndExtra — Это дополнительные области памяти, которые вы можете использовать в своих собственных целях. В рассматриваемой программе дополнительные области памяти не нужны и поэтому обоим параметрам присваивается 0.

wc.cbClsExtra = 0; wc.cbWndExtra = 0;


hInstance — Поле для дескриптора экземпляра нашего приложения. Вспомните, что этот дескриптор был передан нам через функцию WinMain.

wc.hInstance = instanceHandle;

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

wc.hIcon = ::LoadIcon(0, IDI_APPLICATION);

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

wc.hCursor = ::LoadCursor(0, IDC_ARROW);

hbrBackground — Это поле определяет цвет фона клиентской области окна. В нашем примере мы вызываем функцию GetStockObject, которая возвращает дескриптор кисти указанного нами цвета. Описание других встроенных кистей можно найти в MSDN.

wc.hbrBackground = static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH));

lpszMenuName — Задает меню окна. В нашем приложении нет меню, поэтому значение данного поля равно 0.

wc.lpszMenuName = 0;

lpszClassName — Определяет имя создаваемого класса окна. Вы можете выбрать любое имя. В нашем приложении класс называется «Hello». Имя используется для идентификации структуры данных класса, чтобы мы могли обращаться к ней в дальнейшем.

wc.lpszClassName = "Hello";

После того, как мы описали параметры класса нашего окна, нам надо зарегистрировать его в Windows. Это выполняется с помощью функции RegisterClass, которая получает указатель на структуру WNDCLASS. В случае успешного завершения функция возвращает 0.

if(!::RegisterClass(&wc))