Win32 против MFC - Часть I

ОГЛАВЛЕНИЕ

Данная статья рассматривает архитектуру документ/вид и структуру обертки MFC.

Введение

Польза MFC упрощает жизнь программиста. Встает вопрос: "Что такое архитектура документ/вид?" Неясно, что это такое, и как MFC инкапсулирует Win32 API внутри. Возникает второй вопрос: "Где функция WinMain находится в приложении MFC?"

Основы приложений Win32

Программа Windows содержит минимум две функции: WinMain и wndProc. Каждой программе Windows нужна функция WinMain, являющаяся точкой входа. Вторая функция является оконной процедурой, обрабатывающей сообщения окна (Хотя название «оконная процедура» не слишком блестящее, так как в действительности она обрабатывает любые сообщения, соответствующие программе).
Сначала рассматривается устройство WinMain и его функциональность, а затем разбирается wndProc.

Функция WinMain делится на три части: объявление процедуры, инициализация программы и цикл обработки сообщений.

Когда программа начинает выполняться, Windows передает ей некоторую информацию, включая, в том числе, описатель текущего экземпляра приложения и описатель предыдущего экземпляра, при его наличии. Рассмотрим Win32 API, сгенерированный компилятором MSVC++:

int APIENTRY WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)

Первый параметр, hInstance, объявляется как int и является описателем выполняющегося в данный момент экземпляра приложения.
Второй параметр, hPrevInstance, является описателем ранее выполнявшегося экземпляра того же самого приложения. Однако в 32-битном API Windows NT этот параметр всегда равен NULL, независимо от выполнения нескольких экземпляров одного и того же приложения. Причина этого лежит в том, что в 32-битном Windows API каждая копия каждой программы выполняется в своем адресном пространстве и не делит ничего с другими выполняющимися в данный момент экземплярами из соображений безопасности.

Третий параметр, lpCmdLine, содержит параметры командной строки для программы.

Последний параметр, nCmdShow, используется для объявления стиля главного окна при первом запуске программы. Он сообщает Windows, как отобразить главное окно –  т.е. развернутым, свернутым и тому подобное.

Возвращаемое значение функции имеет прототип APIENTRY, определенный следующим образом:

#define APIENTRY WINAPI
и, в свою очередь, WINAPI объявлен так:
#define WINAPI __stdcall

А что такое __stdcall? К сожалению, ответ выходит за рамки данной статьи, поэтому он пропущен и оставлен читателю.

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

Инициализация предусматривает вызов трех процедур Windows API: RegisterClass (или ее расширенная версия RegisterClassEx), CreateWindow (или ее расширенный аналог CreateWindowEx) и ShowWindow (для этого API нет расширения).

Чтобы создать окно, надо заполнить члены структуры WNDCLASSEX и передать экземпляр этой структуры в RegisterClassEx API таким образом:

WNDCLASSEX wcl;
wcl.cbSize = sizeof(WNDCLASSEX);
wcl.hInstance = hInstance;
wcl.lpfnWndProc = (WNDPROC)wndProc;
wcl.style = CS_HREDRAW | CS_VREDRAW;
wcl.hIcon = LoadIcon(hInstance, IDC_ARROW);
wcl.hIconSm = NULL;
wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
wcl.lpszMenuName = NULL;
wcl.cbClsExtra = 0;
wcl.cbWndExtra = 0;
wcl.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wcl.lpszClassName = "myClass";

if(!RegisterClassEx(&wcl))
    return 0;

В результате этого Windows копирует элементы этой структуры куда-то в базу данных класса. Когда программа хочет создать окно, она ссылается на запись в базе данных класса, а затем Windows использует эти данные для создания окна. Ловко, да?

Сейчас пора рассмотреть члены этой структуры, но поскольку в ней есть много членов, выяснение функциональности каждого члена оставлено читателю. Однако нужен один из ее членов, так как он указывает на оконную процедуру - вторую функцию, которая будет в программе. Да, это член lpfnWndProc. Но подождите! Сначала надо закончить часть инициализации программы функции WinMain. После регистрации данных класса вызывается API CreateWindowEx для создания фактического окна.

HWND hWnd = CreateWindow("myClass",
    "WindowTitle",
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    NULL,
    NULL,
    hInstance,
    NULL);

Затем демонстрируется окно с помощью API ShowWindow следующим образом:

ShowWindow (hWnd, nCmdShow);

В этом и заключается суть инициализации программы. Теперь вернемся к члену lpfnWndProc структуры WNDCLASSEX. Этот член является "длинным указателем на функцию, именуемую оконной процедурой". Этот член был присвоен функции wndProc, поэтому в программе будет объявлена функция по имени wndProc, обрабатывающая сообщения окна.

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

MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

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

Цикл обработки сообщений состоит из трех основных API: GetMessage, втаскивающего сообщения в программу, TranslateMessage, преобразующего все сообщения нажатия клавиш в правильные значения символов и помещающего их в очередь приватных сообщений приложения как сообщение WM_CHAR, и последнего API DispatchMessage, проталкивающего извлеченное сообщение (msg) в оконную процедуру для обработки.

Имея данную информацию, изучим функциональность оконной процедуры. Помните, что уже был присвоен член lpfnWndProc структуры WNDCLASSEX в wndProc. Теперь посмотрим, как wndProc выглядит:

LRESULT CALLBACK wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}

Где LRESULT объявлен как тип данных long, и CALLBACK задает соглашение о вызовах __stdcall.

Оконная процедура вызывается всегда, когда генерируется сообщение. При необходимости можно обработать сообщение или пропустить сообщение и передать его API DefWindowProc. DefWindowProc делает все, что нужно для обеспечения работы окна. Microsoft предоставил исходный код этой процедуры в Windows SDK (комплект для разработки ПО). Кстати, при получении WM_DESTROY вызывается API PostQuitMessage, обрабатывающий процесс завершения работы приложения.