更新履歴(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 アプリの実務向けに整理します。
flowchart TB
accTitle: 1EXEにできてもOS依存は消えない
accDescr: シングルバイナリにしたいという要望には配布の話と依存の話が混ざりやすく、配布物を1EXEへ寄せることはかなりできても、対象Windowsへの依存をゼロにすることはできないことを示す図。
a0["「シングルバイナリにしたい」"] --> a1["配布の話: 1個にしたい・置くだけにしたい"]
a0 --> a2["依存の話: ランタイム不要・OS非依存"]
a1 --> a3["1EXEへ寄せるのはかなり可能"]
a2 --> a4["OS依存をゼロにはできない"]
図1: 「1ファイルで配りたい」には、可能な話と不可能な話が混ざっている。
1. まず結論
最初に結論だけまとめると、こうです。
- 普通のデスクトップ EXE なら、かなり高いところまで single binary 化できます
- ただし、1 EXE にできること と 対象 Windows に依存しないこと は別です
- Shell 拡張、Windows サービス、ドライバ、WebView2、WinUI 3 の一部は、ファイル数より OS へ何を登録し、何を前提にするか のほうが本題になりやすいです
- 実務で一番大事なのは、single binary 化したいのか、インストーラ不要にしたいのか、OS 依存を減らしたいのか を分けて決めることです
言い換えると、Windows での線引きはこうなります。
- 配布物を 1 個に寄せる: かなり可能
- 追加ランタイムを抱え込む: かなり可能
- xcopy 配布に寄せる: アプリ種別による
- 対象 Windows 側の依存を消す: 不可能
flowchart TB
accTitle: Windowsでの線引き
accDescr: 配布物を1個に寄せることと追加ランタイムを抱え込むことはかなり可能、xcopy配布に寄せられるかはアプリ種別により、対象Windows側の依存を消すことは不可能という線引きを示す図。
b1["配布物を1個に寄せる"] --> b2["かなり可能"]
b3["追加ランタイムの同梱"] --> b2
b4["xcopy配布に寄せる"] --> b5["アプリ種別による"]
b6["OS側の依存を消す"] --> b7["不可能"]
図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配布のほうが保守しやすいことが多い。
flowchart LR
accTitle: Windowsアプリのシングルバイナリ化の知識マップ
accDescr: 配布物を1個にすることとOSへの依存を消すことが別の軸であること、.NETとC/C++それぞれの単一ファイル化手段、WebView2やWinUI 3のようなホスト統合が配布方式そのものを制約することを示す図
single_binary_packaging["シングルバイナリ化(1ファイル配布)"]
windows_os_dependency["対象Windowsへの依存"]
self_contained_deployment["self-contained発行"]
framework_dependent_deployment["framework-dependent発行(.NET)"]
native_aot["Native AOT"]
builtin_com_interop["built-in COM interop"]
single_file_publish["single-file発行(.NET)"]
static_linking_crt["CRTの静的リンク(/MT)"]
dynamic_linking_crt["CRTの動的リンク(/MD)"]
vc_redistributable["Visual C++ 再頒布可能パッケージ"]
ucrt["UCRT(Universal C Runtime)"]
msix_packaging["MSIXパッケージング(packaged)"]
winui3["WinUI 3 / Windows App SDK"]
webview2["Microsoft Edge WebView2"]
webview2_runtime["WebView2 Runtime"]
webview2_evergreen["Evergreen配布モード(WebView2)"]
webview2_fixed_version["Fixed Versionランタイム"]
shell_extension["シェル拡張(エクスプローラー拡張)"]
com["COM(コンポーネントオブジェクトモデル)"]
driver_signing["ドライバー署名"]
driver_package["ドライバーパッケージ"]
app_local_deployment["app-local配布(アプリ隣接配布)"]
single_binary_packaging -->|"前提とする"| windows_os_dependency
self_contained_deployment -->|"両立しない"| framework_dependent_deployment
native_aot -->|"両立しない"| builtin_com_interop
single_file_publish -.->|"前提とする"| windows_os_dependency
native_aot -.->|"前提とする"| windows_os_dependency
static_linking_crt -->|"両立しない"| dynamic_linking_crt
dynamic_linking_crt -->|"前提とする"| vc_redistributable
static_linking_crt -->|"利用する"| ucrt
dynamic_linking_crt -->|"利用する"| ucrt
msix_packaging -.->|"両立しない"| single_file_publish
winui3 -.->|"利用する"| msix_packaging
webview2 -->|"前提とする"| webview2_runtime
webview2_runtime -->|"で構成できる"| webview2_evergreen
webview2_runtime -->|"で構成できる"| webview2_fixed_version
webview2_evergreen -->|"両立しない"| webview2_fixed_version
shell_extension -->|"利用する"| com
driver_signing -->|"前提とする"| driver_package
driver_package -->|"用いるのは非推奨"| single_binary_packaging
app_local_deployment -->|"推奨される対応"| single_binary_packaging
self_contained_deployment -.->|"前提とする"| windows_os_dependency
single_binary_packaging -.->|"利用する"| static_linking_crt
webview2 -.->|"前提とする"| windows_os_dependency
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全22件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 「シングルバイナリ」は 4 段階に分けて考える
段階を先に図にすると、こうなります。下から順に難しくなり、一番上は Windows では到達できません。
flowchart BT
A["レベル A: 配布物が 1 個<br/>かなり可能"]
B["レベル B: 言語ランタイムの事前インストールが不要<br/>かなり可能"]
C["レベル C: インストールや登録が不要<br/>アプリ種別による"]
D["レベル D: 対象 Windows に依存しない<br/>Windows では到達できない"]
A --> B
B --> C
C --> D
図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 側の登録や、ホスト側との結線が必要です。
flowchart TB
accTitle: 置くだけでは済まない領域
accDescr: Shell拡張、Windowsサービス、ファイル関連付け、ドライバ、他プロセスに読み込まれるコンポーネントは、ファイルを置くだけでは済まず、OS側の登録やホスト側との結線が必要であることを示す図。
c1["Shell拡張・サービス"] --> c4["置くだけでは済まない"]
c2["関連付け・ドライバ"] --> c4
c3["他プロセスに読み込まれる物"] --> c4
c4 --> c5["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.exe1 個- または
app.exe+ 隣接 DLL 数個 - ただしインストーラ不要、管理者権限不要、xcopy で配れる
この形のほうが、無理に 1 EXE へ圧縮するより保守しやすいことが珍しくありません。
flowchart TB
accTitle: app-local配布という落としどころ
accDescr: DLLを完全にEXEへ埋め込まなくても、EXEの隣にDLLを置き、インストーラ不要・管理者権限不要でxcopy配布できる形なら、無理に1EXEへ圧縮するより保守しやすいことが珍しくないことを示す図。
d1["app.exe+隣接DLL数個"] --> d2["インストーラ不要・管理者権限不要"]
d2 --> d3["xcopyで配れる"]
d3 -.-> d4["無理な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.dlluser32.dlladvapi32.dll- COM 基盤
- サービス制御基盤
このあたりは Windows 側の責任範囲です。
4.3 セキュリティモデル依存
- UAC
- ファイル ACL
- サービス制御マネージャ
- レジストリ
- ドライバ署名ポリシー
こうしたものは、アプリが単独で抱え込めません。
flowchart TB
accTitle: 1EXEにしても残る依存
accDescr: 1EXEにしても、Windows APIの最低サポートOSやアーキテクチャ、kernel32.dllなどのシステムDLLやCOM基盤、UACやACL、ドライバ署名ポリシーといったセキュリティモデルへの依存は残ることを示す図。
e0["1 EXEのアプリ"] --> e1["OSバージョンとarch"]
e0 --> e2["システムDLL・COM基盤"]
e0 --> e3["セキュリティモデル"]
e1 --> e4["どれもアプリ側では抱え込めない"]
e2 --> e4
e3 --> e4
図6: 配布物を1個にしても、Windows側の責任範囲への依存は残る。
4.4 ホストやランタイム依存
単独起動 EXE ではなく、何かのホストに載る設計だと依存は一気に増えます。
- WebView2 を使う: WebView2 Runtime が要る
- WinUI 3 / Windows App SDK を使う: 配布モードの整理が要る
- Shell 拡張を作る: Explorer 側への登録が要る
つまり、UI や統合の選択が、そのまま配布の難しさになる ことが多いです。
flowchart TB
accTitle: UIと統合の選択が配布の難しさになる
accDescr: WebView2を使うとWebView2 Runtimeが要り、WinUI 3を使うと配布モードの整理が要り、Shell拡張を作るとExplorer側への登録が要るというように、ホストやランタイムの選択がそのまま配布の難しさになることを示す図。
f1["WebView2を使う"] --> f2["Runtimeが要る"]
f3["WinUI 3を使う"] --> f4["配布モードの整理が要る"]
f5["Shell拡張を作る"] --> f6["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.lib と vcruntime.lib という DLL 用インポート ライブラリ。実行時は 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
ここで注意したい点が 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側です。
flowchart TB
accTitle: 静的リンクの3つの注意
accDescr: UCRTはWindows 10以降OSに同梱されるがそれより前はvcredistでの再頒布が要ること、DLL側を/MTで作るとCRTの状態がDLL内に閉じて境界をまたぐ確保・解放で事故ること、/clrと/MTは併用できないことを示す図。
g0["/MTで静的リンク"] --> g1["古いWindowsはvcredistが要る"]
g0 --> g2["DLL側の/MTは非推奨"]
g0 --> g3["/clrとは併用できない"]
g2 -.-> g4["境界をまたぐ確保・解放で事故る"]
図8: /MTは強力だが、UCRTの前提・DLL境界・C++/CLIの3点に注意が要る。
5.2 .NET
.NET は single-file、self-contained、Native AOT があるので、見た目の配布単位はかなり小さくできます。
ただし区別は必要です。
- framework-dependent: 対象環境の .NET に依存
- self-contained: .NET ランタイムを抱える
- single-file: 配布物を 1 つへ寄せる
- Native AOT: さらに起動時依存を減らすが、機能制約もある
「single-file だから OS 依存が減る」わけではありません。減るのは主に アプリ配布物のまとまり です。
flowchart TB
accTitle: single-fileで減るのは配布物のまとまり
accDescr: framework-dependentは対象環境の.NETに依存し、self-containedはランタイムを抱え、single-fileは配布物を1つへ寄せるだけで、single-fileだからOS依存が減るわけではないことを示す図。
h1["framework-dependent"] --> h2["対象環境の.NETに依存"]
h3["self-contained"] --> h4[".NETランタイムを抱える"]
h5["single-file"] --> h6["配布物を1つへ寄せる"]
h6 -.-> h7["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
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++ によるデスクトップ開発 ワークロードが要ります。
トレードオフ: 「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 化したら 一度は実機で起動確認 が要ります。
flowchart TB
accTitle: パス前提のコードが静かに壊れる
accDescr: 配布物を1個にするとAssembly.Locationが空文字列を返すなどファイルパス前提のAPIの挙動が変わるため、EXEの隣はAppContext.BaseDirectory、実行ファイルのパスはEnvironment.ProcessPathに寄せ、一度は実機で起動確認することを示す図。
i1["single-file化する"] --> i2["パス前提のAPIの挙動が変わる"]
i2 --> i3["隣のファイルはAppContext.BaseDirectory"]
i2 --> i4["実行ファイルはEnvironment.ProcessPath"]
i3 --> i5["一度は実機で起動確認する"]
i4 --> i5
図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 個」という発想は最初から成立しません。
flowchart TB
accTitle: Native AOTの代償
accDescr: Native AOTは起動時依存を減らせる代わりに、動的読み込みや実行時コード生成、C++/CLI、Windowsの組み込みCOMが使えず、トリミングとsingle-fileの制約も乗り、対象プラットフォームも決め打ちになることを示す図。
j0["Native AOT"] --> j1["起動時依存を減らせる"]
j0 --> j2["動的読み込み・実行時生成が不可"]
j0 --> j3["C++/CLI・組み込みCOMが不可"]
j2 -.-> j4["トリミングとsingle-fileの制約も乗る"]
j3 -.-> j5["対象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 の話とは別に、ここを先に決める必要があります。
flowchart TB
accTitle: WebView2は配布方式を先に決める
accDescr: EvergreenはMicrosoftが自動更新する共有の1本だが端末に入っていないこともあり、Fixed Versionは更新を自分で握れる代わりに配布物が数百MB増えるため、どちらを選んでもアプリ側で有無を確認して足りなければ入れる手順が要ることを示す図。
k0["WebView2を採用"] --> k1["Evergreen: 共有の1本を自動更新"]
k0 --> k2["Fixed Version: 自分で同梱・更新"]
k1 --> k3["有無を確認して足りなければ入れる"]
k2 -.-> k4["配布物が数百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 技術の前提を見直す ほうが早いことがよくあります。
flowchart TB
accTitle: 1EXEとpackage identityの衝突
accDescr: WinUI 3でPublishSingleFileが使えるのはunpackagedかつself-containedの構成だけで、unpackagedにするとpackage identityを失い、通知やファイル関連付けなどの機能が使えなくなるため、1EXEとWindowsの拡張機能が正面からぶつかることを示す図。
m1["PublishSingleFileを使いたい"] --> m2["unpackaged+self-containedだけ"]
m2 --> m3["package identityを失う"]
m3 --> m4["通知・関連付けなどが使えない"]
m4 -.-> m5["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 の土俵に最初から乗りにくいです。
flowchart TB
accTitle: 登録と署名が本題の領域
accDescr: Shell拡張はExplorerへの登録、WindowsサービスはSCMへの登録や権限・起動アカウント、ドライバはINFと署名まで含めて成立するため、ファイル数より登録・依存の設計が本題になることを示す図。
n1["Shell拡張"] --> n2["Explorerへの登録"]
n3["サービス"] --> n4["SCM登録・権限・アカウント"]
n5["ドライバ"] --> n6["INFと署名"]
n4 -.-> n7["ファイル数より登録の設計が本題"]
図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
- 専用ドライバ
flowchart TB
accTitle: 責任範囲の3分類を明文化する
accDescr: アプリに同梱するもの、Windowsに任せるもの、別途前提とするものの3分類を書いておくだけで、配布の事故がかなり減ることを示す図。
p0["配布物の責任範囲"] --> p1["アプリに同梱する物"]
p0 --> p2["Windowsに任せる物"]
p0 --> p3["別途前提とする物"]
p3 -.-> p4["書いておくだけで事故が減る"]
図15: 同梱・OS任せ・別途前提の3分類を、先に明文化しておく。
8.4 single binary を優先するなら、ホスト統合を減らす
これはかなり効きます。
- Shell 拡張をやめて普通の EXE にする
- サービス化せず、タスクスケジューラや明示起動で済ませる
- WebView2 ではなくネイティブ UI を使う
- COM は自プロセス内で閉じる
要するに、OS に「読み込ませる」「登録する」設計を減らす ほど、single binary に近づきます。
flowchart TB
accTitle: ホスト統合を減らすほど近づく
accDescr: Shell拡張をやめて普通のEXEにし、サービス化せずタスクスケジューラや明示起動で済ませ、WebView2ではなくネイティブUIを使うなど、OSに読み込ませ登録する設計を減らすほどsingle binaryに近づくことを示す図。
q1["Shell拡張をやめて普通のEXEに"] --> q4["OSに登録する設計を減らす"]
q2["サービス化せず明示起動に"] --> q4
q3["WebView2でなくネイティブUIに"] --> q4
q4 --> q5["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 との結合度を下げる 方向で設計したほうが、はるかに成功しやすいです。
flowchart TB
accTitle: 成功の鍵はOSとの結合度
accDescr: アプリを1EXEにすることはできても、そのアプリが依存するWindowsまで1EXEにすることはできないため、single binaryを強く優先するなら技術選定の時点でOSとの結合度を下げる方向で設計するほうが成功しやすいことを示す図。
r1["アプリを1EXEにする"] --> r2["できる"]
r3["依存するWindowsまで1EXEに"] --> r4["できない"]
r4 -.-> r5["だから技術選定でOSとの結合度を下げる"]
図17: 1EXE化の成否は、最初のOSとの結合度の設計で決まる。
10. 参考資料
- Microsoft Learn, Create a single file for application deployment
- Microsoft Learn, Native AOT deployment overview
- Microsoft Learn, C runtime (CRT) and C++ standard library (STL) lib files
- Microsoft Learn, Dynamic-link library search order
- Microsoft Learn, Targeting your application for Windows
- Microsoft Learn, Creating Registration-Free COM Objects
- Microsoft Learn, Registering Shell Extension Handlers
- Microsoft Learn, CreateServiceW function
- Microsoft Learn, Overview of INF Files
- Microsoft Learn, Windows driver signing tutorial
- Microsoft Learn, Distribute your app and the WebView2 Runtime
- Microsoft Learn, Windows App SDK deployment guide for self-contained apps
- Microsoft Learn, Package and deploy Windows apps overview
- Microsoft Learn, Packaging overview - Windows apps
- Microsoft Learn, Evergreen vs. fixed version of the WebView2 Runtime
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windowsアプリ 外注・受託開発を依頼する前に整理したいこと
Windowsアプリの外注・受託開発を依頼する前に、既存ソフト改修、装置連携、COM/ActiveX、配布・更新、保守の整理ポイントを解説します。
Time Travel Debugging ── 長期稼働で再現しない不具合を「録画」して巻き戻す
月に一度しか出ない不具合は、クラッシュダンプでは結果しか写りません。WinDbgのTime Travel Debugging(TTD)で実行を録画して巻き戻す方法を、TTD.exeの録画設計、リングバッファ、TTD.Callsクエリ、ダンプとの使い分けまで解説します。
IEモードの次はWebView2でいいのか ── ActiveXが動かない制約と現実的な移行設計
WebView2の基本構造、EvergreenとFixed Versionの配布戦略、ユーザーデータフォルダーの罠、ネイティブとWebの連携方法、そして「ActiveXは動かない」という制約を踏まえたIEモード依存システムからの現実的な移行順序を、社内システム目線で整理します。
ClickOnce とは何か - 仕組み、更新、向いている場面・向いていない場面を実務目線で整理
.NET の Windows デスクトップアプリ配布で使われる ClickOnce について、マニフェスト、更新、キャッシュ、署名、向いている案件・向いていない案件を Mermaid 図つきで整理します。
Windowsアプリで子プロセスを安全に扱うチェックリスト
Windows アプリで子プロセスを安全に扱うには、起動 API よりもプロセス木の所有権と終了手順の設計が重要です。Job Object、終了伝播、標準入出力、watchdog を整理します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
Windows アプリの配布では、single-file 化、ランタイム同梱、WebView2 や WinUI の採用判断、サービス化の是非まで含めて設計したほうが後戻りを減らせます。
技術相談・設計レビュー
『1 EXE にしたい』という要望は、配布単位、OS 依存、登録要否、更新責任を分けて整理すると判断しやすくなります。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- 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個にしたいのか、ランタイムの事前インストールをなくしたいのか、インストーラを不要にしたいのかを最初に切り分けることです。