Любое графическое приложение Windows начинается с создания окна. В этой статье я расскажу, как создать простейшее окно на C++ с помощью WinAPI.
1. Для начала создадим пустой проект в Visual Studio.
2. Добавим файл main.cpp
Исходные файлы -> Добавить -> Создать элемент
3. Изменим тип подсистемы на Windows.
Проект -> Свойства -> Свойства конфигурации -> Компоновщик -> Система -> Подсистема -> Windows
4. Чтобы начать работу с WinAPI, подключим заголовочный файл Windows.h
#include <Windows.h>
5. Точка входа
Точка входа в WinAPI отличается от точки входа консольного приложения. Она может быть WinMain или wWinMain.
int WINAPI WinMain (
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
);
int WINAPI wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd
);
Разберем отдельно каждый параметр.
1. hInstance. Это дескриптор нашего приложения. Каждое приложение, запущенное в Windows, имеет свой дескриптор. На самом деле, это просто адрес в памяти, на котором располагается наше приложение.
2. hPrevInstance. Этот параметр всегда равен нулю. Он использовался во времена 16-разрядного Windows, но сейчас не имеет смысла
3. lpCmdLine. Это указатель на командную строку, с которой запускается приложение. Это то же самое, что argv[0] в консоли. В случае WinMain это ASCII-строка, в случае wWinMain - Unicode-строка.
4. nShowCmd. Это флаг, указывающий, будет ли главное окно приложения свернуто, развернуто или показано обычным образом.
*WINAPI - Это соглашение о вызовах. Оно определяет, каким образом в функцию передаются параметры. То же самое, что __stdcall. Можете об этом не задумываться.
Подробнее про точку входа можно прочитать здесь: https://learn.microsoft.com/RU-RU/windows/win32/learnwin32/winmain--the-application-entry-point
6. Класс окна
Прежде чем создать окно, мы должны зарегистрировать его класс. Вот кусок кода:
WNDCLASSW wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = DefWindowProcW;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = L"MAIN_WINDOW_CLASS";
RegisterClassW(&wc);
Разберем отдельно каждую строчку.
1. WNDCLASS. Это структура, которая хранит информацию о классе окна. В именах WinAPI постфикс "W" означает, что мы будем использовать Unicode, а постфикс "A" - ASCII. Мы будем использовать Unicode, значит пишем WNDCLASSW.
2. wc.style - стиль класса окна. CS_HREDRAW означает, что окно перерисовывается, если изменяется ширина окна, а CS_VREDRAW - если изменяется высота
3. wc.lpfnWndProc - указатель на оконную процедуру. Оконная процедура - это процедура, которая обрабатывает сообщения, которые операционная система посылает нашему окну. Саму процедуру мы напишем в следующей статье, а пока укажем в оконную процедуру по умолчанию - DefWindowProc.
4. wc.cbClsExtra - это для прошаренных, просто ставим 0.
5. wc.cbWndExtra - это тоже для прошаренных, просто ставим 0.
6. wc.hInstance - это дескриптор приложения, которое содержит оконную процедуру.
7. wc.hIcon - это дескриптор иконки нашего окна. Функция LoadIconW(NULL, IDI_APPLICATION) загружает иконку по умолчанию.
8. wc.hCursor - это дескриптор курсора нашего окна. Функция LoadCursorW(NULL, IDC_ARROW) загружает курсор по умолчанию.
9. wc.hbrBackground - это дескриптор кисти, которое закрашивает наше окно. Пока ставим NULL.
10. wc.lpszMenuName - это имя меню окна. У нас нет меню, ставим NULL.
11. wc.lpszClassName - это имя класса окна. Мы будем использовать его при создании окна. Префикс "L" означает, что мы используем Unicode.
12. RegisterClassW(&wc) - функция RegisterClass регистрирует в операционной системе класс окна. Функция принимает в качестве аргумента указатель на структуру WNDCLASS.
NB: Если вы используете WNDCLASSA, то регистрируйте класс с помощью RegisterClassA, а если WNDCLASSW, то с помощью RegisterClassW.
7. Создание окна
Окно создается с помощью функции CreateWindow:
HWND hWnd = CreateWindowW(L"MAIN_WINDOW_CLASS", L"Simple Window", WS_OVERLAPPEDWINDOW, 200, 200, 1280, 720, NULL, NULL, hInstance, NULL);
- Первый параметр - имя класса окна, которое мы указали в wc.lpszClassName.
- Второй параметр - имя самого окна. Этот текст будет написан в заголовке окна.
- Третий параметр - стиль окна. WS_OVERLAPPEDWINDOW - стиль перекрывающегося окна. Подробнее про стили окон можно прочитать в официальной документации Microsoft.
- Четвертый и пятый параметры - координаты левого верхнего угла окна по горизонтали и вертикали соответственно. Точка отсчета располагается в левом верхнем углу экрана
- Шестой и седьмой параметры - ширина и высота окна соответственно.
- Восьмой параметр - дескриптор родительского окна. Он используется при создании дочерних окон. Так как наше окно не дочернее, ставим NULL.
- Девятый параметр - дескриптор меню. Ставим NULL.
- Десятый параметр - дескриптор нашего приложения.
- Одиннадцатый параметр - указатель на значение, которое будет передано окну при его создании. Не используем его, ставим NULL.
Функция CreateWindow возвращает дескриптор окна, которое было создано. Дескриптор окна имеет тип HWND. Сохраним его в переменную hWnd - она нам еще понадобится.
NB: Если вы используете WNDCLASSA, то создавайте окно с помощью CreateWindowA, а если WNDCLASSW, то с помощью CreateWindowW.
8. Отображение окна
Мы создали наше окно, но мы его еще не видим. Чтобы отобразить окно на экране, необходимо вызвать функцию ShowWindow, которая принимает два аргумента: дескриптор окна и флаг отображения.
ShowWindow(hWnd, nShowCmd);
nShowCmd - это четвёртый параметр точки входа WinMain.
*Не забудьте написать в конце функции WinMain
return 0;
Если вы все сделали правильно, то при запуске приложения должно появиться окно и сразу же исчезнуть.
Это происходит потому, что сразу после создания окна программа заканчивает свое выполнение. Чтобы этого не происходило, мы должны добавить в программу главный цикл.
9. Главный цикл
MSG msg;
while (GetMessageW(&msg, hWnd, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
- MSG - это структура, которая хранит информацию о сообщении от операционной системы.
- Функция GetMessage получает от операционной системы сообщение и принимает четыре агрумента: Указатель на структуру сообщения, дескриптор окна и два фильтра сообщений. Мы будем передавать в окно все сообщения, поэтому поставим фильтры на 0.
- Функция TranslateMessage принимает указатель на полученное сообщение и преобразует некоторые виды сообщений.
- Функция DispatchMessage отправляет полученное сообщение в оконную процедуру.
Теперь, если вы запустите приложение, то получите рабочее окошко, которое можно перемещать, растягивать, сжимать, сворачивать и закрывать.
Однако наше окно является еще пустышкой: мы не можем никаким особенным образом с ним взаимодействовать.
NB: После закрытия окна приложение продолжит работать, так как мы не обработали сообщение закрытия окна. Не забудьте снять задачу в диспетчере задач
Чтобы обрабатывать такие сообщения, как нажатия клавиш, кнопок мыши, закрытие окна, изменение размеров окна, необходимо написать свою оконную процедуру. В следующей статье я расскажу как это делать.
10. Полезные ссылки
Официальная документация Microsoft: