Первые две строки являются директивами препроцессора, которые сообщают ему, что до того, как начать процесс компиляции модуля, следует вставить в указанное место файлы заголовков (stdafx.h и API.h). Первый файл мы обсуждали в уроке 1. Он содержит директивы подключения библиотечных файлов-заголовков. Директива
//======== Исключает редко используемые элементы
//======== Windows-заголовков
#define WIN32_LEAN_AND_MEAN
уменьшает размер исполняемого модуля, так как исключает те фрагменты каркаса приложения, которые редко используются. Второй файл (API.h) создал мастер. Вы можете открыть его с помощью окна Solution Explorer и увидеть, что он содержит лишь две строки:
#pragma once
#include"resource.h"
Директива fpragma once сообщает компилятору, что данный файл (API.h) должен быть использован при построении кода приложения только один раз, несмотря на возможные повторы (вида #include "API.h"). Вторая директива подключает файл с идентификаторами ресурсов. Сами ресурсы вы видите в окне Resource View. Все ресурсы приложения и их отдельные элементы должны быть идентифицированы, то есть пронумерованы. Новичкам рекомендуется открыть файл resource.h с помощью окна Solution Explorer и просмотреть его содержимое. В этом файле символическим именам (идентификаторам) IDS_APP_TITLE, IDR_MAINFRAME и т. д. соответствуют числовые константы, которые препроцессор вставит вместо идентификаторов еще до процесса компиляции. В конце файла содержатся пометки Studio.Net, определяющие дальнейший способ нумерации ресурсов различного типа. Рассматриваемый файл не рекомендуется редактировать вручную, так как в случае ошибок вы получите труднолокализуемые отказы. Studio.Net сама следит за состоянием resource.h, вставляя и удаляя макроподстановки #def ine по мере того, как вы редактируете ресурсы с помощью специальных редакторов.
Возвращаясь к коду заготовки, отметим, что далее следует объявление глобальных переменных
HINSTANCE hlnst; // Текущий экземпляр
TCHAR szTitle[MAX_LOADSTRING];
// Текст заголовка окна
TCHAR szWindowClass[MAX_LOADSTRING];
// Текст регистрации
Рассматривайте описатель hlnst как адрес исполняемого модуля в пространстве процесса, соответствующего приложению. Если вы не знакомы с понятиями поток и процесс, то обратитесь к последнему уроку этой книги, где приведены некоторые сведения об архитектуре Windows. Текст регистрации szWindowClass будет загружен из ресурсов при выполнении winMain (см. вызов LoadString).
Примечание 1
Примечание 1
Этот текст представляет собой строку символов «API», которая хранится в ресурсах. Ее можно увидеть, раскрыв дерево ресурсов в окне Resource View, узел String Table и дважды щелкнув на элементе String Table (group). С помощью этой строки ваше приложение регистрируется в операционной системе.
При вызове функции winMain система передает ей параметры:
Ранее в Win 16 второй параметр использовался в целях экономии ресурсов, но в Win32 — это NULL, так как каждый экземпляр приложения теперь выполняется в своем собственном виртуальном адресном пространстве процесса емкостью 4 Гбайт. Все экземпляры процесса загружаются начиная с одного и того же адреса в этом пространстве (см. последний урок). Теперь рассмотрим алгоритм функции WinMain:
Основные атрибуты главного окна приложения задаются в структуре типа WNDCLASSEX. Понятие класс окна появилось до того, как появились классы C++. Поэтому структура WNDCLASSEX не имеет ничего общего с классами MFC. Она является типом структур языка С. Дело в том, что каждое Windows-приложение должно зарегистрировать атрибуты своего окна, а система использует их при создании окна. Структура WNDCLASSEX своими полями определяет некий шаблон или модель для создания окон данного класса. В полях структуры вы указываете необходимые атрибуты окна: адрес исполняемого модуля приложения, .адрес оконной процедуры, имя ресурса меню, набор битов для описания стиля окна, местонахождение изображения курсора, значка и т. д. Эту «неинтересную» работу выполняет большая часть кодов функции MyRegisterClass. Используя классы MFC, вы избавляете себя от подобной рутинной работы.
При регистрации класса окон (точнее, переменной типа WNDCLASSEX) операционная система связывает оконную процедуру (WndProc) с приложением. В winMain вы должны зарегистрировать главное окно приложения, остальные же окна, если они нужны, могут быть зарегистрированы и в других местах программы. Адрес заполненной структуры передается в функцию RegisterClassEx, которая говорит Windows, что от нее ожидается, когда окна подобного класса появляются на экране. Теперь система знает, какой вид курсора использовать при попадании указателя мыши в пределы окна, кому посылать сообщения о событиях, происходящих в области окна, какие значки (большой 32 х 32 и маленький 16 х 16) будут связаны с приложением и т. д. Функция RegisterClassEx возвращает число типа АТОМ (16-ти битное целое без знака), которое соответствует строке регистрации в таблице атомов, поддерживаемой системой.
После регистрации класса главного окна идет вызов функции Initlnstance, которая пытается создать окно (CreateWindow) зарегистрированного класса. Если система нашла класс окна в трех поддерживаемых ею списках зарегистрированных классов окон, то функция CreateWindow возвращает описатель окна (HWND). Мы запоминаем его для того, чтобы использовать в качестве параметра при вызове других функций. Если нет, то функция вернет нулевое значение и приложение молча завершает свою работу. Попробуйте при вызове CreateWindow вставить пустую строку, вместо szWindowClass. Запустив приложение, вы поймете, что в ветвь if (ihwnd) неплохо бы вставить вызов:
MessageBox(0,"Не нашла класс окна","Ошибка",МВ_ОК);
При успешном создании окна происходит вызов функций
//====== Показать окно
ShowWindow(hWnd, nCmdShow);
//====== Сделать это, минуя очередь
UpdateWindow(hWnd);
Далее в коде winMain загружается таблица акселераторов (соответствий клавиатурных комбинаций командам меню), которая используется в цикле ожидания и обработки сообщений. Функция GetMessage выбирает сообщение из очереди сообщений потока и помещает его в структуру типа MSG, служащей для хранения информации о сообщении Windows. Функция TranslateAccelerator пытается транслировать (преобразовать) сообщения WM_KEYDOWN (нажата клавиша) или WM_SYSKEYDOWN (нажата F10 или ALT+другая клавиша) в сообщения WM_COMMAND или WM_SYSCOMMAND, но только в том случае, если в таблице акселераторов присутствует код клавиши.
Преобразованное сообщение направляется непосредственно в оконную процедуру. Характерно то, что TranslateAccelerator ждет завершения обработки сообщения. Функция TranslateMessage транслирует сообщения, поступившие в виде виртуальных кодов клавиш, в символьные сообщения и снова отправляет их в очередь сообщений потока для того, чтобы отреагировать на него на следующей итерации цикла. Например, сообщение WM_KEYDOWN (virtual key message) она может преобразовать в WM_CHAR (character message) или в WM_DEADCHAR (см. MSDN). Функция DispatchMessage отправляет сообщение оконной процедуре.
Коротко алгоритм работы winMain может быть сформулирован так. После выполнения инициализирующих действий функция WinMain входит в цикл обработки сообщений. После выхода из этого цикла работа приложения завершается. Выход происходит, когда придет сообщение WM_QUIT. Обычно его посылает оконная процедура, когда пользователь закрывает главное окно.
Стартовая заготовка иллюстрирует стандартную последовательность действий при создании Windows-приложения на базе API-функций. Обратите внимание на то, что функция WndProc нигде явно не вызывается, хотя именно она выполняет всю полезную работу. Для проверки усвоения прочитанного ответьте на вопрос: «Когда и кем она вызывается?»