Здравствуйте. Эта статья посвящена самому началу работы с Direct2D(Первая в серии, дальше будут продолжения), поскольку в документации от Microsoft мало что сказано о том, что вообще нужно сделать для создания окна и прочих вещей, а также слишком много «воды». Таким образом, это альтернатива началу документации от Microsoft.
Структура будет построена как ряд вопросов и ответов на них. Так как всё сводится к понятию окно, первым делом будет дано объяснение, что это такое в Windows.
Что такое окно в Windows?
Если просто, - как и всё остальное, - это N структура. Правильно заполнив, зарегистрировав и вызвав соответствующие функции, вы получите на экране белый прямоугольник определённого стиля.
Но прямого доступа к структуре окна мы не имеем, лишь к её части - структуре класса окна WNDCLASSEXW. Обратившись к MSDN, получим следующее:
typedef struct WNDCLASSEXW {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
HICON hIconSm;
} WNDCLASS;
Сообщения окна. Оконная процедура
Прежде чем начать заполнение структуры, важно рассказать об этих двух вещах. Каждое ваше действие в Windows - это сообщение. Сообщение обрабатывается оконной процедурой (функцией). Её синтаксис:
LRESULT CALLBACK WindowProc(
HWND hwnd, // Получатель (может отличаться от MSG.hwnd для дочерних окон)
UINT uMsg, // Идентификатор сообщения
WPARAM wParam, // Дополнительные данные
LPARAM lParam // Дополнительные данные
);
hwnd - это ID (идентификатор) окна; uMsg - число, по которому определяется тип сообщения (системные - от 0x0000 до 0x03FF, пользовательские, возникающие из-за действий пользователя, - от 0x0400 до 0x7FFF и от 0xC000 до 0xFFFF). Ссылка подробней; в wParam и lParam передаются дополнительные данные (например, если uMsg == WM_MOUSEMOVE, то в wParam - состояние кнопок мыши, а в lParam - координаты x и y (младшее и старшее слово)). Список всех сообщений можно найти в документации. Пример функции:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
// Инициализация при создании окна
return 0;
case WM_PAINT:
// Обработка перерисовки
return 0;
case WM_DESTROY:
PostQuitMessage(0); //Отправляет сообщение WM_QUIT
return 0;
default:
// Сообщения, которые мы не обработали, передаем на обработку по умолчанию
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
Заполнение WNDCLASSEXW
Зная, что для регистрации нужна заполненная структура, произведём необходимое:
1.Размер, достаточно указать в значение результат sizeof(WNDCLASSEXW)
1.Стиль окна определяет как визуальное отображение, так и его поведение. Список стилей.
2.Ссылка на оконную процедуру (описана выше).
3. и 4. Название говорит само за себя, но четвёртый параметр - для каждого отдельного дочернего окна.
5. HInstance - это базовый адрес в памяти, по которому загружен исполняемый модуль (EXE или DLL); его значение передаётся через точку входа.
6. и 7. и 8. Названия говорят сами за себя.
9. То самое меню, которое отображается у программ.
10. Название класса окна в Unicode.
11. Иконка окна.
Минимальный пример заполнения:
WNDCLASSEXW wcew = { 0 };
wcew.cbSize = sizeof(WNDCLASSEXW); // Обязательно: размер структуры
wcew.lpfnWndProc = WindowProc; // Указатель на оконную процедуру
wcew.hInstance = hInstance; // Экземпляр приложения
wcew.hCursor = LoadCursor(NULL, IDC_ARROW); // Курсор - стрелка
wcew.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // Фон - цвет окна
wcew.lpszClassName = L"MyWindowClass"; // Уникальное имя класса
WindowProc и hInstance указаны как бы вне; полноценный пример будет позже.
Регистрация WNDCLASSEXW. Создание окна
Для регистрации достаточно вызвать RegisterClassExW, передав в аргументы ссылку на структуру WNDCLASSEXW, то есть: RegisterClassExW(&wcew).
Теперь создание окна происходит при помощи вызова функции CreateWindowExW, результатом которого является ID окна - HWND. Аргументы функции:
dwExStyle - дополнительный стиль окна, изменяющий поведение. Список;
lpClassName - тип создаваемого окна или элемента управления;
lpWindowName - название окна;
dwStyle - определяет внешний вид и поведение окна. Таблица(прокрутить вниз);
X - позиция окна по оси X;
Y - позиция окна по оси Y;
nWidth - ширина окна;
nHeight - высота окна;
hWndParent - родительское окно;
hMenu - меню или ID контрола;
hInstance - экземпляр приложения;
lpParam - дополнительные параметры (указатель на любые данные).
Зная все нужные параметры, можно просто написать:
HWND hwnd = CreateWindowExW(0, wcew.lpszClassName, L"Minimal Window",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
400, 300, NULL, NULL, hInst, NULL);
Если при создании не указать стиль его видимости, то это необходимо сделать вручную через ShowWindow. Данная функция управляет отображением окна: первый аргумент - это ID окна (HWND), второй - тип видимости (список). Также чтобы не было проблем с отображением окна, вызывается функция UpdateWindow, которая немедленно перерисовывает окно и принимает один аргумент - ID окна. В итоге имеем следующее:
ShowWindow(hwnd,3);
UpdateWindow(hwnd);
Точка входа
До этого момента точка входа не рассматривалась, так как были более важные вещи. Теперь рассмотрим и её:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow)
{
return 0;
}
WINAPI - это макрос, который определяет, как функция получает параметры (справа налево) и кто очищает стек (сама функция).
hInstance - это ID приложения.
hPrev - ID предыдущего приложения; в x32 и x64 Windows не используется.
lpCmdLine - аргументы, указанные при запуске в командной строке (аргумент под индексом 0 не содержит названия программы).
int nCmdShow - флаг отображения окна(Список. Прокрутить вниз). Передаётся от программы-инициатора. То есть если программа запускается с скрытым окном, то это поведение сохранится и для нового.
Приём и обработка сообщений, передача оконной процедуре. Финал
И сразу код:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
MSG - cодержит информацию о сообщении из очереди сообщений (описание).
GetMessage - получение сообщения. Первый аргумент - ссылка на структуру; второй - ID окна (null - все окна текущего потока); третий аргумент - первое сообщение для фильтрации; четвёртый аргумент - последнее сообщение для фильтрации (0 - нет фильтра).
TranslateMessage - преобразует сообщения от клавиатуры в специальные сообщения; если сообщение не от клавиатуры - пропускает. Принимает ссылку на структуру сообщения.
DispatchMessage - передаёт структуру MSG оконной процедуре. Принимает ссылку на структуру сообщения.
Имея все пункты, соединяем воедино:
#include <Windows.h>
// Глобальное имя класса окна
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0); // Отправляем WM_QUIT для завершения приложения
return 0;
case WM_PAINT:
return 0;
}
// Сообщения, которые мы не обработали, передаем на обработку по умолчанию
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
const wchar_t* CLASS_NAME = L"MyWindowClass";
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow)
{
// 1. Заполнение структуры класса окна
WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(WNDCLASSEX); // Обязательно: размер структуры
wc.lpfnWndProc = WindowProc; // Указатель на оконную процедуру
wc.hInstance = hInstance; // Экземпляр приложения
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Курсор - стрелка
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // Фон - цвет окна
wc.lpszClassName = CLASS_NAME; // Уникальное имя класса
// 2. Регистрация класса окна
RegisterClassEx(&wc);
// 3. Создание окна
HWND hwnd = CreateWindowEx(
0, // Расширенные стили
CLASS_NAME, // Имя класса окна
L"Test Window", // Заголовок окна
WS_OVERLAPPEDWINDOW, // Стиль окна
100, 100, // Позиция (x, y)
400, 300, // Размер (width, height)
NULL, // Родительское окно
NULL, // Меню
hInstance, // Экземпляр приложения
NULL // Дополнительные параметры
);
// 4. Показать и обновить окно
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// 5. Цикл обработки сообщений
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
На этом, по сути, и всё. Дерзайте! Далее будет рассказ о рисовании, отображении различного контента и т.п.
Комментарии (11)

Serpentine
05.11.2025 19:09статья посвящена самому началу работы с Direct2D
Тогда что она делает в сишном хабе? Нет, если автор покажет рабочий код Direct2D на чистых сях (без костылей на плюсах), это будет номер!
в документации от Microsoft мало что сказано о том, что вообще нужно сделать для создания окна
А это что? Целый учебник с картинками: Get Started with Win32 and C++ (даю линк на английскую версию, т.к. перевод ИИ в MSDN на русский — кровь из глаз).
И почему в затравочном абзаце речь про DX, а тут резко упали до D2D?
структуре класса окна _WNDCLASS. Обратившись к MSDN, получим следующее
Почему WNDCLASSEX в "цитате" из MSDN и в статье обозвали "_WNDCLASS", когда у последнего (сиречь WNDCLASS) полей меньше? И ссылка дана на ansi версию, когда по ходу пьесы у нас вовсю юникод будет?

Serpentine
05.11.2025 19:09UPD. Что-то странное с примерами кода:
intWINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmd,intnCmdShow)...lpCmdLine - аргументы, указанные при
Это который из четырех?
Имея все пункты, соединяем воедино:
Соединили:
#include <Windows.h> LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); //Отправляет сообщение WM_QUIT return 0; case WM_PAINT: { return 0; } // Сообщения, которые мы не обработали, передаем на обработку по умолчанию return DefWindowProc(hwnd, uMsg, wParam, lParam); } const wchar_t* CLASS_NAME = L"MyWindowClass"; int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmd, int nCmdShow) { // 1. Заполнение структуры класса окна WNDCLASSEX wc = { 0 }; wc.cbSize = sizeof(WNDCLASSEX); // Обязательно: размер структуры wc.lpfnWndProc = WindowProc; // Указатель на оконную процедуру wc.hInstance = hInstance; // Экземпляр приложения wc.hCursor = LoadCursor(NULL, IDC_ARROW); // Курсор - стрелка wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // Фон - цвет окна wc.lpszClassName = CLASS_NAME; // Уникальное имя класса // 2. Регистрация класса окна RegisterClassEx(&wc)); // 3. Создание окна HWND hwnd = CreateWindowEx( 0, // Расширенные стили CLASS_NAME, // Имя класса окна L"Test Window", // Заголовок окна WS_OVERLAPPEDWINDOW, // Стиль окна 100, 100, // Позиция (x, y) 400, 300, // Размер (width, height) NULL, // Родительское окно NULL, // Меню hInstance, // Экземпляр приложения NULL // Дополнительные параметры ); // 4. Показать и обновить окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // 5. Цикл обработки сообщений MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; // Код завершения из WM_QUIT }Я нашел 2 причины, почему этот код вообще не скомпилируется. Это нейруха сгенерировала что ли, или просто влом было собирать перед публикацией?
Ну и:
return0; // Код завершения из WM_QUITНет, это обычный ноль!

Johnny_Depp Автор
05.11.2025 19:09Спасибо за замечания, были некоторые варианты, не до конца провёл форматирование итоговое и не заметил, хотя перечитывал

Johnny_Depp Автор
05.11.2025 19:09Возьмите любую функцию и откройте её в MSDN пролистайте вниз, внизу будет указано в какой она DLL , ну а дальше используете DLL как обычно.(Если хотите могу пример и на ассемблере x86 (NASM) скинуть, но в личку)
Ну вы сами понимаете, что есть только на английском, а это на русском.
Как ранее говорил, ошибка форматирования статьи, думал что исправил. Уже исправлено.
Спасибо за комментарий

morgot
05.11.2025 19:09Спасибо , конечно, за пересказ книги Петцольда 95 года. Надеюсь, дальше будет что-то интереснее.

Wolf4D
05.11.2025 19:09А зачем вообще выбирать Direct2D? Штука прямо ОЧЕНЬ специфическая, со своими приколами. Почему не Direct 3D, как делает 95% разработчиков? Почему не DirectDraw какой-нибудь?

Johnny_Depp Автор
05.11.2025 19:09Насчёт вопроса почему 2D, ну потому что рисуя двухмерное, не важное что это, при помощи Direct3D придётся игнорировать ещё одну ось(об этом кстати даже в интернете есть обсуждения).
Ну то есть это просто странно.
А насчёт DirectDraw - он давно устарел, "DirectDraw уже давно не используется, он был актуален в определённых версиях DirectX. Direct2D появился с DirectX10 и считается актуальным для 2D-графики." Вот ссылка подробней.
Einherjar
Ну и где тут хоть одна строка имеющая отношение к Direct2D?
Johnny_Depp Автор
Не совсем вас понял. Буквально всё, имеет к нему отношение. Ведь без окна , графика не возможна. Но если я выдам всё сразу, получится большее полотно, и используя здравый смысл - я разделил на множество статей.
Хотя прочитав ваш комментарий, я теперь слегка изменю и сделаю пометку, что это первая статья в серии.
Johnny_Depp Автор
"Direct2D разработан компанией Microsoft для создания приложений под управлением операционных систем Windows" - прямая цитата с поисковика. Ну вот, собственно, первая статья это начало, которое объясняет создание окна.
Einherjar
С архитектуры процессора тогда уж можно бы было начать, без него Direct2D графика тоже невозможна. Потом плавно перейти к устройству ядра windows, а только уж потом рассказывать о том как создавать окна на winapi.
Я уж не говорю про то что те, кому зачем-то может понадобиться в 2025 году напрямую работать с низкоуровневвм графическим апи вроде d2d, как правило уже в курсе что такое окно.