пятница, 23 сентября 2011 г.

Простейшая Windows программа

Простейшая Windows программа

(как смог, так и перевел http://relisoft.com/win32/winnie.html)

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

Вызовы Windows API выделены синим, а определенные в Windows типы данных показаны зеленым. Я буду обычно ставить двойное двоеточие в API вызовах. В C++, это просто способ вызова глобальных функций, на случай избегания двусмысленностей.

Для проверки работоспособности кода надо создать проект типа Win32 Application.

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

Объявление оконной процедуры (WindowProcedure). Windows вызывает ее с дескриптором окна (handle), сообщением (message) и двумя переменными (данные сообщения) связанными с сообщением, параметры типа WPARAM и LPARAM.

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

Как только все поля WINDCLASS заполнены, мы регистрируем класс в системе 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); // опция
    _class.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // опция
    _class.lpszMenuName = 0;
    _class.lpszClassName = className; // обязательно имя класса окна
}

Как только класс окна зарегистрирован мы можем создать окно. Это делается вызовом CreateWindow, принимающим следующие аргументы:

 имя класса окна, которое мы зарегистрировали; заголовок окна; стиль окна;  позиция; размер; экземпляр приложения. Эта часть программы может также быть заключена в C++ класс, WinMaker.

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,                    // дескриптор родительского окна
        0,                    // дескриптор меню
        hInstance,            // дескриптор экземпляра приложения
        0);                   // данные окна
}

Windows программа управляется событиями. Это означает, что вы, как программист, должны построить оборону. Поскольку, пользователь бомбардирует Windows различными действиями ввода, а Windows будет бомбардировать вашу программу сообщениями соответствующими этим действиям. Всё, что вы можете – это отвечать на эти сообщения. На рисунке схематично показано как это происходит.

Windows обрабатывает различные события от клавиатуры, мыши, портов и т.д. Каждое событие быстро конвертируется в сообщение. Windows доставляет сообщения в соответствующие окна. Все сообщения клавиатуры идут в окно, которое в настоящее время имеет фокус ввода (активное окно). Сообщения мыши посылаются в соответствии с позицией курсора мыши. Они обычно поступают в окно, которое прямо под курсором.

Все эти сообщения выстраиваются в очередь сообщений. Windows создает очередь сообщений для каждого запущенного приложения (фактически для каждого потока). Ваша обязанность отлавливать сообщения одно за другим в петле (цикле) сообщений. Ваша программа содержит вызов GetMessage извлекающий сообщение из очереди. Потом вы вызываете DispatchMessage передовая его обратно в Windows. Может ли Windows выполнятся дальше без задержки и доставки всех этих своих сообщений? В принципе это возможно, но петля сообщений дает вашей программе шанс посмотреть их и может быть совершать какие-либо дополнительные действия перед их отправкой. Или нет …

Каждое сообщение адресовано особому окну. Когда вы говорите Windows доставлять какое-либо сообщение, то это предполагает некоторый уникальный класс окна, поиск соответствующей оконной процедуры, и вызов ее. Каждый сигнал сообщение, посланное вашему окну, будет направлено вашей оконной процедуре. Которая будет отвечать на сообщение. Стоит ли отвечать на каждый возможный тип сообщения Windows? Сообщений может быть сотни. Так или иначе, мы передаем управление Windows, по умолчанию используя DefWindowProc.





 

Давайте взглянем на WinMain. Исполняемая в Windows программа не стартует с main, она стартует с WinMain. В нашей WinMain, мы создаем WinClass и регистрируем его. Потом мы создаем окно (класс которого только что зарегистрировали) и показываем его. Фактически, WinMain вызывается с соответствующей директивой показа, пользователь может захотеть стартовать приложение минимизированным или максимизированным. Далее, мы определяем петлю сообщений и продолжаем посылку сообщений до тех пор, пока 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;
}

 

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

 

// Оконная процедура вызываемая 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 );
}