Мой блог
WinUI 3 без XAML: как написать декларативный GUI на чистом C++23 с помощью библиотеки wxl
Современный фреймворк WinUI 3 позиционируется компанией Microsoft в качестве ключевого фундамента для разработки десктопных приложений под Windows. Однако традиционная связка C++/WinRT и XAML нередко отпугивает разработчиков: долгая компиляция, запутанная иерархия на основе CRTP-миксинов, огромное количество шаблонной рутины и громоздкий синтаксис. Библиотека wxl (WinUI XAML-Less) предлагает кардинально иной подход — создание декларативного графического интерфейса на чистом C++23 с минимизацией накладных расходов WinRT.
Почему именно wxl?
Название wxl расшифровывается как WinUI XAML-Less. Это компактная архитектурная надстройка над WinUI 3, позволяющая описывать пользовательский интерфейс прямо в коде на C++23 в декларативном стиле — аналогично тому, как это сделано во Flutter или SwiftUI. При этом тяжелая стандартная проекция C++/WinRT изолирована внутри библиотеки и больше не раздувает единицы трансляции конечного проекта мегабайтами заголовков и скрытыми вызывами COM-интерфейсов.
Библиотека отлично показывает себя как в небольших утилитах из пары диалоговых окон, так и в масштабных приложениях. В отличие от декларативной разметки XAML, стандартный C++ позволяет легко декомпозировать GUI, повторно использовать фрагменты интерфейса, применять обычные циклы и условные операторы. Для примера, панель кнопок калькулятора в wxl описывается следующим образом:
[&](iterate<L"C÷×√" L"789-" L"456+" L"123%" L"±0.="> key) {
return Button {
key.text(),
fontSize = 18,
fontWeight = 600,
style = face(key),
hAlign.stretch,
vAlign.stretch,
row = key.index / 4 + 1,
column = key.index % 4,
onClick = [symbol = key.value]() { type(symbol); },
};
}
Роль XAML в современном GUI под Windows
Возникает естественный вопрос: оправдан ли полный отказ от XAML? На инфраструктурном уровне XAML решает важные задачи — управление шаблонами контролов, презентерами, словарями ресурсов и стилизацией тем. Он позволяет изменять внешний вид приложения путем подгрузки сторонних XAML-ресурсов без пересборки C++ кода.
Разработчики wxl не отказываются от продуманной системы ресурсов WinUI 3, а используют её в качестве надежного фундамента. Однако для описания структуры окон и логики взаимодействия декларативный C++ оказывается значительно удобнее и быстродейственнее.
Графический бэкенд и мощь Windows Composition API
WinUI 3 тесно интегрирован с системным графическим композитором Windows Composition API (Visual Layer). Корни этой технологии уходят в DirectComposition времен Windows 8, но именно в Windows 10/11 композитор достиг технологической зрелости. Это высокопроизводительный графический движок, работающий выше базового уровня DirectX, но ниже уровня готовых виджетов.
Композитор берёт на себя отрисовку фигур, теней, эффектов, а также управление анимациями в отдельном потоке. Благодаря этому плавный скроллинг, эффекты наведения мыши (hover) и трансформации выполняются без задержек, даже если основной поток GUI временно загружен вычислениями. Нативные приложения, работающие поверх Windows Composition API, показывают производительность, сравнимую с оптимизированными 3D-играми.

Три фундаментальные сложности сложного GUI
Создание графической библиотеки с нуля — крайне трудоемкая задача. На определенном этапе любой разработчик сталкивается с тремя масштабными проблемами:
- Рендеринг и редактирование текста: Полноценный текстовый контрол (TextBox) по сложности сопоставим с отдельным текстовым редактором, а RichTextControl требует реализации форматирования уровня офисных пакетов.
- Автоматизация интерфейса (GUI Automation): Внешние утилиты и алгоритмы искусственного интеллекта должны считывать структуру окна, вводить текст и нажимать кнопки для автоматического тестирования и управления.
- Доступность (Accessibility): Поддержка экранных дикторов, контрастных тем и обхода визуального дерева для пользователей с ограниченными возможностями.
Именно поэтому рациональным решением является использование готовых высокоуровневых компонентов WinUI 3, обернутых в удобный интерфейс на C++.
Декларативный интерфейс на чистом C++23
Архитектура wxl решает основные проблемы традиционной разработки под Windows, обеспечивая лаконичный синтаксис, исключая проникание типов WinRT в публичные заголовки и выстраивая полноценную иерархию классов C++ с кешированием COM-интерфейсов.
Как выглядит декларативный код wxl
Ниже приведен пример создания окна с карточкой, темой и кнопкой без единой строчки XAML-кода:
#include "pch.h"
using namespace wxl;
using namespace wxl::dsl;
wxl::Teardown wxl_launched() {
auto window = Window {
title = L"Hello from WXL",
minSize = {500, 430},
Grid {
rowDefinitions = L"auto,*",
Card {
row = 1,
Rows {
TextBlock { L"A modern C++23 library", styles.textBlock.subtitle },
Button {
content = L"Click",
toolTip = L"Похвалить библиотеку",
onClick = {content = L"Thank You!"},
},
},
},
},
};
window.activate();
return {};
}
Присваивания внутри фигурных скобок вычисляются в отложенные операции, а вариативный конструктор последовательно применяет их к созданному объекту. Конструкция onClick = {content = L"Thank You!"} создает обработчик, который автоматически транслирует новые значения обратно объекту-источнику.
Борьба с мегабайтами заголовков C++/WinRT
Официальная проекция C++/WinRT включает свыше 30 МБ заголовочных файлов, распределенных по 252 файлам. Компилятор вынужден повторно анализировать этот массив данных в каждой единице трансляции. В библиотеке wxl применяется генератор winui-srcgen, который формирует минимальную проекцию по JSON-профилю с учетом графа зависимостей.
В результате объем заголовочных файлов снизился до 574 КБ (почти в 50 раз меньше оригинала), а вся бинарная реализация занимает около 1 МБ. Публичный интерфейс wxl полностью изолирован от имен winrt::, ABI-указателей и типов HRESULT. Исходники C++/WinRT парсятся лишь единожды при сборке самой библиотеки wxl.
Замеры времени сборки демонстрируют существенный прирост скорости:
- Пересборка
main.cppтестового приложения с линковкой исполняемого файла занимает всего 4,7 секунды. - Полная пересборка файла библиотеки с повторным разбором проекции cppwinrt требует 30,8 секунды.
Настоящая иерархия классов вместо CRTP-миксинов
В стандартном C++/WinRT класс Button не наследуется от UIElement в привычном понимании ООП. Вместо этого используется набор шаблонов CRTP (consume_X<D>), а приведение к базовым типам выполняется через вызов .as<T>(). Это приводит к раздуванию бинарного кода за счет генерации дубликатов функций для каждого типа.
В wxl реализовано прямое наследование: wxl::Button наследует wxl::ButtonBase, который восходит к wxl::UIElement и wxl::Object. Публичные обертки не содержат полей данных — все состояние скрыто в приватной цепочке Impl. Благодаря этому скомпилированный код методов базовых классов существует в единственном экземпляре для всех наследников.
Ленивое кэширование и однократный QueryInterface
Каждый объект WinRT поддерживает множество интерфейсов (например, UIElement реализует IUIElement, IAnimationObject, IVisualElement и др.). В стандартной проекции обращение к методам базовых интерфейсов сопровождает цепочка вызовов QueryInterface, AddRef и Release с атомарными операциями.
В wxl интерфейсы запрашиваются лениво через поля структуры Impl при первом обращении:
template <auto member>
member_type<member> const& Object::Impl::get() {
auto& field = static_cast<class_type<member>&>(*this).*member;
if (!field) {
field = inspectable_.as<member_type<member>>(); // Выполняется один раз
}
return field;
}
Все последующие обращения выполняют только быструю проверку указателя на nullptr. Поскольку все виджеты создаются и функционируют исключительно в главном STA-потоке (Single-Threaded Apartment), операции со счетчиками ссылок обходятся без межпотоковых блокировок.
Удобные хелперы для рутинных задач
Для ускорения разработки библиотека wxl содержит набор встроенных DSL-хелперов:
minSize = {500, 430}— перехват системного сообщенияWM_GETMINMAXINFOдля ограничения размеров окна;placement— сохранение и корректное восстановление позиции окна на активном мониторе;rowDefinitions = L"auto,*"— декларативная разметка сетки Grid без ручного наполнения коллекций;toolTip = L"..."— единовременная установка всплывающей подсказки и имени для экранного диктора;brushes.Card.BackgroundFillColor— автоприменение цвета из активной системной темы;wxl::DrawingSurface— прямая интеграция Direct2D с поверхностью композитора;wxl::accept_file_drops— встроенная обработка Drag-and-Drop файлов;wxl::UiThread— безопасный вызов функций в интерфейсном потоке из фоновых задач.
Где изучить исходники и примеры
Ознакомиться с практической реализацией библиотеки можно в репозитории проекта:
samples/HelloHere— базовое окно с карточкой, настройкой темы и кнопкой;samples/Calculator— пример полнофункционального калькулятора с сеткой кнопок и обработкой ввода;wxl.gen/profiles/— настраиваемые профили генератора проекций под задачи конкретного проекта.
Источник: habr.com
