在 Windows 上,單一執行檔(single binary)能做到什麼程度 - 能收進 1 個 EXE 的範圍、無法消除的 Windows 依賴、以及發布前的判斷表
· 小村 豪 · 發布, 單一執行檔, .NET, C++, WebView2, WinUI
這篇文章起源於下面這則貼文。
「如果可以,就想用 1 個檔案發布」——這在 Windows 上是再普通不過的需求。公司內部工具、設備整合工具、監控端、離線環境,或是盡量避免跑 installer 的現場,做成單一執行檔 都相當有吸引力。
不過這件事若沒有一開始就釐清,討論常常在中途就兜不攏。Windows 上講「想做成單一執行檔」時,裡面其實容易混進下面 4 種需求。
- 發布物想只有 1 個
- 希望不用事先安裝 .NET 或 Visual C++ 的 runtime
- 希望不用 installer、不需系統管理員權限,放著就能跑
- 希望不受「目標 Windows」版本或差異影響
這 4 種需求並不一樣。實務上比較不會偏的思考方式是:
把發布物收成 1 個 EXE 是相當可行的。
但要完全消除對「目標 Windows」的依賴,做不到。
本文就以 Windows 應用實務為角度,把這條界線整理清楚。
1. 先下結論
先直接把結論列出來:
- 一般桌面 EXE,其實可以做到很高程度的 single binary
- 但 能收成 1 個 EXE 與 不依賴目標 Windows 是兩回事
- Shell 擴充、Windows 服務、驅動、WebView2、WinUI 3 的部分情境,重點不在檔案數,而是 要向 OS 註冊什麼、要以什麼為前提
- 實務上最重要的是分清楚:是要 single binary、還是想免 installer、還是想降低 OS 依賴
換句話說,在 Windows 上這條界線其實很實務:
- 把發布物收成 1 個:相當可行
- 把額外 runtime 吞進來:相當可行
- 做到 xcopy 發布:視應用種類而定
- 消除對目標 Windows 的依賴:不可能
2. 把「單一執行檔」分成 4 個層次
2.1 Level A:發布物就只有 1 個
最表面的一層:
- 郵件用 1 個附件寄出
- USB 放 1 個檔
- 到目的地只放
app.exe
這只是 發布單位的外觀。實際上它可能在啟動時暫存解壓縮,也可能依賴 OS 側 DLL,但光就這個條件,是能達成的。
2.2 Level B:目標機器不需預先安裝語言 runtime
接下來是不需要事先裝 .NET runtime 或 VC++ 再發行套件:
- C/C++ 靜態連結
- .NET self-contained
- .NET single-file
- .NET Native AOT
到這一層,「可以單獨帶走」的感覺就相當明顯。
2.3 Level C:不用安裝或註冊
這一層會突然變難。
單純的 EXE 也許放著就能跑,但下面這些就不同:
- Shell 擴充
- Windows 服務
- 自訂 URL scheme 或副檔名關聯
- 驅動
- 會被 Explorer、Office 等其他行程載入的元件
這塊光 放檔案 是不夠的,需要向 OS 註冊,或跟宿主端對接。
2.4 Level D:不依賴目標 Windows
在 Windows 上做不到。
Windows 應用最終仍然要跑在 Windows 的 API、loader、安全模型、裝置堆疊上。single binary 能處理到的範圍,只到 應用這側的責任範圍,並不包含把 OS 本身也帶走。
3. 比較容易收成 1 個 EXE 的領域
在 Windows 上,下列型態的應用比較容易收成 1 個 EXE。
- 單獨啟動的桌面工具
- EXE 本身就同時負責 UI 與處理的業務應用
- 通訊、檔案處理、日誌收集、監控、設備控制這類工具
- 不需要跟 Explorer、Office 做宿主整合的
- 不以 Web runtime 為前提的 UI
這種應用,下列內容可以相當輕鬆地放進本體:
- 自家程式碼
- 資源
- manifest
- 預設設定
- 範本資料
- 部分第三方函式庫
- 語言 runtime 本體
此外,就算沒有把 DLL 完全嵌入 EXE,把 DLL 放在 EXE 旁邊(app-local) 在 Windows 上還是相當實用。實務上,
- 1 個
app.exe - 或是
app.exe+ 幾個相鄰 DLL - 並且不需 installer、不需系統管理員權限、可直接 xcopy 發布
這種形式往往比硬要壓縮成 1 個 EXE 更容易維護。
4. 即使做成 1 個 EXE 也無法消除的 Windows 依賴
如果以為「只要 EXE 只有 1 個,就不依賴目標 Windows」,在這裡就會出事。實際上就算是 1 個 EXE,以下依賴仍在。
4.1 OS 版本依賴
Windows API 各自有最低支援版本,x64/Arm64 也有差異。就算打包成 1 個 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
- 服務控制管理員(SCM)
- 登錄
- 驅動簽章政策
這些,應用程式不可能獨自扛起來。
4.4 宿主與 runtime 依賴
若不是單獨啟動的 EXE,而是要跑在某個宿主裡的設計,依賴會一下子增加:
- 用 WebView2:需要 WebView2 Runtime
- 用 WinUI 3 / Windows App SDK:需要把發布模式釐清
- 做 Shell 擴充:需要向 Explorer 註冊
也就是說,UI 或整合方式的選擇,直接決定了發布的困難度。
5. 依技術別看現實的落地點
5.1 Native C/C++
Native C/C++ 是 single binary 自由度比較高的一側,有靜態連結空間,單獨啟動的 EXE 很容易收攏。
不過比起「全部壓進一個檔」,實務上更重要的是:
- UCRT、VC++ runtime 怎麼處理
- 第三方 DLL 要不要放在 app-local
- 目標 CPU / OS 要收到多窄
5.2 .NET
.NET 有 single-file、self-contained、Native AOT,表面上發布單位可以做得很小。
但要分清楚:
- framework-dependent:仍依賴目標環境的 .NET
- self-contained:把 .NET runtime 吞進去
- single-file:把發布物集中成一個
- Native AOT:進一步減少啟動期依賴,但有功能限制
「single-file 就不依賴 OS」這句話並不對。減少的主要是 應用發布物的集合。
5.3 WebView2
採用 WebView2,single binary 的難度就會大不同。這時的主題不是 EXE 的數量,而是 WebView2 Runtime 怎麼處理。
更正確的問法不是「能不能做成 1 個 EXE」,而是:
- 是否以現場已有 Runtime 為前提
- 是否使用 Evergreen
- 是否同捆 Fixed Version
- 離線部署要扛到哪裡
5.4 WinUI 3 / Windows App SDK
WinUI 3 也是一選下去,發布要求就跟著變。UI 技術的選擇,直接變成發布方式的選擇。
若 single binary 是第一優先,先回頭檢視 UI 技術的前提 往往比較快。
6. 本質上就需要「註冊、依賴」的領域
6.1 Shell 擴充
被 Explorer 載入的 Shell 擴充,跟「放著就能跑的 EXE」本質不同。這塊的主題不是幾個檔,而是 怎麼向 Explorer 註冊。
6.2 Windows 服務
就算服務本身的 exe 可以收成 1 個檔,發布還是另一回事:
- SCM 註冊
- 權限
- 啟動帳號
- 復原設定
都得想。也就是說,服務這塊,重點在「怎麼安裝」,而不是「做成 1 個 EXE」。
6.3 驅動
驅動更是明確:INF、簽章、安裝流程都要備齊才成立,從一開始就不太站在 single binary 的擂台上。
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 個
- 想省掉事先安裝 runtime
- 想免 installer
- 想讓離線更新更簡單
這個答案會決定要選什麼技術。
8.2 先固定最低支援的 Windows 與 arch
single-file 與 Native AOT 基本上都是 OS / architecture specific。若這一點含糊,就硬推「反正收成一個檔」,最後會被 API 缺失或 runtime 不匹配擋下。
8.3 把「同捆」與「交給 Windows」明文化
實務上光是把這張表寫下來就能減少很多意外:
- 要同捆的
- 本體 exe
- 自製 DLL
- 設定範本
- self-contained runtime
- 交給 Windows 的
- 系統 DLL
- OS API
- SCM / 登錄 / Explorer
- 驅動基礎
- 另外以前提存在的
- WebView2 Runtime
- VC++ Redistributable
- Office / Excel
- 專用驅動
8.4 想優先 single binary,就減少宿主整合
這招相當有效。
- 放棄 Shell 擴充,改回普通 EXE
- 不做服務化,改用工作排程器或明示啟動
- 不用 WebView2,改用原生 UI
- COM 都收在自己的行程內
簡單說,越減少「讓 OS 載入」「向 OS 註冊」的設計,就越靠近 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 註冊與額外 runtime
- single binary 成敗,取決於一開始就把「要把什麼收成 1 個」分清楚
若一定要優先 single binary,在技術選型階段就 朝著降低與 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
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows 應用安全處理子行程的 checklist - Job Object、結束傳播、標準輸入輸出、watchdog 的最佳實務
在 Windows 應用上安全處理子行程,關鍵不在挑啟動 API,而是設計行程樹的擁有者與結束流程。本文整理 Job Object 的 KILL_ON_JOB_CLOSE、GUI 與 console 的 graceful shutdown、stdio 平行抽乾與 EOF、w...
.NET 的 Native AOT 是什麼 - 先釐清與 JIT、ReadyToRun、trimming 的差異
把 .NET 的 Native AOT 與 JIT、ReadyToRun、self-contained、single-file、trimming、source generator 放在一起釐清,並從啟動、發布、相依性的角度整理它合適與不合適的情境,幫助讀者判斷該不該採用。
用 Native AOT 把 C# 做成原生 DLL 的方法 - 用 UnmanagedCallersOnly 從 C/C++ 呼叫
從現有 C/C++ 應用程式以 in-process 方式呼叫 C# 邏輯時,本文示範以 Native AOT 將類別庫發佈為原生 DLL,並用 UnmanagedCallersOnly 公開 cdecl 進入點。透過 handle、錯誤碼與扁平 C ABI 設計交界面,整...
ClickOnce 是什麼 - 以實務視角整理機制、更新、適合場面・不適合場面
本文以實務視角整理 ClickOnce 是什麼,從 manifest、快取、更新、簽章的構造,到適合公司內部 .NET 桌面業務應用程式的案件與不適合 machine-wide 或 service、driver 等深度 OS 整合的案件,幫助讀者判斷是否採用並掌握 depl...
Windows 什麼時候需要系統管理員權限 - UAC、保護區、設計上的分辨方式
從邊界與儲存位置的角度,整理 Windows 何時真正需要系統管理員權限:UAC、保護區、HKLM、服務、驅動、防火牆。同時說明 per-user 與 per-machine 的差異,以及把管理員處理切成獨立 EXE、服務或工作的設計取捨,幫讀者判斷該不該提權。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。