Распространение Windows-приложения одним файлом — single binary и пределы зависимости от ОС

· Обновлено: · · Windows, распространение, single binary, .NET, C++, WebView2, WinUI

История изменений (1 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619758)

Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.

Го Комура (2026). Распространение Windows-приложения одним файлом — single binary и пределы зависимости от ОС. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619758 https://comcomponent.com/ru/blog/2026/03/19/003-windows-single-binary-and-os-dependencies/

DOI (последняя версия)
10.5281/zenodo.21619758
DOI (эта версия)
10.5281/zenodo.21619759

Эта статья началась с разговора о том, «до какой степени в Windows ещё можно говорить single binary». Пост, с которого всё пошло, ниже. Этот абзац можно пропустить: дальше текст читается самостоятельно.

В Windows пожелание «хотелось бы распространять по возможности одним файлом» встречается сплошь и рядом. Для внутренних инструментов, средств интеграции с оборудованием, терминалов мониторинга, офлайн-сред и площадок, которые стремятся по максимуму избегать установщиков, переход на single binary выглядит очень привлекательно.

Однако если не разделить этот разговор на составляющие с самого начала, обсуждение обычно перестаёт складываться где-то на середине. Дело в том, что за словами «хотим single binary» в Windows нередко скрываются сразу четыре разных запроса.

  • Хотим, чтобы файл поставки был один
  • Хотим, чтобы не требовалась предварительная установка среды выполнения .NET или Visual C++
  • Хотим, чтобы приложение заработало, если его просто положить, без установщика и без прав администратора
  • Не хотим зависеть от различий между версиями целевой Windows

Эти четыре вещи не одно и то же. На практике меньше всего путаницы, если рассуждать так:

Свести файлы поставки к одному EXE — вполне реально. А вот свести к нулю зависимость от целевой Windows — нет.

В этой статье мы разбираем эту границу применительно к практике разработки Windows-приложений.

Один EXE ещё не убирает зависимость от ОСРисунок показывает, что в желании получить single binary легко смешиваются разговор о поставке и разговор о зависимостях: свести поставку к одному EXE вполне реально, а зависимость от целевой Windows к нулю свести нельзя.«Хотим single binary»Поставка: один файл, просто положитьЗависимости: без рантайма, без привязки к ОССвести к одному EXE вполне реальноЗависимость от ОС к нулю не сводится

Рис. 1: В «хотим один файл» смешаны и возможное, и невозможное.

1. Сначала вывод

Если сразу сформулировать вывод, он звучит так.

  • Для обычного настольного EXE упаковку в single binary можно довести довольно далеко
  • Но возможность собрать один EXE и отсутствие зависимости от целевой Windows — разные вещи
  • Для расширений оболочки, служб Windows, драйверов, WebView2 и части приложений на WinUI 3 главный вопрос обычно не в числе файлов, а в том, что регистрируется в ОС и что при этом предполагается по умолчанию
  • Важнее всего на практике — заранее разделить, чего именно вы хотите: single binary, отказа от установщика или снижения зависимости от ОС

Другими словами, границы в Windows проходят так.

  • Свести поставку к одному файлу: вполне реально
  • Включить дополнительные среды выполнения в поставку: вполне реально
  • Приблизиться к развёртыванию методом xcopy: зависит от типа приложения
  • Убрать зависимость от самой целевой Windows: невозможно
Границы в WindowsРисунок показывает границу: свести поставку к одному файлу и включить дополнительную среду выполнения в поставку вполне реально, приблизиться к xcopy зависит от типа приложения, убрать зависимость со стороны целевой Windows невозможно.Свести поставку к одному файлуВполне реальноВключить дополнительный рантайм в поставкуПриблизиться к развёртыванию xcopyЗависит от типа приложенияУбрать зависимость со стороны ОСНевозможно

Рис. 2: Граница между тем, что можно, что зависит от типа и что нельзя.

1.1 Термины, которыми пользуется эта статья

Сначала сведём слова, которые дальше будут встречаться снова и снова.

Термин Как читать и раскрывать Смысл в этой статье
UCRT Universal C Runtime Часть стандартной библиотеки C, которая появилась, когда в Visual Studio 2015 среду выполнения C разделили. Начиная с Windows 10 входит в состав ОС
Распространяемый пакет VC++ Visual C++ Redistributable Установщик, который кладёт на целевую машину DLL среды выполнения Visual C++, кроме UCRT
framework-dependent развёртывание с зависимостью от .NET в целевой среде Форма развёртывания, которая исходит из того, что на целевой машине уже есть среда выполнения .NET
self-contained среда выполнения .NET в поставке Форма, в которой приложение несёт с собой полный набор среды выполнения .NET
single-file публикация одним файлом Параметр публикации .NET, который собирает поставку в один EXE
Native AOT Native Ahead-Of-Time Форма развёртывания, при которой IL заранее компилируется в машинный код. Во время выполнения JIT не используется
развёртывание app-local DLL рядом с приложением Способ поставки, при котором DLL лежат в той же папке, что и EXE, и ими пользуется только это приложение
развёртывание xcopy распространение простым копированием Способ поставки, при котором не нужны ни установщик, ни права администратора: достаточно положить папку целиком
SCM Service Control Manager, диспетчер управления службами Механизм ОС, который регистрирует, запускает и останавливает службы Windows
UAC User Account Control, контроль учётных записей Механизм безопасности Windows, который управляет повышением до прав администратора
расширение оболочки shell extension COM-компонент, который загружается в процесс вроде Проводника и работает внутри него
Evergreen постоянно обновляемый канал Режим распространения, в котором WebView2 Runtime оставляют одной общей автоматически обновляемой копией
Fixed Version фиксированная версия Режим распространения, в котором конкретную версию WebView2 Runtime включают в поставку своего приложения
arch architecture, архитектура CPU x86 / x64 / Arm64

Карта знаний этой статьи

Windows-приложение довольно часто можно свести к одному EXE: self-contained, single-file и Native AOT в .NET, статическая компоновка (/MT) в C/C++. Но зависимость от версии ОС, архитектуры, системных DLL и модели безопасности остаётся — зависимость от целевой Windows до нуля не свести. self-contained и framework-dependent — взаимоисключающие варианты; Native AOT по сути работает как single-file, но взамен нельзя использовать, например, built-in COM. Для WebView2 нужно выбрать, как поставлять runtime, — Evergreen или Fixed Version; у WinUI 3 доступность PublishSingleFile зависит от packaged (MSIX) или unpackaged. У расширений оболочки и драйверов основная задача — регистрация и подпись, поэтому сопровождать чаще проще распространение app-local, чем насильно сжимать всё в один EXE.

Карта знаний: один двоичный файл Windows-приложенияСхема показывает, что свести дистрибутив к одному файлу и убрать зависимость от ОС — разные оси; какие средства single-file есть в .NET и в C/C++; и как интеграция с хостом вроде WebView2 или WinUI 3 сама ограничивает способ распространениятребуетнесовместимо снесовместимо стребуеттребуетнесовместимо стребуетиспользуетиспользуетнесовместимо сиспользуеттребуетнастраиваетсянастраиваетсянесовместимо сиспользуеттребуетне рекомендуетсярекомендуется длятребуетиспользуеттребуетупаковка в один двоичный файлзависимость от целевой Windowsself-contained публикацияframework-dependent публикация (.NET)Native AOTbuilt-in COM interopsingle-file публикация (.NET)статическая линковка CRT (/MT)динамическая линковка CRT (/MD)Visual C++ RedistributableUCRT (Universal C Runtime)упаковка MSIX (packaged)WinUI 3 / Windows App SDKMicrosoft Edge WebView2WebView2 Runtimeрежим распространения Evergreen (WebView2)рантайм Fixed Versionрасширение оболочки (Explorer)COM (Component Object Model)подпись драйверапакет драйвераapp-local развёртывание

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 22, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. «Single binary» стоит рассматривать как четыре уровня

Если сначала показать уровни схемой, получится так. Снизу вверх становится труднее, до самого верхнего в Windows не добраться.

уровень A: один файл поставкивполне реальноуровень B: языковая среда выполнения не ставится заранеевполне реальноуровень C: не нужны установка и регистрациязависит от типа приложенияуровень D: нет зависимости от целевой Windowsв Windows недостижимо

Рис. 3: Четыре уровня single binary. Снизу вверх становится труднее, до уровня D не добраться.

От того, какой из этих четырёх уровней имеется в виду под «хотим single binary», нужная работа меняется полностью.

2.1 Уровень A: один файл поставки

Это самый поверхностный уровень.

  • Можно отправить один файл по почте
  • Достаточно положить один файл на USB-накопитель
  • В месте развёртывания находится только app.exe

Речь идёт о видимой единице поставки. Даже если приложение при запуске временно распаковывает какие-то файлы или зависит от системных DLL, одно это условие может выполняться.

2.2 Уровень B: не требуется предварительная установка языковой среды выполнения

Следующий уровень — состояние, при котором приложение работает без предварительной установки на целевой машине среды выполнения .NET или распространяемого пакета VC++.

  • статическая линковка в C/C++
  • self-contained в .NET
  • single-file в .NET
  • Native AOT в .NET

На этом уровне ощущение «это можно унести с собой как самостоятельную единицу» становится заметно сильнее.

2.3 Уровень C: не требуется установка или регистрация

Начиная отсюда, сложность резко возрастает.

Обычный EXE иногда способен работать сразу после того, как его просто положили в нужное место. Но следующее — уже другая история.

  • расширения оболочки
  • службы Windows
  • пользовательские URL-схемы и файловые ассоциации
  • драйверы
  • компоненты, загружаемые в другие процессы, такие как Проводник или Office

Для этой области недостаточно просто разместить файл. Требуется регистрация на стороне ОС или привязка к хост-процессу.

Область, где просто положить файл недостаточноРисунок показывает, что расширения оболочки, службы Windows, файловые ассоциации, драйверы и компоненты, загружаемые в чужой процесс, нельзя просто положить файлом: нужна регистрация на стороне ОС или привязка к хосту.Расширения оболочки и службыПросто положить недостаточноАссоциации и драйверыТо, что загружается в чужой процессНужны регистрация в ОС и привязка к хосту

Рис. 4: На уровне C резко становится труднее, потому что появляется область, где нужна регистрация в ОС.

2.4 Уровень D: отсутствие зависимости от целевой Windows

Это в Windows невозможно.

Windows-приложение в конечном счёте выполняется поверх API Windows, загрузчика, модели безопасности и стека устройств. Упаковка в single binary покрывает лишь зону ответственности самого приложения. Саму операционную систему с собой не унести.

3. Область, где приложение довольно легко свести к одному EXE

Даже в Windows встречаются приложения, которые относительно легко сводятся к одному EXE.

  • настольные инструменты, запускаемые как самостоятельный процесс
  • бизнес-приложения, где интерфейс и обработка сосредоточены в самом EXE
  • инструменты для связи, обработки файлов, сбора логов, мониторинга, управления оборудованием
  • то, что не требует интеграции с хост-процессами вроде Проводника или Office
  • интерфейсы, не предполагающие веб-среду выполнения

Для такого типа приложений многое легко включить в само приложение.

  • собственный код
  • ресурсы
  • манифест
  • настройки по умолчанию
  • шаблонные данные
  • часть сторонних библиотек
  • саму языковую среду выполнения

Более того, даже без полного встраивания DLL внутрь EXE, развёртывание app-local, при котором DLL размещаются рядом с EXE, в Windows остаётся вполне жизнеспособным и распространённым вариантом. На практике это часто выглядит так:

  • один app.exe
  • либо app.exe плюс несколько соседних DLL
  • при этом установщик не нужен, права администратора не нужны, распространение возможно методом xcopy

Такая форма нередко оказывается проще в сопровождении, чем попытка насильно сжать всё в один EXE.

Практичный вариант — развёртывание app-localРисунок показывает, что даже без полного встраивания DLL в EXE форма, где DLL лежат рядом с EXE, не нужен установщик и права администратора и можно распространять методом xcopy, нередко проще в сопровождении, чем попытка насильно сжать всё в один EXE.app.exe плюс несколько соседних DLLБез установщика и без прав администратораМожно распространять методом xcopyИногда проще в сопровождении, чем насильственное сжатие в один EXE

Рис. 5: Даже без настойчивости на одном EXE цель часто закрывается развёртыванием app-local.

4. Зависимости от Windows, которые остаются даже при одном EXE

Если решить, что «раз EXE один, значит, зависимости от целевой Windows нет», именно здесь и происходит сбой. На деле зависимости остаются даже при одном EXE.

4.1 Зависимость от версии ОС

У каждого API Windows есть своя минимально поддерживаемая версия ОС. Есть и различия между x64 и Arm64. То есть даже при одном EXE заранее нужно зафиксировать:

  • работает ли приложение вплоть до Windows 10;
  • предполагается ли Windows 11 как базовая версия;
  • должно ли приложение работать и на Windows Server;
  • какая архитектура является целевой — x86, x64 или Arm64.

4.2 Зависимость от системных DLL

Даже если вам кажется, что EXE один, во время выполнения приложение, разумеется, использует компоненты, предоставляемые ОС.

  • kernel32.dll
  • user32.dll
  • advapi32.dll
  • инфраструктура COM
  • инфраструктура управления службами

Всё это — зона ответственности самой Windows.

4.3 Зависимость от модели безопасности

  • UAC
  • ACL файловой системы
  • диспетчер управления службами
  • реестр
  • политика подписи драйверов

Всё это приложение не может взять на себя в одиночку.

Зависимости, которые остаются и при одном EXEРисунок показывает, что даже при одном EXE остаются зависимости от минимально поддерживаемой версии Windows API и архитектуры, от системных DLL вроде kernel32.dll и инфраструктуры COM, а также от модели безопасности вроде UAC, ACL и политики подписи драйверов.Приложение из одного EXEВерсия ОС и archСистемные DLL и инфраструктура COMМодель безопасностиНичего из этого приложение не может взять на себя

Рис. 6: Даже если файл поставки один, зависимость от зоны ответственности Windows остаётся.

4.4 Зависимость от хоста и среды выполнения

Если архитектура — не самостоятельный EXE, а нечто, работающее поверх хоста, зависимости сразу резко увеличиваются.

  • используется WebView2 — требуется WebView2 Runtime;
  • используется WinUI 3 / Windows App SDK — требуется продумать режим развёртывания;
  • создаётся расширение оболочки — требуется регистрация на стороне Проводника.

Иными словами, выбор интерфейса или интеграции часто напрямую превращается в сложность распространения.

Выбор UI и интеграции становится сложностью распространенияРисунок показывает, что WebView2 требует WebView2 Runtime, WinUI 3 требует продумать режим развёртывания, расширение оболочки требует регистрации в Проводнике, то есть выбор хоста и среды выполнения сразу становится сложностью распространения.Используем WebView2Нужен RuntimeИспользуем WinUI 3Нужно продумать режим развёртыванияДелаем расширение оболочкиНужна регистрация в Проводнике

Рис. 7: Чем дальше от самостоятельно запускаемого EXE, тем резче растут зависимости.

5. Реалистичные компромиссы по технологиям

5.1 Нативный C/C++

Нативный C/C++ обладает высокой степенью свободы в упаковке в single binary. Доступна статическая линковка, и самостоятельный EXE можно свести к очень компактной форме.

Тем не менее на практике важнее не «утрамбовать всё в один файл», а решить:

  • что делать с UCRT и средой выполнения VC++;
  • размещать ли сторонние DLL по схеме app-local;
  • насколько узко ограничивать целевые CPU и ОС.

Отправная точка: разница между /MT и /MD

В MSVC вопрос «нести среду выполнения с собой или нет» по сути решает этот параметр компилятора.

Параметр Что компонуется Что нужно на целевой машине
/MD библиотеки импорта DLL ucrt.lib и vcruntime.lib. Во время выполнения используются ucrtbase.dll и vcruntime<версия>.dll UCRT и среда выполнения VC++
/MT статическая компоновка libucrt.lib, libvcruntime.lib, libcmt.lib дополнительная среда выполнения не нужна

Минимальный способ попробовать — вот эта команда в Developer Command Prompt.

cl /std:c++17 /EHsc /O2 /MT app.cpp /Fe:app.exe

Здесь стоит запомнить три момента.

  • UCRT стал составляющей Windows, когда в Visual Studio 2015 среду выполнения C разделили, и начиная с Windows 10 входит в ОС. Если целевая система старше, нужна повторная поставка через vcredist.
  • Собирать DLL с /MT не рекомендуется. Состояние статически слинкованной CRT замыкается внутри этой DLL, поэтому выделение памяти, локаль и действие _set_se_translator начинают расходиться между EXE и DLL. Если небрежно выровнять «EXE на /MT, прилагаемые DLL тоже на /MT», авария случается на выделении и освобождении через границу модуля.
  • /clr и /MT вместе использовать нельзя. Если в смеси есть C++/CLI, остаётесь на стороне /MD.
Три оговорки статической линковкиРисунок показывает, что UCRT начиная с Windows 10 входит в ОС, а на более старых системах нужна повторная поставка через vcredist; собирать DLL с /MT не рекомендуется, потому что состояние CRT замыкается внутри DLL и выделение с освобождением через границу модуля ломается; /clr и /MT вместе использовать нельзя.Статическая линковка /MTНа старой Windows нужен vcredist/MT на стороне DLL не рекомендуетсяС /clr вместе нельзяАвария на выделении и освобождении через границу модуля

Рис. 8: /MT силён, но нужны три оговорки: предпосылки UCRT, граница DLL и C++/CLI.

5.2 .NET

В .NET есть single-file, self-contained и Native AOT, поэтому видимую единицу поставки можно сделать весьма компактной.

Однако различия важно понимать чётко.

  • framework-dependent — зависит от .NET, установленного в целевой среде
  • self-contained — несёт среду выполнения .NET вместе с собой
  • single-file — объединяет поставку в один артефакт
  • Native AOT — дополнительно снижает зависимости на этапе запуска, но накладывает функциональные ограничения

«Раз это single-file, значит, зависимостей от ОС меньше» — неверное утверждение. Уменьшается в первую очередь разрозненность файлов поставки приложения.

Single-file уменьшает разрозненность поставки, а не зависимость от ОСРисунок показывает, что framework-dependent зависит от .NET в целевой среде, self-contained несёт рантайм с собой, single-file только собирает поставку в один файл, и из того, что это single-file, снижение зависимости от ОС не следует.framework-dependentЗависит от .NET в целевой средеself-containedНесёт среду выполнения .NET с собойsingle-fileСобирает поставку в один файлЗависимость от ОС от этого не уменьшается

Рис. 9: У каждой формы публикации .NET своё, что уменьшается и что остаётся.

Отправная точка: три варианта dotnet publish

Чтобы сначала запустить и увидеть разницу, этих трёх команд достаточно.

rem 1. framework-dependent + single-file: на целевой машине нужна среда выполнения .NET
dotnet publish -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true

rem 2. self-contained + single-file: среда выполнения .NET идёт с собой
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true

rem 3. Native AOT: публиковать после того, как в csproj записан PublishAot
dotnet publish -c Release -r win-x64

PublishSingleFile и PublishAot официально рекомендуется писать не в командной строке, а в csproj. Тогда во время сборки включается анализ совместимости, и меньше случаев, когда об инциденте узнаёшь уже после публикации.

<PropertyGroup>
  <PublishSingleFile>true</PublishSingleFile>
  <PublishAot>true</PublishAot>
</PropertyGroup>

Заметьте: если указать RuntimeIdentifier, SelfContained по умолчанию становится true. Если нужно framework-dependent, явно пишите --self-contained false. Чтобы публиковать Native AOT на Windows, нужна рабочая нагрузка Visual Studio Разработка классических приложений на C++.

Компромиссы: «сделать один файл» не бесплатно

Если здесь написать только «можно», будет авария, поэтому рядом положим и цену.

Распаковка single-file при запуске

  • По умолчанию в пакет попадают только управляемые DLL. При запуске они читаются в память и на диск в папку не распаковываются. Собственные машинные двоичные файлы среды выполнения остаются отдельными файлами.
  • Чтобы включить в один файл и машинные двоичные файлы, используют IncludeNativeLibrariesForSelfExtract; чтобы перед запуском распаковать всё — IncludeAllContentForSelfExtract. Оба варианта работают как «сначала распаковать, потом запустить», и в Windows распаковка идёт в %TEMP%\.net. Место можно сменить через DOTNET_BUNDLE_EXTRACT_BASE_DIR, но не ставьте каталог, в который могут писать пользователи с другими правами или службы.
  • Если включить EnableCompressionInSingleFile, EXE заметно худеет, но при запуске его ещё нужно разжать в памяти, и старт замедляется. В официальной документации прямо сказано: «прежде чем включать, измерьте и размер, и стоимость запуска». Влияние сильно зависит от приложения.

Несовместимость API у single-file

Когда поставка становится одним файлом, код, который исходит из пути к файлу, тихо ломается.

API Поведение в single-file
Assembly.Location возвращает пустую строку
Assembly.CodeBase PlatformNotSupportedException
Assembly.GetFile IOException
Module.Name возвращает строку <Unknown>

Если нужно трогать файлы рядом с EXE, опирайтесь на AppContext.BaseDirectory; если нужен путь к исполняемому файлу — на Environment.ProcessPath. Сторонние библиотеки иногда используют эти API внутри себя, поэтому после перехода на single-file хотя бы раз нужно проверить запуск на реальной машине.

Код, завязанный на путь, тихо ломаетсяРисунок показывает, что когда поставка становится одним файлом, поведение API, завязанных на путь к файлу, меняется — например Assembly.Location возвращает пустую строку, — поэтому соседние файлы берут через AppContext.BaseDirectory, путь к исполняемому файлу через Environment.ProcessPath, и запуск хотя бы раз проверяют на реальной машине.Переход на single-fileМеняется поведение API, завязанных на путьСоседние файлы — AppContext.BaseDirectoryИсполняемый файл — Environment.ProcessPathХотя бы раз проверить запуск на реальной машине

Рис. 10: После перехода на single-file способ получения пути меняют и проверяют на реальной машине.

Функциональные ограничения Native AOT

Цена «быстрый запуск, среда выполнения не нужна» в том, что следующее становится недоступно.

  • динамическая загрузка вроде Assembly.LoadFile
  • генерация кода во время выполнения вроде System.Reflection.Emit
  • C++/CLI
  • на Windows — встроенный COM
  • обрезка (trimming) обязательна, поэтому её ограничения тоже наследуются
  • по сути это single-file, поэтому несовместимость API выше тоже наследуется
  • System.Linq.Expressions всегда работает интерпретатором и медленнее кода, сгенерированного во время выполнения

Целевая платформа тоже задаётся жёстко. В .NET 8 для Windows это x64 и Arm64, начиная с .NET 9 добавился x86. Идея «один файл под AnyCPU» с самого начала не складывается.

Цена Native AOTРисунок показывает, что Native AOT снижает зависимости на запуске ценой того, что недоступны динамическая загрузка и генерация кода во время выполнения, C++/CLI и встроенный COM на Windows, плюс наследуются ограничения trimming и single-file, а целевая платформа задаётся жёстко.Native AOTЗависимости на запуске можно снизитьДинамическая загрузка и генерация во время выполнения недоступныC++/CLI и встроенный COM недоступныНаследуются ограничения trimming и single-fileЦелевые ОС и arch задаются жёстко

Рис. 11: Плюсы Native AOT смотрят в комплекте с тем, что становится недоступно.

5.3 WebView2

Использование WebView2 полностью меняет сложность упаковки в single binary. Здесь реальный вопрос не в количестве EXE, а в том, как обращаться с WebView2 Runtime.

Прежде чем спрашивать «можно ли сделать один EXE», нужно ответить на другие вопросы.

  • Предполагается ли, что Runtime уже есть в среде?
  • Использовать ли канал Evergreen?
  • Включать ли в поставку Fixed Version?
  • В какой мере брать на себя ответственность за офлайн-распространение?

Разница между Evergreen и Fixed Version в общих чертах такая.

  Evergreen Fixed Version
Кто обновляет Microsoft. Обновляется автоматически Вы. Меняете вместе с обновлением приложения
Экземпляр на машине одна общая копия на все приложения свой экземпляр в поставке каждого приложения
Размер поставки почти не растёт превышает 250 MB
Как ставить Bootstrapper около 2 MB скачивает нужное. Для офлайна в поставку кладут Standalone Installer вместе с приложением разносят весь развёрнутый набор двоичных файлов
Существующие ограничения нужно проверить, есть ли Runtime на машине, до запуска нельзя запускать с сетевого пути или UNC

Evergreen Runtime на Windows 11 предустановлен как часть ОС. На стороне Windows 10 машины без него ещё остаются, поэтому Microsoft сама пишет в духе «даже если выбрали Evergreen, Runtime лучше всё же распространять». То есть процедура «проверить наличие на стороне приложения и поставить, если не хватает» нужна в любом случае. Fixed Version даёт «момент обновления держим сами» ценой того, что поставка тяжелеет на сотни мегабайт. Это нужно решить раньше и отдельно от разговора про один EXE.

У WebView2 сначала решают способ распространенияРисунок показывает, что Evergreen — одна общая автоматически обновляемая Microsoft копия, которой на машине может не быть, а Fixed Version даёт контроль над обновлением ценой сотен мегабайт в поставке, поэтому в любом варианте приложению нужно проверить наличие и поставить Runtime, если его нет.Принимаем WebView2Evergreen: одна общая копия, автообновлениеFixed Version: сами кладём в поставку и обновляемПроверить наличие и поставить, если не хватаетПоставка тяжелеет на сотни мегабайт

Рис. 12: Главный вопрос WebView2 не в числе EXE, а в том, как обращаться с Runtime.

5.4 WinUI 3 / Windows App SDK

WinUI 3 тоже меняет требования к развёртыванию сразу в момент выбора этой технологии. Выбор UI-технологии, по сути, и есть выбор способа распространения.

Конкретно решать нужно по двум осям.

  • packaging: packaged (MSIX), packaged с указанием внешнего расположения или unpackaged
  • runtime: framework-dependent или self-contained

А с точки зрения single binary срабатывает вот это ограничение комбинаций.

  • PublishSingleFile доступен только для приложений WinUI 3 в конфигурации unpackaged и self-contained, и нужен Windows App SDK 1.5 или новее.
  • В packaged-приложениях и в packaged-приложениях с указанием внешнего расположения PublishSingleFile использовать нельзя.
  • В unpackaged теряется package identity, поэтому функции Windows, которые исходят из package identity, — уведомления, фоновые задачи, файловые ассоциации, расширения контекстного меню — становятся недоступны.

То есть в WinUI 3 «сделать один EXE» и «пользоваться расширениями Windows» сталкиваются лоб в лоб. Если single binary — приоритет номер один, часто быстрее сначала пересмотреть саму предпосылку выбора UI-технологии.

Столкновение одного EXE и package identityРисунок показывает, что в WinUI 3 PublishSingleFile доступен только в конфигурации unpackaged и self-contained, unpackaged лишает package identity, уведомления и ассоциации становятся недоступны, поэтому один EXE и расширения Windows сталкиваются лоб в лоб.Хотим PublishSingleFileТолько unpackaged + self-containedТеряется package identityУведомления, ассоциации и прочее недоступныОдин EXE и расширения сталкиваются лоб в лоб

Рис. 13: В WinUI 3 выбор одного EXE означает отказ от package identity.

6. Область, где регистрация и зависимости неизбежны по своей природе

6.1 Расширения оболочки

Расширение оболочки, загружаемое в Проводник, — это совсем не то же самое, что «EXE, который просто кладут в папку». Здесь реальный вопрос не в количестве файлов, а в том, как зарегистрировать расширение в Проводнике.

6.2 Службы Windows

Даже если сам исполняемый файл службы можно свести к одному файлу, распространение — отдельная проблема.

  • регистрация в SCM;
  • права доступа;
  • учётная запись для запуска;
  • настройки восстановления после сбоя.

Всё это нужно продумать. Иными словами, для служб важнее не «как сделать один EXE», а «как организовать установку».

6.3 Драйверы

С драйверами всё ещё более однозначно. Они складываются в цельный пакет только вместе с INF-файлом, подписью и процедурой установки, поэтому изначально плохо вписываются в парадигму single binary.

Область, где главный вопрос — регистрация и подписьРисунок показывает, что расширению оболочки нужна регистрация в Проводнике, службе Windows — регистрация в SCM, права и учётная запись запуска, драйверу — INF и подпись, поэтому главный вопрос не в числе файлов, а в проектировании регистрации и зависимостей.Расширение оболочкиРегистрация в ПроводникеСлужбаРегистрация в SCM, права, учётная записьДрайверINF и подписьГлавный вопрос — проектирование регистрации, а не число файлов

Рис. 14: В этих трёх областях прорабатывают не «как сделать один EXE», а «как регистрировать».

7. Таблица решений для практики

Для грубой оценки удобна такая таблица.

Что вы хотите создать Реалистичность одного EXE Что продумать в первую очередь
Самостоятельный инструмент на Win32 / C++ Высокая Статическая линковка, целевая ОС / архитектура
Самостоятельный инструмент на WinForms / WPF Высокая Применимость self-contained, single-file, Native AOT
Приложение на WinUI 3 / Windows App SDK Средняя Режим развёртывания, дополнительные зависимости
Настольный интерфейс на базе WebView2 От низкой до средней Способ распространения Runtime
Расширение контекстного меню Проводника или обработчик предпросмотра Низкая Регистрация через COM / реестр
Служба Windows Средняя Регистрация в SCM, права, процедура обновления
Приложение с драйвером в комплекте Низкая INF, подпись, установка

Главный вывод из этой таблицы — понимание того, что «количество двоичных файлов» и «зона ответственности за распространение» — разные вещи.

8. Что нужно решить заранее при проектировании развёртывания

Если вы хотите, чтобы упаковка в single binary действительно удалась, есть вещи, которые стоит решить ещё до реализации.

8.1 Определите, что именно должно быть «одним»

  • Хотите ли вы один файл поставки?
  • Хотите ли вы избавиться от предварительной установки среды выполнения?
  • Хотите ли вы отказаться от установщика?
  • Хотите ли вы упростить офлайн-обновления?

От ответа зависит, какую технологию выбрать.

8.2 Заранее зафиксируйте минимально поддерживаемую версию Windows и архитектуру

И single-file, и Native AOT по своей природе привязаны к конкретной ОС и архитектуре. Если оставить это неопределённым и просто двигаться в духе «главное — один файл», в итоге можно упереться в нехватку API или несовпадение версий среды выполнения.

8.3 Явно зафиксируйте, что включается в поставку, а что остаётся на стороне Windows

На практике достаточно просто выписать такую таблицу, чтобы избежать значительной части проблем.

  • что включается в поставку приложения
    • основной exe-файл;
    • собственные DLL;
    • шаблоны настроек;
    • self-contained-среда выполнения.
  • что остаётся на стороне Windows
    • системные DLL;
    • API ОС;
    • SCM / реестр / Проводник;
    • инфраструктура драйверов.
  • что закладывается как отдельное внешнее условие
    • WebView2 Runtime;
    • VC++ Redistributable;
    • Office / Excel;
    • специализированные драйверы.
Письменно зафиксировать три зоны ответственностиРисунок показывает, что значительную часть инцидентов распространения снимает уже то, что вы заранее выписываете три класса: что кладёте в поставку приложения, что оставляете Windows и что принимаете как отдельную внешнюю предпосылку.Зона ответственности поставкиЧто кладём в поставку приложенияЧто оставляем WindowsЧто принимаем как отдельную предпосылкуУже запись этого снижает число инцидентов

Рис. 15: Три класса — в поставке, на стороне ОС, отдельная предпосылка — фиксируют письменно заранее.

8.4 Если single binary в приоритете — снижайте интеграцию с хостом

Это работает очень хорошо.

  • отказаться от расширения оболочки в пользу обычного EXE;
  • не превращать приложение в службу, а использовать Планировщик заданий или явный запуск;
  • использовать нативный интерфейс вместо WebView2;
  • держать COM в пределах собственного процесса.

Иначе говоря, чем меньше в архитектуре решений, где ОС что-то «загружает» или «регистрирует», тем ближе приложение к single binary.

Чем меньше интеграции с хостом, тем ближеРисунок показывает, что отказ от расширения оболочки в пользу обычного EXE, отказ от службы в пользу Планировщика заданий или явного запуска, нативный UI вместо WebView2 — то есть сокращение решений, где ОС что-то загружает и регистрирует, — приближают к single binary.Отказаться от расширения оболочки в пользу обычного EXEСократить решения, где ОС регистрируетНе делать службу, запускать явноНативный UI вместо WebView2Ближе к single binary

Рис. 16: Если single binary в приоритете, сокращают саму интеграцию с хостом.

9. Итог

Упаковка в single binary в Windows возможна в значительной степени. Но в конечном счёте всё сводится к одной формулировке:

Приложение можно сделать одним EXE. Но Windows, от которой это приложение зависит, одним EXE сделать нельзя.

Особенно стоит запомнить пять пунктов.

  • Для обычного самостоятельного EXE распространение одним файлом можно довести весьма далеко
  • Статическая линковка в C/C++, single-file в .NET и Native AOT — сильные варианты
  • Но зависимость от версии ОС, архитектуры, системных DLL и модели безопасности никуда не исчезает
  • Для расширений оболочки, служб, драйверов, WebView2 и части приложений на WinUI 3 главной темой становится регистрация в ОС и дополнительные среды выполнения
  • Успех упаковки в single binary определяется тем, разделили ли вы заранее, что именно должно быть «одним»

Если single binary — сильный приоритет, гораздо больше шансов на успех даёт проектирование, при котором ещё на этапе выбора технологии закладывается снижение связанности с ОС.

Ключ успеха — степень связанности с ОСРисунок показывает, что приложение можно сделать одним EXE, а Windows, от которой оно зависит, одним EXE сделать нельзя, поэтому если single binary в сильном приоритете, успешнее проектировать со снижением связанности с ОС уже на этапе выбора технологии.Сделать приложение одним EXEМожноСделать одним EXE и Windows, от которой оно зависитНельзяПоэтому на выборе технологии снижают связанность с ОС

Рис. 17: Успех сведения к одному EXE решается ещё на стартовом проектировании связанности с ОС.

10. Источники

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Разработка приложений для Windows

При распространении Windows-приложений меньше приходится переделывать, если сразу заложить в проект переход на single-file, включение среды выполнения в поставку, решение об использовании WebView2 или WinUI и целесообразность реализации в виде службы.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Можно ли распространять Windows-приложение единственным EXE-файлом?
Для настольного инструмента, который запускается сам по себе, это можно довести довольно далеко. Статическая линковка C/C++, self-contained и single-file в .NET, Native AOT позволяют свести поставку к одному EXE. Но возможность собрать один EXE и отсутствие зависимости от целевой Windows — разные вещи: зависимости от версии ОС, архитектуры, системных DLL и модели безопасности никуда не исчезают.
Если сделать .NET single-file, зависимость от ОС исчезнет?
Нет. Single-file в первую очередь уменьшает разрозненность файлов поставки приложения, а не зависимость от ОС. Развёртывание framework-dependent зависит от .NET в целевой среде, self-contained несёт среду выполнения .NET с собой, Native AOT ещё сильнее снижает зависимости на запуске, но накладывает ограничения на функции. И single-file, и Native AOT по сути привязаны к конкретной ОС и архитектуре, поэтому минимально поддерживаемую Windows и arch нужно зафиксировать с самого начала.
Какие приложения трудно свести к одному EXE?
Расширения оболочки, службы Windows, драйверы, интерфейс на базе WebView2 и часть приложений на WinUI 3. Здесь главный вопрос не в числе файлов, а в регистрации в ОС и в том, как обращаться с дополнительной средой выполнения. Расширению оболочки нужна регистрация в Проводнике, службе — регистрация в SCM, права и учётная запись запуска, драйверу — INF и подпись, поэтому распространение «просто положил файлы» не складывается. Для WebView2 сначала нужно решить, как распространять WebView2 Runtime.
Есть ли способ распространения лучше, чем насильно встраивать всё в один EXE?
Сильный вариант — развёртывание app-local, когда DLL лежат рядом с EXE. Форма `app.exe` плюс несколько соседних DLL, если при этом не нужен установщик, не нужны права администратора и можно распространять методом xcopy, нередко проще в сопровождении, чем попытка насильно сжать всё в один EXE. Главное — с самого начала разделить, хотите ли вы один файл поставки, хотите ли убрать предварительную установку среды выполнения или хотите обойтись без установщика.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог