Windowsアプリの1ファイル配布 - シングルバイナリとOS依存の限界

· 更新日: · · Windows, 配布, シングルバイナリ, .NET, C++, WebView2, WinUI

更新履歴(4件・最終更新 2026年08月23日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

1EXE化とOS依存の線引き、.NETの発行形態の違い、Native AOTの代償、WinUI 3やWebView2の配布制約などを図でも追えるように、Mermaid図を16点追加しました(地の文500〜750字につき1図の規約に合わせたものです)。既存の4段階の図にもキャプションを付けました。本文の文章は変えていません。
記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
`/MT`と`/MD`の対比表と`dotnet publish`の3パターンを追加し、レベルA〜Dを階層図にしました。用語表を追加し、single-fileの起動時展開の挙動、`Assembly.Location`が空文字列になるなどのAPI非互換、Native AOTの制約(動的読み込み不可、C++/CLI不可、組み込みCOM不可)、EvergreenとFixed Versionの対比を追記しました。
初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589706)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「Windowsアプリの1ファイル配布 - シングルバイナリとOS依存の限界」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589706 https://comcomponent.com/blog/2026/03/19/003-windows-single-binary-and-os-dependencies/

DOI(最新版)
10.5281/zenodo.21589706
DOI(この版)
10.5281/zenodo.22064716

この記事は、「Windows では、どこまでを シングルバイナリ と言っていいのか」という雑談から始まりました。きっかけになった投稿は次のものです。ここを読み飛ばしても、以降は単独で読めるように書いています。

Windows で「できれば 1 ファイルで配りたい」は、かなり普通の要望です。社内ツール、装置連携ツール、監視端末、オフライン環境、インストーラを極力避けたい現場では、シングルバイナリ化 はとても魅力があります。

ただ、この話は最初に切り分けておかないと、だいたい途中で噛み合わなくなります。Windows で言う「シングルバイナリにしたい」には、実は 4 つの別々の話が混ざりやすいからです。

  • 配布物を 1 個にしたい
  • .NET や Visual C++ のランタイムを事前インストール不要にしたい
  • インストーラや管理者権限なしで置くだけで動かしたい
  • 対象 Windows の違いに依存したくない

この 4 つは同じではありません。実務的には、こう考えるのが一番ずれません。

配布物を 1 EXE に寄せることはかなりできる。 でも、対象 Windows への依存をゼロにすることはできない。

この記事では、その境界線を Windows アプリの実務向けに整理します。

1EXEにできてもOS依存は消えないシングルバイナリにしたいという要望には配布の話と依存の話が混ざりやすく、配布物を1EXEへ寄せることはかなりできても、対象Windowsへの依存をゼロにすることはできないことを示す図。「シングルバイナリにしたい」配布の話: 1個にしたい・置くだけにしたい依存の話: ランタイム不要・OS非依存1EXEへ寄せるのはかなり可能OS依存をゼロにはできない

図1: 「1ファイルで配りたい」には、可能な話と不可能な話が混ざっている。

1. まず結論

最初に結論だけまとめると、こうです。

  • 普通のデスクトップ EXE なら、かなり高いところまで single binary 化できます
  • ただし、1 EXE にできること対象 Windows に依存しないこと は別です
  • Shell 拡張、Windows サービス、ドライバ、WebView2、WinUI 3 の一部は、ファイル数より OS へ何を登録し、何を前提にするか のほうが本題になりやすいです
  • 実務で一番大事なのは、single binary 化したいのか、インストーラ不要にしたいのか、OS 依存を減らしたいのか を分けて決めることです

言い換えると、Windows での線引きはこうなります。

  • 配布物を 1 個に寄せる: かなり可能
  • 追加ランタイムを抱え込む: かなり可能
  • xcopy 配布に寄せる: アプリ種別による
  • 対象 Windows 側の依存を消す: 不可能
Windowsでの線引き配布物を1個に寄せることと追加ランタイムを抱え込むことはかなり可能、xcopy配布に寄せられるかはアプリ種別により、対象Windows側の依存を消すことは不可能という線引きを示す図。配布物を1個に寄せるかなり可能追加ランタイムの同梱xcopy配布に寄せるアプリ種別によるOS側の依存を消す不可能

図2: できること・種別によること・できないことの線引き。

1.1 この記事で使う用語

先に、以降で何度も出てくる言葉だけ整理しておきます。

用語 読み方・展開 この記事での意味
UCRT Universal C Runtime Visual Studio 2015 で C ランタイムが分割されてできた、標準 C ライブラリ部分。Windows 10 以降では OS の構成要素として同梱されています
VC++ 再頒布可能パッケージ Visual C++ Redistributable UCRT 以外の Visual C++ ランタイム DLL を対象機に入れるためのインストーラ
framework-dependent 対象環境の .NET に依存する配布 対象機に .NET ランタイムが入っている前提の配布形態
self-contained .NET ランタイム同梱 アプリ側が .NET ランタイム一式を抱えて配る形態
single-file 単一ファイル発行 配布物を 1 個の EXE にまとめる .NET の発行オプション
Native AOT Native Ahead-Of-Time 発行時に IL をネイティブコードへ事前コンパイルする配布形態。実行時に JIT を使いません
app-local 配布 アプリ隣接配布 DLL を EXE と同じフォルダーに置き、そのアプリからだけ使う配り方
xcopy 配布 コピーするだけの配布 インストーラも管理者権限も使わず、フォルダーごと置けば動く配り方
SCM Service Control Manager、サービス制御マネージャー Windows サービスの登録・起動・停止を管理する OS の仕組み
UAC User Account Control、ユーザー アカウント制御 管理者権限への昇格を制御する Windows のセキュリティ機構
Shell 拡張 シェル拡張 エクスプローラーなどのプロセスへ読み込まれて動く COM コンポーネント
Evergreen 常時更新方式 WebView2 Runtime を自動更新される共有の 1 本に任せる配布モード
Fixed Version 版固定方式 特定バージョンの WebView2 Runtime を自分のアプリに同梱する配布モード
arch architecture、CPU アーキテクチャ x86 / x64 / Arm64 のこと

この記事の知識マップ

Windowsアプリは配布物を1個のEXEへ寄せること自体はかなり可能で、.NETのself-containedやsingle-file・Native AOT、C/C++の静的リンク(/MT)が使えるが、OSバージョンやアーキテクチャ、システムDLL、セキュリティモデルへの依存は残り、対象Windowsへの依存をゼロにはできない。self-containedとframework-dependentは排他的な選択肢で、Native AOTは実質single-fileとして動く代わりに組み込みCOMなどが使えなくなる。WebView2はEvergreenかFixed Versionかでランタイムの提供方法を選ぶ必要があり、WinUI 3はpackaged(MSIX)かunpackagedかでPublishSingleFileの可否が変わる。Shell拡張やドライバーは登録・署名の設計が本題になるため、無理に1EXEへ圧縮するよりapp-local配布のほうが保守しやすいことが多い。

Windowsアプリのシングルバイナリ化の知識マップ配布物を1個にすることとOSへの依存を消すことが別の軸であること、.NETとC/C++それぞれの単一ファイル化手段、WebView2やWinUI 3のようなホスト統合が配布方式そのものを制約することを示す図前提とする両立しない両立しない前提とする前提とする両立しない前提とする利用する利用する両立しない利用する前提とするで構成できるで構成できる両立しない利用する前提とする用いるのは非推奨推奨される対応前提とする利用する前提とするシングルバイナリ化(1ファイル配布)対象Windowsへの依存self-contained発行framework-dependent発行(.NET)Native AOTbuilt-in COM interopsingle-file発行(.NET)CRTの静的リンク(/MT)CRTの動的リンク(/MD)Visual C++ 再頒布可能パッケージUCRT(Universal C Runtime)MSIXパッケージング(packaged)WinUI 3 / Windows App SDKMicrosoft Edge WebView2WebView2 RuntimeEvergreen配布モード(WebView2)Fixed Versionランタイムシェル拡張(エクスプローラー拡張)COM(コンポーネントオブジェクトモデル)ドライバー署名ドライバーパッケージapp-local配布(アプリ隣接配布)

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全22件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. 「シングルバイナリ」は 4 段階に分けて考える

段階を先に図にすると、こうなります。下から順に難しくなり、一番上は Windows では到達できません。

レベル A: 配布物が 1 個かなり可能レベル B: 言語ランタイムの事前インストールが不要かなり可能レベル C: インストールや登録が不要アプリ種別によるレベル D: 対象 Windows に依存しないWindows では到達できない

図3: シングルバイナリの4段階。下から順に難しくなり、レベルDには到達できない。

「シングルバイナリにしたい」という話は、この 4 段のうちどこを指しているのかで、必要な作業がまったく変わります。

2.1 レベル A: 配布物が 1 個

一番表面的なのはこれです。

  • メールで 1 個送れる
  • USB に 1 個置けばよい
  • 展開先に app.exe だけ置く

これは 見た目の配布単位 の話です。実際には起動時に一時展開していても、OS 側の DLL に依存していても、この条件だけなら満たせます。

2.2 レベル B: 言語ランタイムの事前インストールが不要

次は、対象マシンにあらかじめ .NET ランタイムや VC++ 再頒布可能パッケージを入れなくても動く状態です。

  • C/C++ の静的リンク
  • .NET の self-contained
  • .NET の single-file
  • .NET Native AOT

このレベルになると、「単体で持っていける」感じはぐっと強くなります。

2.3 レベル C: インストールや登録が不要

ここから急に難しくなります。

単なる EXE なら、置くだけで動くことがあります。でも、こういうものは別です。

  • Shell 拡張
  • Windows サービス
  • カスタム URL スキームやファイル関連付け
  • ドライバ
  • Explorer や Office など、他プロセスに読み込まれるコンポーネント

この領域は ファイルを置くだけ では済みません。OS 側の登録や、ホスト側との結線が必要です。

置くだけでは済まない領域Shell拡張、Windowsサービス、ファイル関連付け、ドライバ、他プロセスに読み込まれるコンポーネントは、ファイルを置くだけでは済まず、OS側の登録やホスト側との結線が必要であることを示す図。Shell拡張・サービス置くだけでは済まない関連付け・ドライバ他プロセスに読み込まれる物OS側の登録・ホストとの結線が要る

図4: レベルCで急に難しくなるのは、OSへの登録が要る領域だから。

2.4 レベル D: 対象 Windows に依存しない

これは Windows では無理です。

Windows アプリは最終的に Windows の API、ローダー、セキュリティモデル、デバイススタックの上で動くからです。single binary 化できるのは、アプリ側の責任範囲まで です。OS 自体まで持っていくわけではありません。

3. かなり 1 EXE にしやすい領域

Windows でも、比較的 1 EXE に寄せやすいアプリはあります。

  • 単独起動されるデスクトップツール
  • EXE 自身が UI と処理を持つ業務アプリ
  • 通信、ファイル処理、ログ収集、監視、装置制御のようなツール
  • Explorer や Office のホスト統合を必要としないもの
  • Web ランタイムを前提にしない UI

このタイプなら、アプリ本体に含めやすいものは多いです。

  • 自前コード
  • リソース
  • マニフェスト
  • 既定設定
  • テンプレートデータ
  • 一部のサードパーティ ライブラリ
  • 言語ランタイム本体

さらに、DLL を完全に EXE 内へ埋め込まなくても、EXE の隣に DLL を置く app-local 配布 は Windows では普通に有力です。実務では、

  • app.exe 1 個
  • または app.exe + 隣接 DLL 数個
  • ただしインストーラ不要、管理者権限不要、xcopy で配れる

この形のほうが、無理に 1 EXE へ圧縮するより保守しやすいことが珍しくありません。

app-local配布という落としどころDLLを完全にEXEへ埋め込まなくても、EXEの隣にDLLを置き、インストーラ不要・管理者権限不要でxcopy配布できる形なら、無理に1EXEへ圧縮するより保守しやすいことが珍しくないことを示す図。app.exe+隣接DLL数個インストーラ不要・管理者権限不要xcopyで配れる無理な1EXE圧縮より保守しやすいことも

図5: 1EXEにこだわらなくても、app-local配布で目的を満たせることは多い。

4. 1 EXE でも消えない Windows 依存

「EXE 1 個なら、対象 Windows に依存しない」と思ってしまうと、ここで事故ります。実際には、1 EXE にしても残る依存があります。

4.1 OS バージョン依存

Windows API にはそれぞれ最低サポート OS があります。x64 / Arm64 の違いもあります。つまり単一 EXE にしても、

  • Windows 10 まで動くのか
  • Windows 11 前提なのか
  • Windows Server でも動かすのか
  • x86 / x64 / Arm64 のどれを対象にするのか

は最初に固定する必要があります。

4.2 システム DLL 依存

こちらが 1 EXE にしたつもりでも、実行時には当然ながら OS 提供のコンポーネントを使っています。

  • kernel32.dll
  • user32.dll
  • advapi32.dll
  • COM 基盤
  • サービス制御基盤

このあたりは Windows 側の責任範囲です。

4.3 セキュリティモデル依存

  • UAC
  • ファイル ACL
  • サービス制御マネージャ
  • レジストリ
  • ドライバ署名ポリシー

こうしたものは、アプリが単独で抱え込めません。

1EXEにしても残る依存1EXEにしても、Windows APIの最低サポートOSやアーキテクチャ、kernel32.dllなどのシステムDLLやCOM基盤、UACやACL、ドライバ署名ポリシーといったセキュリティモデルへの依存は残ることを示す図。1 EXEのアプリOSバージョンとarchシステムDLL・COM基盤セキュリティモデルどれもアプリ側では抱え込めない

図6: 配布物を1個にしても、Windows側の責任範囲への依存は残る。

4.4 ホストやランタイム依存

単独起動 EXE ではなく、何かのホストに載る設計だと依存は一気に増えます。

  • WebView2 を使う: WebView2 Runtime が要る
  • WinUI 3 / Windows App SDK を使う: 配布モードの整理が要る
  • Shell 拡張を作る: Explorer 側への登録が要る

つまり、UI や統合の選択が、そのまま配布の難しさになる ことが多いです。

UIと統合の選択が配布の難しさになるWebView2を使うとWebView2 Runtimeが要り、WinUI 3を使うと配布モードの整理が要り、Shell拡張を作るとExplorer側への登録が要るというように、ホストやランタイムの選択がそのまま配布の難しさになることを示す図。WebView2を使うRuntimeが要るWinUI 3を使う配布モードの整理が要るShell拡張を作るExplorerへの登録が要る

図7: 単独起動EXEから離れるほど、依存は一気に増える。

5. 技術別に見る現実的な落としどころ

5.1 ネイティブ C/C++

ネイティブ C/C++ は、single binary 化の自由度が高い側です。静的リンクを選ぶ余地があり、単独起動 EXE ならかなり寄せやすいです。

ただし、全部を 1 ファイルに押し込むことより、

  • UCRT や VC++ ランタイムをどうするか
  • サードパーティ DLL を app-local に置くか
  • 対象 CPU / OS をどこまで絞るか

のほうが実務上は重要です。

入口: /MT/MD の違い

MSVC で「ランタイムを抱えるかどうか」を決めているのは、実質このコンパイラ オプションです。

オプション リンクされるもの 対象機に必要なもの
/MD ucrt.libvcruntime.lib という DLL 用インポート ライブラリ。実行時は ucrtbase.dllvcruntime<バージョン>.dll を使う UCRT と VC++ ランタイム
/MT libucrt.liblibvcruntime.liblibcmt.lib を静的リンク 追加ランタイムなし

最小の試し方は、Developer Command Prompt でこれだけです。

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

ここで注意したい点が 3 つあります。

  • UCRT は Visual Studio 2015 で C ランタイムが分割されたときに Windows の構成要素 になり、Windows 10 以降には OS の一部として同梱されています。それより前の Windows を対象にするなら、vcredist での再頒布が要ります。
  • DLL 側を /MT で作るのは推奨されません。 静的リンクした CRT は状態がその DLL の中だけに閉じるため、EXE と DLL でメモリ確保やロケール、_set_se_translator の効き方がずれます。「EXE は /MT、同梱 DLL も /MT」と雑に揃えると、境界をまたいだ確保・解放で事故ります。
  • /clr/MT は併用できません。C++/CLI が混ざるなら /MD 側です。
静的リンクの3つの注意UCRTはWindows 10以降OSに同梱されるがそれより前はvcredistでの再頒布が要ること、DLL側を/MTで作るとCRTの状態がDLL内に閉じて境界をまたぐ確保・解放で事故ること、/clrと/MTは併用できないことを示す図。/MTで静的リンク古いWindowsはvcredistが要るDLL側の/MTは非推奨/clrとは併用できない境界をまたぐ確保・解放で事故る

図8: /MTは強力だが、UCRTの前提・DLL境界・C++/CLIの3点に注意が要る。

5.2 .NET

.NET は single-fileself-containedNative AOT があるので、見た目の配布単位はかなり小さくできます。

ただし区別は必要です。

  • framework-dependent: 対象環境の .NET に依存
  • self-contained: .NET ランタイムを抱える
  • single-file: 配布物を 1 つへ寄せる
  • Native AOT: さらに起動時依存を減らすが、機能制約もある

「single-file だから OS 依存が減る」わけではありません。減るのは主に アプリ配布物のまとまり です。

single-fileで減るのは配布物のまとまりframework-dependentは対象環境の.NETに依存し、self-containedはランタイムを抱え、single-fileは配布物を1つへ寄せるだけで、single-fileだからOS依存が減るわけではないことを示す図。framework-dependent対象環境の.NETに依存self-contained.NETランタイムを抱えるsingle-file配布物を1つへ寄せるOS依存が減るわけではない

図9: .NETの発行形態ごとに、減るものと残るものが違う。

入口: dotnet publish の 3 パターン

まず動かして違いを見るなら、この 3 本で足ります。

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

PublishSingleFilePublishAot は、コマンドラインではなく csproj に書くほうが公式の推奨です。ビルド中の互換性解析が有効になり、発行してから気づく事故が減るためです。

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

なお RuntimeIdentifier を指定すると SelfContained は既定で true になります。framework-dependent にしたいときは --self-contained false を明示してください。Native AOT を Windows で発行するには、Visual Studio の C++ によるデスクトップ開発 ワークロードが要ります。

トレードオフ: 「1 個にする」は無料ではない

ここは「できる」だけ書くと事故るので、代償のほうも並べておきます。

single-file の起動時展開

  • 既定では、バンドルされるのは managed DLL だけです。起動時にメモリ上へ読み込まれ、フォルダーへは展開されません。ランタイム本体のネイティブ バイナリは別ファイルのまま残ります。
  • ネイティブ バイナリまで 1 個に含めたいなら IncludeNativeLibrariesForSelfExtract、全部を実行前に展開させるなら IncludeAllContentForSelfExtract を使います。どちらも「展開してから起動する」動作 になり、Windows では %TEMP%\.net の下へ展開されます。DOTNET_BUNDLE_EXTRACT_BASE_DIR で場所を変えられますが、権限の違うユーザーやサービスから書ける場所にしないでください。
  • EnableCompressionInSingleFile を有効にすると EXE はかなり小さくなりますが、起動時にメモリ上で伸長するぶん起動が遅くなります。公式も「使う前にサイズと起動コストの両方を測れ」という書き方をしています。影響はアプリによって大きく変わります。

single-file の API 非互換

配布物を 1 個にすると、ファイルパス前提のコードが静かに壊れます。

API single-file での挙動
Assembly.Location 空文字列を返す
Assembly.CodeBase PlatformNotSupportedException
Assembly.GetFile IOException
Module.Name <Unknown> という文字列を返す

EXE の隣のファイルを触るなら AppContext.BaseDirectory、実行ファイルのパスが要るなら Environment.ProcessPath に寄せます。サードパーティ製ライブラリが内部でこれらを使っていることもあるので、single-file 化したら 一度は実機で起動確認 が要ります。

パス前提のコードが静かに壊れる配布物を1個にするとAssembly.Locationが空文字列を返すなどファイルパス前提のAPIの挙動が変わるため、EXEの隣は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
  • トリミングが必須なので、トリミングの制約もそのまま乗ります
  • 実質 single-file なので、上の API 非互換もそのまま乗ります
  • System.Linq.Expressions は常にインタープリタ動作になり、実行時生成コードより遅くなります

対象プラットフォームも決め打ちです。.NET 8 では Windows は x64 と Arm64、.NET 9 以降で x86 が加わりました。「AnyCPU で 1 個」という発想は最初から成立しません。

Native AOTの代償Native AOTは起動時依存を減らせる代わりに、動的読み込みや実行時コード生成、C++/CLI、Windowsの組み込みCOMが使えず、トリミングとsingle-fileの制約も乗り、対象プラットフォームも決め打ちになることを示す図。Native AOT起動時依存を減らせる動的読み込み・実行時生成が不可C++/CLI・組み込みCOMが不可トリミングとsingle-fileの制約も乗る対象OSとarchは決め打ち

図11: Native AOTの利点は、使えなくなる機能とセットで見る。

5.3 WebView2

WebView2 を採用すると、single binary の難しさはがらりと変わります。ここでの本題は EXE の数ではなく、WebView2 Runtime をどう扱うか です。

「1 EXE にできるか」より先に、考えるべき問いがあります。

  • Runtime を既存環境前提にするか
  • Evergreen を使うか
  • Fixed Version を同梱するか
  • オフライン配布でどこまで責任を持つか

Evergreen と Fixed Version の違いは、ざっくりこうです。

  Evergreen Fixed Version
更新するのは Microsoft。自動更新される 自分。アプリの更新に合わせて入れ替える
端末上の実体 全アプリで 1 本を共有 アプリごとに同梱
配布サイズ ほぼ増えない 250 MB を超える
入れ方 Bootstrapper は約 2 MB で、必要なぶんをダウンロードする。オフラインなら Standalone Installer を同梱する 展開したバイナリ一式をアプリと一緒に配る
既存の制約 端末に入っているか、起動前に確認する必要がある ネットワーク経路や UNC パスからは実行できない

Evergreen Runtime は Windows 11 には OS の一部としてプリインストールされています。ただし Windows 10 側には入っていない端末も残るため、Microsoft 自身も「Evergreen を選んでも Runtime は配布したほうがよい」という書き方をしています。つまり アプリ側で有無を確認して足りなければ入れる 手順は、どちらにしても要ります。Fixed Version は「更新のタイミングを自分で握れる」代わりに、配布物が数百 MB 増えます。1 EXE の話とは別に、ここを先に決める必要があります。

WebView2は配布方式を先に決めるEvergreenはMicrosoftが自動更新する共有の1本だが端末に入っていないこともあり、Fixed Versionは更新を自分で握れる代わりに配布物が数百MB増えるため、どちらを選んでもアプリ側で有無を確認して足りなければ入れる手順が要ることを示す図。WebView2を採用Evergreen: 共有の1本を自動更新Fixed Version: 自分で同梱・更新有無を確認して足りなければ入れる配布物が数百MB増える

図12: WebView2の本題はEXEの数ではなく、Runtimeの扱いにある。

5.4 WinUI 3 / Windows App SDK

WinUI 3 も、採用した時点で配布要件が変わります。UI 技術の選択が、そのまま配布方式の選択になります。

具体的には、決めるものが 2 軸あります。

  • packaging: packaged(MSIX)か、外部の場所を指す packaged か、unpackaged か
  • runtime: framework-dependent か self-contained か

そして single binary の観点で効いてくるのが、この組み合わせ制約です。

  • PublishSingleFile が使えるのは unpackaged かつ self-contained の WinUI 3 アプリだけで、Windows App SDK 1.5 以降が要ります。
  • packaged なアプリ、および外部の場所を指す packaged なアプリでは PublishSingleFile は使えません。
  • unpackaged にすると package identity を失うので、通知、バックグラウンド タスク、ファイル関連付け、コンテキスト メニュー拡張といった、package identity を前提にした Windows 機能は使えなくなります。

つまり WinUI 3 では、「1 EXE にする」と「Windows の拡張機能を使う」が 正面からぶつかります。single binary を最優先にするなら、最初に UI 技術の前提を見直す ほうが早いことがよくあります。

1EXEとpackage identityの衝突WinUI 3でPublishSingleFileが使えるのはunpackagedかつself-containedの構成だけで、unpackagedにするとpackage identityを失い、通知やファイル関連付けなどの機能が使えなくなるため、1EXEとWindowsの拡張機能が正面からぶつかることを示す図。PublishSingleFileを使いたいunpackaged+self-containedだけpackage identityを失う通知・関連付けなどが使えない1EXEと拡張機能が正面からぶつかる

図13: WinUI 3では、1EXEを選ぶことがpackage identityを捨てることになる。

6. 本質的に「登録・依存」が必要な領域

6.1 Shell 拡張

Explorer に読み込まれる Shell 拡張は、単なる「置くだけ EXE」とは別物です。ここはファイル数より、Explorer へどう登録するか が本題です。

6.2 Windows サービス

サービス本体の exe 自体は 1 ファイルにできても、配布は別問題です。

  • SCM への登録
  • 権限
  • 起動アカウント
  • 復旧設定

を考える必要があります。つまりサービスは、「1 EXE にする」より「どうインストールするか」を詰める領域です。

6.3 ドライバ

ドライバはさらに明確です。INF、署名、インストール手順まで含めて成立するので、single binary の土俵に最初から乗りにくいです。

登録と署名が本題の領域Shell拡張はExplorerへの登録、WindowsサービスはSCMへの登録や権限・起動アカウント、ドライバはINFと署名まで含めて成立するため、ファイル数より登録・依存の設計が本題になることを示す図。Shell拡張Explorerへの登録サービスSCM登録・権限・アカウントドライバINFと署名ファイル数より登録の設計が本題

図14: この3領域では「1EXEにする」より「どう登録するか」を詰める。

7. 実務での判断表

ざっくり判断するなら、この表が使いやすいです。

作りたいもの 1 EXE 現実度 先に考えるべきこと
単独起動の Win32 / C++ ツール 高い 静的リンク、対象 OS / arch
単独起動の WinForms / WPF ツール 高い self-contained、single-file、Native AOT の適性
WinUI 3 / Windows App SDK アプリ 配布モード、追加依存
WebView2 ベースのデスクトップ UI 低から中 Runtime の配布方式
Explorer 右クリック拡張やプレビュー 低い COM / レジストリ登録
Windows サービス SCM 登録、権限、更新手順
ドライバ同梱アプリ 低い INF、署名、インストール

この表で一番大事なのは、「バイナリの数」と「配布の責任範囲」は別 だと分かることです。

8. 配布設計で先に決めるべきこと

single binary 化を成功させたいなら、実装より前に決めておきたいことがあります。

8.1 何を 1 個にしたいのかを決める

  • 配布物を 1 個にしたいのか
  • ランタイム事前インストールをなくしたいのか
  • インストーラ不要にしたいのか
  • オフライン更新を簡単にしたいのか

この答えによって、選ぶ技術は変わります。

8.2 最低サポート Windows と arch を最初に固定する

single-file も Native AOT も、基本的に OS / architecture specific です。ここを曖昧にしたまま「とにかく 1 ファイルで」と進めると、最後に API 不足や runtime 不一致で詰まります。

8.3 「同梱するもの」と「Windows に任せるもの」を明文化する

実務では、この表を書いておくだけでかなり事故が減ります。

  • アプリに同梱するもの
    • 本体 exe
    • 自前 DLL
    • 設定テンプレート
    • self-contained runtime
  • Windows に任せるもの
    • システム DLL
    • OS API
    • SCM / レジストリ / Explorer
    • ドライバ基盤
  • 別途前提とするもの
    • WebView2 Runtime
    • VC++ Redistributable
    • Office / Excel
    • 専用ドライバ
責任範囲の3分類を明文化するアプリに同梱するもの、Windowsに任せるもの、別途前提とするものの3分類を書いておくだけで、配布の事故がかなり減ることを示す図。配布物の責任範囲アプリに同梱する物Windowsに任せる物別途前提とする物書いておくだけで事故が減る

図15: 同梱・OS任せ・別途前提の3分類を、先に明文化しておく。

8.4 single binary を優先するなら、ホスト統合を減らす

これはかなり効きます。

  • Shell 拡張をやめて普通の EXE にする
  • サービス化せず、タスクスケジューラや明示起動で済ませる
  • WebView2 ではなくネイティブ UI を使う
  • COM は自プロセス内で閉じる

要するに、OS に「読み込ませる」「登録する」設計を減らす ほど、single binary に近づきます。

ホスト統合を減らすほど近づくShell拡張をやめて普通のEXEにし、サービス化せずタスクスケジューラや明示起動で済ませ、WebView2ではなくネイティブUIを使うなど、OSに読み込ませ登録する設計を減らすほどsingle binaryに近づくことを示す図。Shell拡張をやめて普通のEXEにOSに登録する設計を減らすサービス化せず明示起動にWebView2でなくネイティブUIにsingle binaryに近づく

図16: single binaryを優先するなら、ホスト統合そのものを減らす。

9. まとめ

Windows における single binary 化は、かなりのところまで可能です。ただし、行き着くところはこの一文です。

アプリを 1 EXE にすることはできる。 でも、そのアプリが依存する Windows まで 1 EXE にすることはできない。

特に覚えておきたい点を 5 つ挙げます。

  • 単独起動の普通の EXE なら、相当なところまで 1 ファイル配布へ寄せられる
  • C/C++ の静的リンク、.NET single-file、Native AOT は有力
  • ただし OS バージョン、arch、システム DLL、セキュリティモデルへの依存は消えない
  • Shell 拡張、サービス、ドライバ、WebView2、WinUI 3 の一部は、OS 登録や追加ランタイムの話が本体になる
  • single binary の成否は、「何を 1 個にしたいのか」を最初に切り分けることで決まる

もし single binary を強く優先するなら、技術選定の時点で OS との結合度を下げる 方向で設計したほうが、はるかに成功しやすいです。

成功の鍵はOSとの結合度アプリを1EXEにすることはできても、そのアプリが依存するWindowsまで1EXEにすることはできないため、single binaryを強く優先するなら技術選定の時点でOSとの結合度を下げる方向で設計するほうが成功しやすいことを示す図。アプリを1EXEにするできる依存するWindowsまで1EXEにできないだから技術選定でOSとの結合度を下げる

図17: 1EXE化の成否は、最初のOSとの結合度の設計で決まる。

10. 参考資料

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

Windowsアプリを1つのEXEファイルだけで配布できますか?
単独起動されるデスクトップツールであれば、かなり高いところまで可能です。C/C++の静的リンク、.NETのself-containedやsingle-file、Native AOTを使えば、配布物を1個のEXEに寄せられます。ただし、1EXEにできることと、対象Windowsに依存しないことは別の話で、OSバージョン・アーキテクチャ・システムDLL・セキュリティモデルへの依存は消えません。
.NETのsingle-fileにすればOS依存はなくなりますか?
なりません。single-fileで減るのは主にアプリ配布物のまとまりで、OS依存が減るわけではありません。framework-dependentは対象環境の.NETに依存し、self-containedは.NETランタイムを抱え、Native AOTは起動時依存をさらに減らせますが機能制約もあります。single-fileもNative AOTも基本的にOS・アーキテクチャ固有なので、最低サポートWindowsとarchは最初に固定する必要があります。
1EXEにしにくいのはどんなアプリですか?
Shell拡張、Windowsサービス、ドライバ、WebView2ベースのUI、WinUI 3の一部です。これらはファイル数よりも、OSへの登録や追加ランタイムの扱いが本題になります。たとえばShell拡張はExplorerへの登録が必要で、サービスはSCMへの登録・権限・起動アカウントの設計が要り、ドライバはINFと署名まで含めて成立するため、置くだけの配布は成立しません。WebView2はWebView2 Runtimeの配布方式を先に決める必要があります。
無理に1EXEへ埋め込むより良い配布方法はありますか?
EXEの隣にDLLを置くapp-local配布が有力です。app.exeに隣接DLL数個という形でも、インストーラ不要・管理者権限不要でxcopy配布できるなら、無理に1EXEへ圧縮するより保守しやすいことが珍しくありません。重要なのは、配布物を1個にしたいのか、ランタイムの事前インストールをなくしたいのか、インストーラを不要にしたいのかを最初に切り分けることです。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る