«Hello Windows!» в классовой обертке
«Hello Windows!» в классовой обертке
Простейшая Windows программа
Перевод А. И. Легалова
Англоязычный оригинал находится на сервере компании Reliable Software
Прежде, чем приступить к программированию для Windows, необходимо понять, как выполняется представленная здесь самая простая программа. Обратите внимание: это — Win32-программа. Она будет выполняться под управлением Windows 95 и Windows NT (если кто-то хочет, чтобы Вы программировали для 16-разрядной платформы, то он должен платить Вам вдвое больше!). Windows API вызовы окрашены в синий цвет, а специфические для Windows типы данных окрашены зеленым. Я буду также ставить два двоеточия перед обращениями к функциям API. В C++ это означает вызов глобальной функции и позволяет, в ряде случаев, избавиться от неоднозначности.
Исходные тексты программы расположены на сервере Reliable software (чужого стараюсь не держать [А.Л.]). Не забудьте откомпилировать его как приложение Windows. Например, в Visual C++ выберите File.New.Projects.Win32 Application. Иначе вы получите ошибку: нерешенный внешний _main. (я обеспечил проектным файлом тех, кто используют MS VC++ 6.0)
Сначала Вы должны определить классы окон, которые будут отображаться вашим приложением. В данном случае мы отобразим только одно окно (WinClass), но тем не менее, мы должны дать Windows некоторую минимальную информацию относительно его класса. Наиболее важная часть WinClass — адрес процедуры обратного вызова, или оконной процедуры (WindowProcedure). Windows, в соответствии с внутренней организацией, вызывает нас — он посылает сообщения нашей программе, вызывая оконную процедуру.
Обратите внимание на объявление оконной процедуры. Windows будет вызывать ее, передавая дескриптор текущего окна и два элемента данных, связанных с сообщением (параметры сообщения): WPARAM и LPARAM.
В WinClass мы также должны определить дескриптор экземпляра программы HINSTANCE, курсор мыши (мы лишь загружаем стандартный курсор – стрелку), кисть, чтобы закрасить фон окна (мы выбрали заданный по умолчанию цвет окна) и имя нашего класса.
После заполнения всех полей структуры WNDCLASS мы регистрируем класс окна в Windows.
#include <windows.h>
LRESULT CALLBACK WindowProcedure(HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam);
class WinClass {
public:
WinClass (WNDPROC winProc, char const * className, HINSTANCE hInst);
void Register() {
::RegisterClass (&_class);
}
private:
WNDCLASS _class;
};
WinClass::WinClass(WNDPROC winProc, char const * className, HINSTANCE hInst) {
_class.style = 0;
_class.lpfnWndProc = winProc; // оконная процедура: обязательна
_class.cbClsExtra = 0;
_class.cbWndExtra = 0;
_class.hInstance = hInst; // владелец класса: обязательный
_class.hIcon = 0;
_class.hCursor = ::LoadCursor (0, idc_arrow); // optional
_class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // optional
_class.lpszMenuName = 0;
_class.lpszClassName = className; // обязательно
}
Как только Класс Окна зарегистрирован, мы можем продолжить создание окна. Для этого вызываем функцию API CreateWindow. У нее много параметров: имя только что зарегистрированного класса окна, заголовок, появляющийся в названии окна, стиль, положение на экране и размер, а также дескриптор экземпляра приложения. Остальные параметры в данный момент оставлены обнуленными.
Окно не будет появляться на экране до тех пор, пока Вы не скажете, чтобы Windows показал его.
class WinMaker {
public:
WinMaker(): _hwnd (0) {}
WinMaker (char const* caption, char const* className, HINSTANCE hInstance);
void Show (int cmdShow) {
::ShowWindow(_hwnd, cmdshow);
::UpdateWindow(_hwnd);
}
protected:
HWND _hwnd;
};
WinMaker::WinMaker(char const * caption, char const * className, HINSTANCE hInstance) {
_hwnd = :: CreateWindow(
className, // имя регистрируемого оконного класса
caption, // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, // позиция x
CW_USEDEFAULT, // позиция y
CW_USEDEFAULT, // ширина
CW_USEDEFAULT, // высота
0, // handle to parent window
0, // handle to menu
hInstance, // дескриптор экземпляра
0); // дата создания (window creation data)
}
Windows программа управляется событиями. Это означает, что Вам, как программисту, полагается находиться в обороне. Пользователь будет бомбардировать Windows различными внешними действиями, а Windows будет бомбардировать вашу программу сообщениями, соответствующими этим действиям. Все, что Вы должны делать — это отвечать на сообщения. Рисунок ниже схематично показывает как все это работает.
Каждое сообщение адресовано определенному окну. Когда Вы запрашиваете сообщение у Windows, система выяснит класс вашего окна, найдет связанную с ним оконную процедуру, и вызовет ее. Любое сообщение, посланное нашему окну может обрабатываться в нашей оконной процедуре. Нам остается только отреагировать на его. И что? Мы должны отвечать соответстветствующим образом на всевозможные сообщения Windows? Там их сотни! К счастью, нет! Мы должны перехватывать только те сообщения, в которых заинтересованы. Все остальные мы возвращаем обратно в Windows для обработки по умолчанию, используя DefWindowProc.
Windows получает различные события от клавиатуры, мыши, портов, и т.д. Каждое событие быстро преобразуется в сообщение. Windows посылает сообщения, соответствующим окнам. Например, все сообщения от клавиатуры идут к окну, которое в настоящее время имеет фокус ввода (активное окно). Сообщения мыши посылаются согласно позиции курсора мыши. Они обычно идут к окну, которое расположено непосредственно под курсором (если какая-нибудь программа не захватила мышь).
Все эти сообщения заканчиваются в очередях сообщений. Windows поддерживает очередь сообщений для каждой выполняющейся прикладной программы (фактически, для каждого потока). Ваша задача состоит в последовательном получении этих сообщений в так называемом цикле сообщений (message loop). Для этого программа должна вызывать GetMessage. Затем вызывается DispatchMessage, чтобы отдать сообщение обратно Windows. Разве сама Windows не может посылать все эти сообщения самостоятельно? В принципе это возможно, но цикл сообщений дает вашей программе возможность посмотреть на них и, если это необходимо, выполнить некоторые дополнительные действия перед возвратом. Или не выполнять…
Давайте рассмотрим WinMain. Выполнение Windows программы не начинается с функции main — оно начинается с WinMain. Сначала, мы создаем winclass и регистрируем его. Затем мы создаем экземпляр окна (на основе только что зарегистрированного класса) и отображаем его. В общем случае WinMain вызывается с соответствующей директивой show. Пользователь может запустить приложение со свернутым или развернутым окном. Так что мы только следуем этой директиве. Затем мы запускаем цикл сообщения, в котором обрабатываем и посылаем сообщения до тех пор, пока GetMessage не возвратит 0. В этот момент параметр wparam сообщения будет содержать код возврата для всей программы.
int WINAPI WinMain(hinstance hinst, hinstance hprevinst, char * cmdParam, int cmdShow) {
char className [] = "Winnie";
WinClass winClass(WindowProcedure, className, hInst);
winClass.Register();
WinMaker win("Hello Windows!", className, hInst);
win.Show(cmdShow);
MSG msg;
int status;
while ((status = ::GetMessage(&msg, 0, 0, 0)) != 0) {
if (status == –1) return –1;
::DispatchMessage(&msg);
}
return msg.wParam;
}
Функция API GetMessage – интересный пример причудливой troolean (в противоположность традиционной Boolean) логики Microsoft. GetMessage определена таким образом, чтобы возвратить BOOL, но в документации определяется три варианта возвращаемых значений: ненулевых, нуля и –1. Я это не выдумал! Приведу цитату из справки:
Если функция передает сообщение, иное чем WM_QUIT, возвращаемое значение отлично от нуля.
Если же функция выдает сообщение WM_QUIT, возвращаемое значение — нуль.
При возникновении ошибки возвращаемое значение равно –1.
Другая часть Windows программы — оконная процедура. Помните, что Windows вызывает ее при обработке всех сообщений. Эти сообщения могут игнорироваться, если их пересылать к DefWindowProc. Только одно сообщение мы всегда обязаны перехватывать. Это WM_DESTROY, посылаемое самой Windows в тот момент, когда пользователь закрывает окно (нажимая кнопку закрытия в заголовке окна). Стандартный ответ на WM_DESTROY заключается в посылке сообщения о выходе из программы со значением нуля в качестве кода возврата. Вот и все, что можно сказать по данной теме.
// Window Procedure called by Windows
LRESULT CALLBACK WindowProcedure(HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_DESTROY:
::PostQuitMessage (0);
return 0;
}
return ::DefWindowProc(hwnd, message, wparam, lparam);
}
Выше рассмотрена самая "уродливая" и менее всего поддающаяся инкапсуляции (при попытке использовать объектно-ориентированный подход) часть Windows . Она станет гораздо лучше, если разрабатывать программу так, как это предлагается в обобщенной Windows программе.