Наконец-то! Наконец-то! Сегодня мы начнём создавать полноценное окно Windows. Прощай убогая консоль!!!

К этому моменту вы уже должны неплохо знать синтаксис C++, уметь работать с ветвлениями и циклами, хорошо понимать работу функций. Если вы справились с морским боем, можете считать, что всё это вы усвоили.

Венгерская форма записи

Весь код, который мы встретим в WinAPI написан в венгерской форме. Это такое соглашение по написанию кода.

При этом перед именем переменной ставится начальная буква типа. Все слова в именах переменных и функций начинаются с заглавной буквы.

Вот несколько префиксов:

b - переменная типа bool.
l - переменная типа long integer.
w - от word (слово) - 16 бит. Переменная типа unsigned short.
dw - от double word (двойное слово) - 32 бита. Переменная типа unsigned long.
sz - строка заканчивающаяся нулём (string terminated zero). Просто обычная строка, которую мы постоянно использовали.
p или lp - указатель (от pointer). lp (от long pointer) - данные указатели перешли из прошлого. Сейчас lp и p означают одно и то же.
h - описатель (от handle).

Например, указатель будет называться вот так:

void* pData;

Данная форма записи используется Microsoft. Многие критикуют этот способ именования переменных. Но подобные вещи (соглашения о кодировании) в больших компаниях жизненно необходимы.

Напомню, что идентификаторы констант обычно состоят только из заглавных букв: WM_DESTROY. WM_DESTOY - это 2, константа определена через define.

Кроме того, в winAPI используется очень много переопределённых типов. Вот на этой страничке - http://msdn.microsoft.com/en-us/library/aa383751(VS.85).aspx , можете найти описания всех типов Windows (на английском).

И ещё одна вещь, которую мы не разбирали. Указателям часто присваивается значение NULL. Считайте, что это просто 0 и указатели которым присвоено значение NULL (ноль), не указывают ни на какой участок памяти.

Windows API (WinAPI)

Все программы под Windows используют специальный интерфейс программирования WinAPI. Это набор функций и структур на языке C, благодаря которым ваша программа становится совместимой с Windows.

Windows API обладает огромными возможностями по работе с операционной системой. Можно даже сказать - безграничными.

Мы не рассмотрим даже один процент всех возможностей WinAPI. Первоначально я хотел взять больше материала, но это заняло бы слишком много времени, и увязнув в болоте WinAPI, до DirectX'а мы добрались бы через пару лет. Описание WinAPI займёт два урока (включая этот). В них мы рассмотрим только каркас приложения под Windows.

Программа под Windows точно так же как и программа под DOS, имеет главную функцию. Здесь эта функция называется WinMain.

Функция WinMain

Программа под Windows состоит из следующих частей (всё это происходит внутри WinMain):

Создание и регистрация класса окна. Не путайте с классами C++. WinAPI написана на C, здесь нет классов в привычном для нас понимании этого слова.
Создание окна программы.
Основной цикл, в котором обрабатываются сообщения.
Обработка сообщений программы в оконной процедуре. Оконная процедура представляет собой обычную функцию.
Вот эти четыре пункта - основа программы Windows. В течение этого и следующего урока мы разберём всё это подробно. Если вы запутаетесь в описании программы, то вернитесь к этим пунктам.

Теперь разберём всё это подробно:

WinAPI: Структура WNDCLASS

Прежде всего нужно создать и заполнить структурную переменную WNDCLASS, а затем на её основе зарегистрировать оконный класс.

Вот как выглядит эта структура:

код на языке c++
typedef struct {
  UINT style;          // стиль окна
  WNDPROC lpfnWndProc; // указатель  на оконную процедуру
  int cbClsExtra;      // дополнительные байты после класса. Всегда ставьте 0
  int cbWndExtra;      // дополнительные байты после экземпляра окна. Всегда ставьте 0
  HINSTANCE hInstance; // экземпляр приложения. Передаётся  в виде параметра  в WinMain
  HICON hIcon;           // иконка приложения
  HCURSOR hCursor;       // курсор приложения
  HBRUSH hbrBackground;  // цвет фона
  LPCTSTR lpszMenuName;  // имя меню
  LPCTSTR lpszClassName; // имя класса
} WNDCLASS, *PWNDCLASS;


Структура WNDCLASS в составе WinAPI определяет базовые свойства создаваемого окна: иконки, вид курсора мыши, есть ли меню у окна, какому приложению будет принадлежать окно...

После того как вы заполните эту структуру, на её основе можно зарегистрировать оконный класс. Речь идёт не о таких классах как в C++. Скорее можно считать, что оконный класс это такой шаблон, вы его зарегистрировали в системе, и теперь на основе этого шаблона можно создать несколько окон. И все эти окна будут обладать свойствами, которые вы определили в структурной переменной WNDCLASS.

WinAPI: Функция CreateWindow

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

код на языке c++
HWND CreateWindow(
  LPCTSTR lpClassName,  // имя класса
  LPCTSTR lpWindowName, // имя окна (отображается  в заголовке)
  DWORD dwStyle, // стиль окна
  int x,         // координата  по горизонтали  от левого края экрана
  int y,         // координата  по вертикали  от верхнего края экрана
  int nWidth,    // ширина окна
  int nHeight,   // высота окна
  HWND hWndParent, // родительское окно
  HMENU hMenu,     // описатель меню
  HINSTANCE hInstance, // экземпляр приложения
  LPVOID lpParam       // параметр; всегда ставьте NULL
);


Если в оконном классе (структуре WNDCLASS) задаются базовые свойства окна, то здесь - более специфические для каждого окна: размер окна, координаты...

Данная функция возвращает описатель окна. С помощью описателя можно обращаться к окну, это примерно как идентификатор.

Обратите внимание, что здесь очень много новых типов. На самом деле они все старые, просто переопределены. Например: HWND - это переопределение типа HANDLE, который в свою очередь является переопределением PVOID, который в свою очередь является переопределением void*. Как глубоко закопана правда! Но всё же тип HWND - это указатель на void.

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

Это что касается оконного режима. Довольно долго мы будем практиковаться с DiectX именно в окне - не будем пользоваться полноэкранным режимом.

Обработка сообщений (Message handling)

Основным отличием всех наших предыдущих программ от программ под Windows является обработка сообщений.

Например, когда пользователь нажимает какую-нибудь клавишу на клавиатуре, генерируется сообщение, что была нажата клавиша. Затем это сообщение поступает в приложение, которое было активным, когда пользователь нажал клавишу.

Здесь у нас произошло событие (event) - была нажата клавиша.

Событием может быть: перемещение курсора мыши, смена фокуса приложения, нажатие клавиши клавиатуры, закрытие окна. Событий очень много. Очень! За секунду в операционной системе может происходить десятки событий.

Так вот, когда происходит какое-либо событие, операционная система создаёт сообщение: была нажата такая-то клавиша, координаты курсора мыши изменились, открылось новое окно.

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

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

код на языке c++
typedef struct tagMSG {
HWND hwnd;      // окно, которое получит это сообщение
UINT message;   // код сообщения
WPARAM wParam;  // параметр
LPARAM lParam;  // параметр
DWORD time;     // время, когда произошло сообщение
POINT pt;       // координаты курсора мыши
} MSG;


Обратите внимание, как с помощью typedef переопределяются структуры.

Чтобы создать данную структуру можно воспользоваться следующим кодом:

MSG msg;

Всё. Дальше можете использовать эту структуру как обычно.

код на языке c++
msg.messgae == 2;          // эти две строки эквивалентны так как
msg.message == WM_DESTROY; // константа WM_DESTROY равна двум


Здесь, поле, в котором содержится код сообщения (имя сообщения, сравнивается с константой WM_DESTROY. WM - от Windows Message (сообщение Windows). WM_DESTROY - это сообщение, которое генерируется при закрытии окна (destroy - уничтожить).

Коды сообщений определены с помощью констант и имеют префикс WM_: WM_CLOSE, WM_CREATE и др.

В структуре MSG встречается тип HWND - от Window Handle (дескриптор окна или описатель окна). Это такая штука, которая "описывает" окно. Это что-то вроде идентификатора (имени окна).

Запомните это слово - handle (описатель, дескриптор). В Windows это понятие используется очень часто. Почти все типы Windows, которые начинаются с H - описатели: описатель иконки, описатель шрифта, описатель экземпляра приложения. Их штук тридцать насколько я помню.

Все взаимодействия между приложениями в Windows осуществляются с помощью этих самых описателей окон (HWND).

Существует ещё один важный описатель - описатель приложения (HINSTANCE - первый параметр WinMain) - это уникальный идентификатор приложения, благодаря которому операционная система не сможет перепутать две разных программы. Это примерно как штрих-код. Мы рассмотрим его позже.

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

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

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

Оконная процедура (Window procedure - WndProc)

Продолжаем с того момента, как сообщение попало в очередь сообщений приложения. Как только до него дошла очередь, оно обрабатывается. Для обработки сообщений в каждой программе должна существовать специальная функция - оконная процедура. Обычно она называется WndProc (от Window Procedure). Вызов оконной процедуры расположен в основном цикле программы и выполняется при каждой итерации цикла.

Сообщения (в виде структурных переменных MSG) попадают в данную функцию в виде параметров: описатель окна, идентификатор сообщения и два параметра. Обратите внимание, что в оконную процедуру не передаются поля time и pt. То есть сообщение уже "разобрано".

Внутри оконной процедуры расположено ветвление switch, в котором идёт проверка идентификатора сообщения. Вот пример простой оконной процедуры (она полностью рабочая):

код на языке c++
//  не обращайте пока внимания  на HRESULT  и __stdcall. Мы рассмотрим их позже
HRESULT __stdcall WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
  case WM_PAINT:
    // код обработки сообщения WM_PAINT
    return 0;
  case WM_DESTROY:
    // код обработки сообщения WM_DESTROY
    return 0;
  }
  // обработчик всех остальных сообщений
}

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

Вот, в общем-то, и всё на сегодня. Уже видно, что программа под WinAPI намного сложнее программы под DOS. Как я уже писал выше, в следующем уроке мы разберём код работающей программы.

В качестве упражнения создайте новый проект. В окне New Project (Новый проект) выберите шаблон (template) - Win32Project (до сих пор мы выбирали Win32 Console Application). В одном из следующих окон не ставьте флажок Empty Project (пустой проект) и IDE сгенерирует заготовку программы.

Если вы внимательно посмотрите на код файла имя_проекта.cpp, то вы обнаружите все вещи, которые мы обсуждали: структурную переменную MSG, заполнение структуры WNDCLASS, создание окна функцией CreateWindow, основной цикл программы. Кроме того, в файле определена функция WndProc. В ней происходит обработка нескольких сообщений в ветвях switch: WM_COMMAND, WM_PAINT, WM_DESTROY. Найдите всё это в файле.

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

На сегодня всё.