<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Personal Project on kenji.blog</title><link>http://kenji.blog/ru/tags/personal-project/</link><description>Recent content in Personal Project on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ru</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 04:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ru/tags/personal-project/index.xml" rel="self" type="application/rss+xml"/><item><title>Опыт разработки LogicPad: путь до релиза личного Windows-приложения</title><link>http://kenji.blog/ru/p/logicpad-windows-app-development-journey/</link><pubDate>Sun, 13 Sep 2026 04:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/logicpad-windows-app-development-journey/</guid><description>&lt;img src="http://kenji.blog/p/logicpad-windows-app-development-journey/img/eyecatch.jpg" alt="Featured image of post Опыт разработки LogicPad: путь до релиза личного Windows-приложения" />&lt;h2 id="1-введение-почему-именно-сейчас-стоит-создавать-нативные-приложения-для-windows">1. Введение: почему именно сейчас стоит создавать нативные приложения для Windows
&lt;/h2>&lt;p>В современной разработке приложений кроссплатформенные технологии, такие как Electron, Tauri и React Native, несомненно стали мейнстримом. Подход «написал один раз, работает везде» (Write Once, Run Anywhere) с использованием веб-технологий весьма рационален с точки зрения скорости разработки и простоты поддержки. Однако я намеренно выбрал путь создания «LogicPad» как нативного приложения, полностью оптимизированного под Windows.&lt;/p>
&lt;p>LogicPad — это симулятор цифровых логических схем и текстовый редактор, целевой аудиторией которого являются инженеры по аппаратному обеспечению и те, кто изучает логические схемы. Приложению необходимо моделировать десятки тысяч логических вентилей в реальном времени, одновременно отображая сложные волновые данные без задержек. В области, где требуется такая экстремальная производительность, паузы на сборку мусора длительностью в несколько миллисекунд (микрофризы) или накладные расходы на рендеринг веб-представления (WebView) приводят к фатальному ухудшению пользовательского опыта.&lt;/p>
&lt;p>В этой статье я подробно рассмотрю весь путь: от концепции разработки LogicPad, реализации на C++ и WinUI 3 (Windows App SDK), преодоления специфических технических барьеров, упаковки в формат MSIX, и до публикации для всего мира через Microsoft Store, сопровождая это глубокими техническими объяснениями. Я надеюсь, что мой опыт того, как независимый разработчик может создать нативное Windows-приложение корпоративного качества, станет ориентиром для тех, кто также решил бросить вызов нативной разработке.&lt;/p>
&lt;h2 id="2-график-проекта">2. График проекта
&lt;/h2>&lt;p>Разработка LogicPad велась как личный проект в выходные дни и по вечерам. Весь проект занял около полугода (6 месяцев). Ниже представлена диаграмма Ганта, показывающая ход выполнения проекта.&lt;/p>
&lt;pre class="mermaid">
gantt
title График разработки LogicPad
dateFormat YYYY-MM-DD
section &amp;#34;Планирование и дизайн&amp;#34;
&amp;#34;Анализ требований и прототипирование&amp;#34; :a1, 2025-10-01, 30d
&amp;#34;Проектирование архитектуры (Ядро C++)&amp;#34; :a2, after a1, 20d
section &amp;#34;Разработка ядра&amp;#34;
&amp;#34;Движок симуляции (C++)&amp;#34; :a3, after a2, 45d
&amp;#34;Интеграция WinUI 3 XAML&amp;#34; :a4, after a3, 40d
section &amp;#34;Улучшение UI/UX&amp;#34;
&amp;#34;Кастомная строка заголовка и окна&amp;#34; :a5, after a4, 15d
&amp;#34;Асинхронный диспетчер и многопоточность&amp;#34; :a6, after a5, 15d
section &amp;#34;Релиз и магазин&amp;#34;
&amp;#34;Упаковка MSIX и разрешения&amp;#34; :a7, after a6, 10d
&amp;#34;Сертификация Microsoft Store&amp;#34; :a8, after a7, 14d
&lt;/pre>
&lt;p>Как видно, большая часть времени разработки была потрачена на оптимизацию базового движка и интеграцию асинхронной обработки между WinUI 3 и C++. Несмотря на то, что нативная разработка имеет более высокие затраты на начальную настройку и обучение по сравнению с кроссплатформенной разработкой, эти инвестиции определенно окупаются в виде конечной производительности.&lt;/p>
&lt;h2 id="3-выбор-технологий-глубины-c--winui-3--windows-app-sdk">3. Выбор технологий: Глубины C++ / WinUI 3 / Windows App SDK
&lt;/h2>&lt;p>При разработке LogicPad выбор технологического стека был одним из самых важных решений. Исторически на платформе Windows существовали различные варианты нативных UI-фреймворков, такие как Win32 API (User32/GDI), MFC, Windows Forms, WPF, UWP и др. В настоящее время Microsoft рекомендует для разработки современных настольных приложений Windows использовать &lt;strong>WinUI 3&lt;/strong>, который входит в состав &lt;strong>Windows App SDK&lt;/strong>.&lt;/p>
&lt;h3 id="31-архитектура-windows-app-sdk-и-winui-3">3.1. Архитектура Windows App SDK и WinUI 3
&lt;/h3>&lt;p>Windows App SDK — это набор библиотек для предоставления новейших Windows API независимо от версии ОС. В то время как традиционная UWP (Universal Windows Platform) была тесно связана с обновлениями ОС, Windows App SDK распространяется вместе с приложением, что гарантирует стабильную работу от Windows 10 (начиная с версии 1809) до Windows 11.&lt;/p>
&lt;p>WinUI 3 — это нативный UI-фреймворк, работающий поверх Windows App SDK, полностью поддерживающий Fluent Design System. Внутреннее устройство WinUI 3 реализовано на C++ и DirectX, что обеспечивает очень высокую скорость работы.&lt;/p>
&lt;h3 id="32-почему-был-выбран-c-cwinrt-а-не-c">3.2. Почему был выбран C++ (C++/WinRT), а не C#
&lt;/h3>&lt;p>Для разработки на WinUI 3 поддерживаются языки C# и C++. Использование C# и .NET значительно повысило бы скорость разработки, но в LogicPad я выбрал &lt;strong>C++/WinRT&lt;/strong> по следующим причинам:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Детерминированное управление памятью&lt;/strong>: Отсутствие сборщика мусора (GC) позволяет полностью контролировать время выделения и освобождения памяти. Это предотвращает паузы GC во время цикла симуляции.&lt;/li>
&lt;li>&lt;strong>Оптимизация SIMD и кэша&lt;/strong>: В C++ можно строго определить физическую структуру данных в памяти (например, Struct of Arrays), чтобы максимизировать процент попадания в кэш процессора.&lt;/li>
&lt;li>&lt;strong>Нативные ABI-границы&lt;/strong>: C++/WinRT — это современная проекция C++ для COM (Component Object Model). Это позволяет вызывать нативные API ОС напрямую, без накладных расходов P/Invoke, как в C#.&lt;/li>
&lt;/ol>
&lt;p>В основе C++/WinRT лежит COM. Все объекты WinRT по сути являются COM-объектами, реализующими интерфейс &lt;code>IUnknown&lt;/code>, а умные указатели C++/WinRT, такие как &lt;code>winrt::com_ptr&lt;/code>, автоматически управляют подсчетом ссылок (&lt;code>AddRef&lt;/code> / &lt;code>Release&lt;/code>).&lt;/p>
&lt;h2 id="4-главные-препятствия-и-прорывы-в-разработке-на-winui-3">4. Главные препятствия и прорывы в разработке на WinUI 3
&lt;/h2>&lt;p>Разработка WinUI 3 с использованием C++/WinRT очень мощная, но при этом сопряжена со специфической сложностью. Здесь я подробно объясню две основные технические проблемы, с которыми я столкнулся при разработке LogicPad, и пути их решения.&lt;/p>
&lt;h3 id="41-ужас-асинхронного-обновления-ui-в-c-и-корутины">4.1. Ужас асинхронного обновления UI в C++ и корутины
&lt;/h3>&lt;p>Золотое правило современных UI-приложений — «не блокировать поток пользовательского интерфейса (UI-поток)». В LogicPad необходимо выполнять симуляцию огромных схем в фоновом потоке, а затем отображать результаты в UI-потоке.&lt;/p>
&lt;p>В C# это можно довольно просто описать с помощью &lt;code>async/await&lt;/code> и &lt;code>DispatcherQueue&lt;/code>, но в C++ это реализуется путем комбинирования корутин C++20 (Coroutines) и &lt;code>winrt::apartment_context&lt;/code>. Здесь необходимо понимание квартирной модели COM (STA: Single-Threaded Apartment и MTA: Multi-Threaded Apartment).&lt;/p>
&lt;p>Следующий код из реальной кодовой базы LogicPad демонстрирует паттерн бесшовного перехода между фоновыми вычислениями и возвратом в UI-поток.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;winrt/Windows.Foundation.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;winrt/Microsoft.UI.Dispatching.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;winrt/Microsoft.UI.Xaml.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">winrt&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">Microsoft&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UI&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Xaml&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">using&lt;/span> &lt;span class="k">namespace&lt;/span> &lt;span class="n">Microsoft&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UI&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Dispatching&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// Обработчик события нажатия кнопки
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">fire_and_forget&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">OnRunSimulationClicked&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">IInspectable&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="cm">/* sender */&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">RoutedEventArgs&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="cm">/* args */&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Захват контекста квартиры (STA) текущего UI-потока
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">apartment_context&lt;/span> &lt;span class="n">ui_thread&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Обновление статуса UI (выполняется в UI-потоке)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">StatusTextBlock&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">L&lt;/span>&lt;span class="s">&amp;#34;Идет симуляция...&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ProgressBar&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">IsIndeterminate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Переход контекста в пул потоков (MTA)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">co_await&lt;/span> &lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">resume_background&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Очень тяжелый процесс симуляции (выполняется в фоновом потоке)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// В это время UI-поток освобождается, предотвращая зависание приложения
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">std&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">vector&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">LogicResult&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">results&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">CoreEngine&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">RunMassiveSimulation&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Форматирование результатов симуляции в строку (продолжается в фоне)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">hstring&lt;/span> &lt;span class="n">outputText&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">FormatResults&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">results&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Возврат контекста в UI-поток
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">co_await&lt;/span> &lt;span class="n">ui_thread&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Дальнейший код выполняется в UI-потоке, поэтому можно безопасно обращаться к элементам управления XAML
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">ResultTextBlock&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">outputText&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ProgressBar&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">IsIndeterminate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">StatusTextBlock&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">L&lt;/span>&lt;span class="s">&amp;#34;Завершено&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">catch&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">hresult_error&lt;/span> &lt;span class="k">const&lt;/span>&lt;span class="o">&amp;amp;&lt;/span> &lt;span class="n">ex&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// При возникновении исключения также возвращаемся в UI-поток для отображения сообщения об ошибке
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">co_await&lt;/span> &lt;span class="n">ui_thread&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">StatusTextBlock&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">Text&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="sa">L&lt;/span>&lt;span class="s">&amp;#34;Ошибка: &amp;#34;&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">ex&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">message&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ProgressBar&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="n">IsIndeterminate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">false&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Поведение &lt;code>winrt::apartment_context&lt;/code> выглядит как магия, но внутренне он использует интерфейс &lt;code>IContextCallback&lt;/code> для запоминания исходного контекста потока, и во время &lt;code>co_await&lt;/code> ставит в очередь (отправляет) выполнение обратно в этот контекст — это продвинутый механизм C++. Это позволяет писать асинхронный код в процедурном стиле, избегая ада коллбеков.&lt;/p>
&lt;h3 id="42-идеальная-реализация-кастомной-строки-заголовка">4.2. Идеальная реализация кастомной строки заголовка
&lt;/h3>&lt;p>В приложениях эпохи Windows 11 «кастомная строка заголовка» (область caption), где размещаются вкладки или строка поиска, является обязательным требованием современного UX. В WinUI 3 просто изменить цвет строки заголовка легко, но попытка удовлетворить требование «расширить клиентскую область на строку заголовка, сохранив при этом возможность перемещения окна и функции Snap Layouts (автоматическое изменение размера при перетаскивании к краю экрана)», резко повышает уровень сложности.&lt;/p>
&lt;p>В LogicPad я использовал API &lt;code>ExtendsContentIntoTitleBar&lt;/code>, чтобы создать строку заголовка из собственных XAML-элементов. Ниже приведен код настройки строки заголовка с использованием класса &lt;code>AppWindow&lt;/code> из Windows App SDK.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;winrt/Microsoft.UI.Windowing.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;winrt/Microsoft.UI.Interop.h&amp;gt;&lt;/span>&lt;span class="cp">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#include&lt;/span> &lt;span class="cpf">&amp;lt;microsoft.ui.interop.h&amp;gt;&lt;/span>&lt;span class="cp"> &lt;/span>&lt;span class="c1">// for GetWindowIdFromWindow
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">void&lt;/span> &lt;span class="n">MainWindow&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">InitializeCustomTitleBar&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Получение HWND (дескриптор окна) текущего окна
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">auto&lt;/span> &lt;span class="n">windowNative&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">this&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">try_as&lt;/span>&lt;span class="o">&amp;lt;::&lt;/span>&lt;span class="n">IWindowNative&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">HWND&lt;/span> &lt;span class="n">hwnd&lt;/span>&lt;span class="p">{&lt;/span> &lt;span class="k">nullptr&lt;/span> &lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">windowNative&lt;/span>&lt;span class="o">-&amp;gt;&lt;/span>&lt;span class="n">get_WindowHandle&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">&amp;amp;&lt;/span>&lt;span class="n">hwnd&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Преобразование HWND в WindowId и получение экземпляра AppWindow
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Microsoft&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UI&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">WindowId&lt;/span> &lt;span class="n">windowId&lt;/span> &lt;span class="o">=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Microsoft&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UI&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">GetWindowIdFromWindow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hwnd&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">auto&lt;/span> &lt;span class="n">appWindow&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Microsoft&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UI&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Windowing&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">AppWindow&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">GetFromWindowId&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">windowId&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Проверка, поддерживает ли версия ОС кастомизацию
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Microsoft&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UI&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Windowing&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">AppWindowTitleBar&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">IsCustomizationSupported&lt;/span>&lt;span class="p">())&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">auto&lt;/span> &lt;span class="n">titleBar&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">appWindow&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">TitleBar&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Расширение клиентской области (контента) на строку заголовка
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">titleBar&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ExtendsContentIntoTitleBar&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">true&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Делаем фон стандартных кнопок заголовка (свернуть/развернуть/закрыть) прозрачным
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">titleBar&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ButtonBackgroundColor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Microsoft&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UI&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Colors&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Transparent&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">titleBar&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ButtonInactiveBackgroundColor&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">winrt&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Microsoft&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">UI&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Colors&lt;/span>&lt;span class="o">::&lt;/span>&lt;span class="n">Transparent&lt;/span>&lt;span class="p">());&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// Установка UI-элемента (AppTitleBar), определенного в XAML, как области перетаскивания
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// *Для этого необходимо отслеживать UIElement на стороне XAML с помощью Dispatcher в UI-потоке,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// и вызывать SetDragRectangles(), чтобы сообщить ОС об областях, доступных для перетаскивания.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Самая большая ловушка в этой реализации заключается в том, что каждый раз, когда размер элемента пользовательского интерфейса со стороны XAML меняется (например, при изменении размера окна), необходимо пересчитывать и уведомлять ОС об области hit-test со словами «здесь можно перетаскивать», используя &lt;code>InputNonClientPointerSource&lt;/code> или &lt;code>SetDragRectangles&lt;/code>. Если этого не сделать, возникнут ошибки: окно не будет двигаться при перетаскивании за заголовок, или нажатие на кнопку будет распознаваться как перетаскивание окна.&lt;/p>
&lt;h2 id="5-глубины-упаковки-msix-и-appxmanifest">5. Глубины упаковки MSIX и AppXManifest
&lt;/h2>&lt;p>Для распространения завершенного LogicPad необходимо создать установщик. Вместо традиционных установщиков MSI или EXE я выбрал современный формат &lt;strong>MSIX&lt;/strong>. MSIX обеспечивает абсолютно чистую установку и удаление (не засоряет реестр) и включает функцию автоматического обновления, что очень безопасно и удобно для пользователей.&lt;/p>
&lt;p>Однако при упаковке нативного приложения, написанного на C++, в формат MSIX самое важное, на что следует обратить внимание — это настройки &lt;code>Package.appxmanifest&lt;/code> (файла манифеста).&lt;/p>
&lt;p>LogicPad должен читать и писать огромные файлы проектов, сохраненные в локальной файловой системе (например, в папке «Документы» пользователя). В стандартной изолированной среде (AppContainer) UWP приложение имеет доступ только к собственной изолированной папке данных. Чтобы получить права полного доступа как у нативного десктопного приложения, необходимо объявить возможность &lt;code>runFullTrust&lt;/code> в манифесте.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;Package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/appx/manifest/foundation/windows10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:uap=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/appx/manifest/uap/windows10&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">xmlns:rescap=&lt;/span>&lt;span class="s">&amp;#34;http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">IgnorableNamespaces=&lt;/span>&lt;span class="s">&amp;#34;uap rescap&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Identity&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;LogicPad.Studio&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Publisher=&lt;/span>&lt;span class="s">&amp;#34;CN=Kenji&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">Version=&lt;/span>&lt;span class="s">&amp;#34;1.0.0.0&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;DisplayName&amp;gt;&lt;/span>LogicPad&lt;span class="nt">&amp;lt;/DisplayName&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;PublisherDisplayName&amp;gt;&lt;/span>Kenji&lt;span class="nt">&amp;lt;/PublisherDisplayName&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Logo&amp;gt;&lt;/span>Assets\StoreLogo.png&lt;span class="nt">&amp;lt;/Logo&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Properties&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;TargetDeviceFamily&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;Windows.Desktop&amp;#34;&lt;/span> &lt;span class="na">MinVersion=&lt;/span>&lt;span class="s">&amp;#34;10.0.17763.0&amp;#34;&lt;/span> &lt;span class="na">MaxVersionTested=&lt;/span>&lt;span class="s">&amp;#34;10.0.22621.0&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Dependencies&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Capabilities&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Обычные ограничения возможностей --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;Capability&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;internetClient&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!-- Ограниченная возможность для работы в качестве нативного десктопного приложения --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;rescap:Capability&lt;/span> &lt;span class="na">Name=&lt;/span>&lt;span class="s">&amp;#34;runFullTrust&amp;#34;&lt;/span> &lt;span class="nt">/&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;lt;/Capabilities&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nt">&amp;lt;/Package&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Это &lt;code>&amp;lt;rescap:Capability Name=&amp;quot;runFullTrust&amp;quot; /&amp;gt;&lt;/code> называется «ограниченной возможностью» (Restricted Capability). При отправке в Microsoft Store вам нужно будет предоставить письменное обоснование рецензентам, почему требуется это разрешение. Я объяснил, что «это приложение является инструментом для профессионалов, которое читает, записывает и экспортирует любые файлы проектов логических схем на локальном диске пользователя», и успешно прошел проверку.&lt;/p>
&lt;h2 id="6-путь-к-microsoft-store-и-процесс-проверки">6. Путь к Microsoft Store и процесс проверки
&lt;/h2>&lt;p>После завершения работы над приложением и сборки пакета MSIX наступает время для отправки в Microsoft Store. Для независимого разработчика распространение через магазин дает неоценимые преимущества: автоматическая доставка обновлений, гарантия надежности и использование платежной системы.&lt;/p>
&lt;p>Процесс отправки в Microsoft Store осуществляется через Partner Center (Центр партнеров). Блок-схема ниже показывает весь путь от сборки до публикации.&lt;/p>
&lt;pre class="mermaid">
flowchart TD
A[&amp;#34;Разработчик (Visual Studio)&amp;#34;] --&amp;gt;|Сборка и подпись| B[&amp;#34;Генерация пакета MSIX Bundle&amp;#34;]
B --&amp;gt; C[&amp;#34;Windows App Certification Kit (WACK)&amp;#34;]
C --&amp;gt;|Пройдено| D[&amp;#34;Панель управления Partner Center&amp;#34;]
C --&amp;gt;|Провалено| E[&amp;#34;Исправление кода / манифеста&amp;#34;]
E --&amp;gt; A
D --&amp;gt;|Загрузка пакета| F[&amp;#34;Процесс сертификации в Store&amp;#34;]
F --&amp;gt; G[&amp;#34;Автоматическое сканирование на вредоносное ПО&amp;#34;]
G --&amp;gt; H[&amp;#34;Ручная проверка контента&amp;#34;]
H --&amp;gt;|Одобрено| I[&amp;#34;Публикация в Microsoft Store&amp;#34;]
H --&amp;gt;|Отклонено| J[&amp;#34;Исправление замечаний и повторная отправка&amp;#34;]
J --&amp;gt; D
I --&amp;gt; K[&amp;#34;Приложение опубликовано!&amp;#34;]
&lt;/pre>
&lt;h3 id="61-барьер-wack-windows-app-certification-kit">6.1. Барьер WACK (Windows App Certification Kit)
&lt;/h3>&lt;p>Перед загрузкой в Partner Center вы должны локально запустить &lt;strong>WACK (Windows App Certification Kit)&lt;/strong> и пройти предварительное тестирование. WACK — это инструмент, который автоматически проверяет приложение на наличие сбоев, вызовы недопустимых API и соответствие требованиям производительности.&lt;/p>
&lt;p>Для нативных приложений на C++ особую осторожность следует проявлять с ошибками «использование неподдерживаемого API». Если вы статически компонуете старую стороннюю C++ библиотеку, то внутри нее могут использоваться устаревшие Win32 API, из-за чего проверка WACK не будет пройдена. Чтобы обойти эту проблему, я обновил зависимости до последних версий и переписал некоторые функции на альтернативные API, предоставляемые Windows App SDK.&lt;/p>
&lt;h3 id="62-проверка-и-публикация">6.2. Проверка и публикация
&lt;/h3>&lt;p>Настройки в Partner Center включают ценообразование приложения, возрастные ограничения (рейтинг IARC), а также ввод скриншотов и описаний для магазина. Поскольку LogicPad — это технический инструмент, я моментально получил рейтинг для всех возрастов.&lt;/p>
&lt;p>С момента отправки пакета до завершения проверки прошло около 3 рабочих дней. После автоматического сканирования на вирусы и проверки функциональности, команда рецензентов Microsoft проводит ручную проверку. Запрос на разрешение &lt;code>runFullTrust&lt;/code> прошел без проблем, и чувство достижения, когда статус наконец изменился на «Опубликовано» (In the Store), было ни с чем не сравнимым.&lt;/p>
&lt;h2 id="7-личная-разработка-как-бизнес-математическая-модель-производительности-и-дохода">7. Личная разработка как бизнес: Математическая модель производительности и дохода
&lt;/h2>&lt;p>Чтобы не просто создать приложение и быть довольным, а постоянно обновлять LogicPad и превратить его в успешный бизнес, необходимо количественно оценивать как технические, так и бизнес-показатели.&lt;/p>
&lt;h3 id="71-модель-оптимизации-потребления-памяти-благодаря-c">7.1. Модель оптимизации потребления памяти благодаря C++
&lt;/h3>&lt;p>Главное преимущество LogicPad заключается в его невероятной легковесности по сравнению с редакторами на базе Electron (например, VSCode). След памяти (memory footprint) приложения $M_{total}$ можно смоделировать следующим образом:&lt;/p>
$$
M_{total} = M_{UI} + M_{engine} + M_{cache}
$$&lt;p>Здесь затраты памяти на нативный рендеринг WinUI 3 $M_{UI}$ кардинально меньше по сравнению с Electron, который загружает браузерный движок (около 50 МБ).
Кроме того, память C++ движка $M_{engine}$ линейно масштабируется относительно количества логических вентилей $N$, благодаря оптимизированным структурам и устранению указателей.&lt;/p>
$$
M_{engine} = N \times \text{sizeof(LogicNode)}
$$&lt;p>Используя &lt;code>#pragma pack&lt;/code> в C++ для оптимизации выравнивания структуры, я минимизировал потребление памяти на каждый узел до предела.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#pragma pack(push, 1)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>&lt;span class="c1">// Отказ от таблицы виртуальных функций (vtable) и упаковка для минимизации памяти
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">struct&lt;/span> &lt;span class="nc">LogicNode&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">uint32_t&lt;/span> &lt;span class="n">id&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 4 байта
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">uint16_t&lt;/span> &lt;span class="n">type&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 2 байта
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">bool&lt;/span> &lt;span class="n">isActive&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 1 байт
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// Размер структуры — 7 байт (без выравнивающего паддинга)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="p">};&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#pragma pack(pop)
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Память кэш-слоя $M_{cache}$ для хранения истории симуляции увеличивается пропорционально $\mathcal{O}(N \log N)$, но поскольку базовый след памяти небольшой, даже для схем из десятков тысяч узлов потребление ОЗУ всей системы остается в пределах 200 МБ.&lt;/p>
&lt;h3 id="72-ltv-и-cac-расчеты-маркетинга">7.2. LTV и CAC: Расчеты маркетинга
&lt;/h3>&lt;p>В стратегии монетизации личных проектов баланс между пожизненной ценностью клиента (LTV: Lifetime Value) и стоимостью привлечения клиента (CAC: Customer Acquisition Cost) решает все. LogicPad использует не подписку, а модель лицензии с разовой покупкой (freemium).&lt;/p>
&lt;p>LTV рассчитывается как сумма текущей стоимости вероятности будущих покупок обновлений, дисконтированная по ставке $d$. За период $T$ это выражается следующей формулой:&lt;/p>
$$
LTV = \sum_{t=1}^{T} \frac{ARPU_t \times Margin}{(1+d)^t}
$$&lt;p>С другой стороны, CAC — это общая сумма маркетинговых затрат, таких как реклама в Twitter (ныне X) или привлечение через статьи в блогах, разделенная на количество новых пользователей.&lt;/p>
$$
CAC = \frac{Total\ Marketing\ Spend}{Number\ of\ New\ Users}
$$&lt;p>Преимущество индивидуальной разработки в том, что затраты на рабочее время можно считать безвозвратными как «время на хобби», и CAC можно рассчитывать только на основе чистых маркетинговых расходов. В настоящее время благодаря органическому трафику, в основном основанному на рекомендациях в нишевых технических сообществах, мы достигаем здоровой юнит-экономики $LTV > CAC$ в состоянии, когда $CAC \approx 0$.&lt;/p>
&lt;h2 id="8-заключение-суровый-но-прекрасный-мир-нативной-разработки">8. Заключение: суровый, но прекрасный мир нативной разработки
&lt;/h2>&lt;p>Оглядываясь на путь разработки LogicPad и его выпуск в Microsoft Store, могу сказать, что это был далеко не простой путь: борьба с запутанными ошибками компиляции C++/WinRT, отслеживание утечек памяти из-за ошибок подсчета ссылок COM, а также изучение XML-спецификаций манифеста MSIX.&lt;/p>
&lt;p>Именно потому, что с развитием веб-технологий наступила эпоха, когда «в браузере можно сделать всё что угодно», опыт нативной разработки, когда вы напрямую обращаетесь к API ОС и заботитесь о каждом байте памяти и такте процессора, в подавляющей степени повышает базовую квалификацию инженера.&lt;/p>
&lt;p>WinUI 3 и Windows App SDK продолжают активно развиваться и являются лучшими инструментами для создания красивых приложений, максимально использующих парадигму пользовательского интерфейса Windows 11. Я искренне надеюсь, что этот пост в блоге поможет разработчикам, которые собираются попробовать свои силы в нативной разработке для Windows, и что в Store появится еще больше отличных приложений.&lt;/p>
&lt;p>Разработка еще не закончена. В следующей версии LogicPad планируется интеграция собственного движка рендеринга волновых форм на базе Direct2D. В следующей статье мы планируем углубиться во взаимодействие DirectX и WinUI 3 (использование SwapChainPanel). Оставайтесь с нами.&lt;/p></description></item></channel></rss>