netlib.narod.ru< Назад | Оглавление | Далее >

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

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

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

Первое, что мы должны сделать — включить заголовочный файл 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);

WinMain

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

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

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

#define WINAPI __stdcall

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

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

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;

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

if(!::RegisterClass(&wc))

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

После того, как мы зарегистрировали в 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
);

 

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

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

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


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

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

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

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

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

Успешно завершив инициализацию, мы переходим к сердцу программы — циклу обработки сообщений. В программе 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;

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

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

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

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

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

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

Наша оконная процедура обрабатывает три сообщения: 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.

Функция MessageBox

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

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

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


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz