之前寫過 把 C# 透過 Native AOT 做成原生 DLL 的方法 - 使用 UnmanagedCallersOnly 從 C/C++ 呼叫,但那篇其實順序有點顛倒了。
原本應該先擺「Native AOT 到底是什麼」比較自然。
Native AOT 的話題,一開始這幾個常常會混在一起。
- 是在講去掉 JIT 的事嗎
- 和 self-contained 或 single-file 有什麼不同
- 和 ReadyToRun 是同一類嗎
- trimming warning 大量跑出來到底是什麼狀況
- WPF 或 WinForms 能不能直接套上去
這些混在一起時,Native AOT 會看起來像「只是變快的魔法」,或者反過來像「滿是限制、不能碰的東西」。
兩邊都稍嫌粗糙。
簡單說,Native AOT 是 把 .NET 應用在 publish 時相當靜態地固化下來的發布模式。
代價是對啟動與發布層面效果明顯。
反過來,想在執行期間動態解決各種事情的程式,就比較不合拍。
這篇文章就從實務面來整理這一點。
1. Native AOT 做了什麼
平時的 .NET 會先產生 IL,再在執行期對需要的地方做 JIT。
Native AOT 則是把「執行期才產生原生碼」這一段大幅提前到 publish 時。
所以在外觀上,它是朝著「把 .NET 包得像原生應用一樣發布」的方向。
這裡重要的是 希望在 publish 時就能看到絕大多數必要的程式碼。
也就是說,
- 執行期才尋找型別
- 執行期才長出程式碼
- 執行期才讀取組件來判斷
這類寫法,無論如何都會比較不合。
若把 Native AOT 只看成「變快的功能」,在這裡就會偏掉。
本質上更像是 捨棄一些動態世界,往靜態容易固化的世界靠攏。
2. 和 ReadyToRun 差在哪裡
先把這裡分清楚會輕鬆很多。
| 觀點 | 一般 JIT | ReadyToRun | Native AOT |
|---|---|---|---|
| 執行期 JIT | 使用 | 仍會使用 | 不使用 |
| 發布產物 | 以 IL 為主 | IL + 事先產生的原生碼 | 以原生可執行檔為主 |
| 啟動 | 基準 | 容易改善 | 更容易改善 |
| 相容性 | 廣 | 相對廣 | 限制較多 |
ReadyToRun 是把 JIT 的工作稍微提前的方向。
Native AOT 則是 不以執行期 JIT 為前提 的方向。
名字看起來接近,實際手感卻差不少。
ReadyToRun 是「先讓啟動稍微輕一點」。
Native AOT 是「為了啟動、發布、執行環境的需求,設計也往靜態一點靠」。
3. 用了 Native AOT 有什麼好處
最直觀的還是啟動。
在 CLI 工具、短命行程、容器裡的小 API、會常駐但功能受限的工具上,JIT 的開銷會很顯眼。
Native AOT 把這部分先做完,起手就會比較輕。
另一個是發布層面。
在不想要求「請先在機器上安裝 .NET Runtime」的情境中,心裡會輕鬆不少。
- 只想發布一支小工具
- 希望容器映像盡量小
- 不想在執行環境上裝 JIT
這種場景下,Native AOT 用得上的位置很明確。
所以 Native AOT 適合的不是「所有應用」,而是 想減少啟動、發布、執行環境前提條件的應用。
4. 反過來哪些地方會變辛苦
這裡答案相當明確。
首先是反射與動態產生程式碼。
Assembly.LoadFileSystem.Reflection.Emit- 執行期把型別逐一列舉出來尋找的結構
- 從字串名稱查型別再實體化的機制
這些在 publish 時不容易把必要程式碼固定下來,就會成為 AOT warning 的溫床。
Native AOT 噴一堆 warning 時,與其覺得「吵死了」,倒不如讀成「這個設計在 publish 時就是看不清楚」。
特別是 RequiresDynamicCode 系列,通常值得認真看。
再來是 trimming。
Native AOT 和 trimming 綁得相當緊。
這裡麻煩的是,不只自己的程式碼,連相依函式庫的寫法都會受影響。
例如,
- 以反射為基礎的序列化器
- 以執行期掃描為前提的 DI
- 使用動態 proxy 的函式庫
這些都容易讓事情變麻煩。
換句話說,做 Native AOT 的時候,與其「執行期聰明處理」,不如 往建置期就能看清楚的形式靠攏 比較好控制。
5. Windows 桌面端要稍微謹慎一點
在 Windows 上,Native AOT 沒有 built-in COM。
再加上 WPF 和 trimming 的搭配並不好,WinForms 也相當仰賴 built-in COM marshalling。
因此,
- 直接把 WPF / WinForms 主體丟去 Native AOT
- 把 COM interop 以平常的感覺直接帶入
這些都要謹慎看。
反過來,比較適合當入口的是,
- Console
- worker
- 小型 Web API
- 職責分明的小元件
。
Native AOT 並不是「.NET 的東西都能直接套上去」。
尤其在 Windows 桌面與 COM 味濃的世界中,很多情境還是維持一般 JIT 會比較順。
6. 最小步驟其實很簡單
在專案端,先在 csproj 裡加上 PublishAot。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PublishAot>true</PublishAot>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
publish 的指令,例如 Windows x64 會是這樣。
dotnet publish -c Release -r win-x64
Linux x64 則是,
dotnet publish -c Release -r linux-x64
走到這一步,就已經相當「像原生應用的世界」了。
由於 RID 是固定的,產物比起「同一支 DLL 到哪都能跑」,更像是為該 OS / 架構量身製作的執行檔。
另外還有一個意外常中獎的地方是 JSON。
與其用平常的方式把 System.Text.Json 寫成靠反射的路徑,不如靠 source generation,比較不容易出事。
using System.Text.Json;
using System.Text.Json.Serialization;
[JsonSerializable(typeof(AppConfig))]
internal partial class AppJsonContext : JsonSerializerContext
{
}
public sealed class AppConfig
{
public string? Name { get; init; }
public int RetryCount { get; init; }
}
var config = new AppConfig
{
Name = "sample",
RetryCount = 3
};
string json = JsonSerializer.Serialize(config, AppJsonContext.Default.AppConfig);
與其說是為了 Native AOT,不如記成 改往「不要讓執行期去找型別」的寫法靠,比較不會偏掉。
7. 從哪裡開始試比較好
第一個對象,下列幾類比較容易上手。
- 啟動很重要的 CLI
- worker
- 小型 Web API
- 在原生整合中職責較窄的元件
這些比較容易減少動態機制,也比較容易看到 Native AOT 的好處。
反之,WPF / WinForms 既有的大型應用主體、以 plugin 載入為核心的結構、重度依賴 COM 的結構,做為首波對象並不太和平。
8. 總結
Native AOT 是在 publish 時把 .NET 做得相當靜態的機制。
- 對啟動有效
- 發布也會變輕鬆
- 代價是對動態機制會變嚴苛
先掌握這三點,視野會清楚很多。
Native AOT 不是「所有 .NET 應用都該套上的標配開關」。
但在啟動重要、想讓發布更輕、想降低對執行環境的前提要求時,它是相當強的選項。
反過來,在 Windows 桌面或 COM 味濃的世界中,繼續走一般 .NET 往往比較順。
能分得出這兩邊之後,Native AOT 就不再是「難搞的新功能」,而是位置清楚的工具。
9. 參考資料
- Native AOT deployment overview - .NET
- Native AOT deployment overview - .NET (日本語)
- Introduction to AOT warnings - .NET
- Prepare .NET libraries for trimming - .NET
- Known trimming incompatibilities - .NET
- How to use source generation in System.Text.Json - .NET
- ASP.NET Core support for Native AOT
- ReadyToRun deployment overview - .NET
- Building native libraries - .NET
- ComWrappers source generation - .NET
- 相關文章:把 C# 透過 Native AOT 做成原生 DLL 的方法 - 使用 UnmanagedCallersOnly 從 C/C++ 呼叫
- 相關文章:要在 C# 中使用原生 DLL,C++/CLI 包裝是有力選項的理由 - 與 P/Invoke 的比較整理
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
如何理解 Windows 的工作階段隔離 ── Session 0・RDP・多使用者同時執行
整理 Windows 應用程式開發者容易混淆的「工作階段」概念。說明服務無法顯示 UI 的 Session 0 隔離原因、RDP 連線時工作階段的行為、具名物件的工作階段隔離,以及共用 PC・RDS 環境常見的設計失誤,從實務角度解說。
Windows 的行程間通訊該怎麼選 ── 具名管道 / TCP / gRPC / 共享記憶體 / COM 判斷表
整理 Windows 應用程式之間該如何選擇溝通方式。以判斷表整理具名管道、本機 TCP、gRPC、共享記憶體、檔案協作、COM 各自的強項與陷阱,並從實務角度說明 UI+服務分離・32bit/64bit 橋接・權限邊界等典型架構,以及具名管道的實作範例。
Windows應用程式資料儲存位置怎麼選 ── SQLite / JSON / 登錄檔 / Access 判斷表
Windows桌面應用程式的資料該存在哪裡、用什麼格式儲存?本文整理AppData/ProgramData的使用區分,以及SQLite、JSON檔案、登錄檔、Access(.accdb)各自的優勢與陷阱,並附判斷表,從實務角度說明防止資料損毀與位元數問題等注意事項。
委外・委託開發 Windows 應用程式前該整理的事項
在委外・委託開發 Windows 應用程式之前,整理既有軟體改版、設備整合、COM/ActiveX、發布與更新、維護等應留意的重點。
.NET 的 Generic Host 是什麼 - 先整理 DI、設定、日誌、BackgroundService
本文整理 .NET Generic Host 的真面目,並把 DI、設定、日誌、IHostedService 與 BackgroundService 的關係,連同 Host.CreateApplicationBuilder 與 WebApplicationBuilder 的...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
32 位元 / 64 位元互通
整理 32 位元 / 64 位元互通、原生邊界與相關 Windows 設計判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
技術諮詢 & 設計審查
協助釐清設計方向、架構邊界、生命週期責任,以及既有 Windows 資產的處理方式。
常見問題
整理諮詢這個主題時常見的問題。
- Native AOT 是什麼?
- Native AOT 是把 .NET 應用在 publish 時相當靜態地固化下來的發布模式。平時的 .NET 會先產生 IL,再在執行期對需要的地方做 JIT;Native AOT 則把「執行期才產生原生碼」這一段大幅提前到 publish 時,產出以原生可執行檔為主的成品,不再使用執行期 JIT。它的本質不是單純「變快的魔法」,而是捨棄一些動態世界、往靜態容易固化的世界靠攏,對啟動與發布層面效果明顯。
- Native AOT 和 ReadyToRun 有什麼不同?
- ReadyToRun 是把 JIT 的工作稍微提前的方向,發布產物是 IL 加上事先產生的原生碼,執行期仍會使用 JIT,相容性相對廣。Native AOT 則是不以執行期 JIT 為前提的方向,發布產物以原生可執行檔為主,啟動更容易改善但限制較多。簡單說,ReadyToRun 是「先讓啟動稍微輕一點」,Native AOT 是「為了啟動、發布、執行環境的需求,設計也往靜態一點靠」。
- 什麼樣的應用適合 Native AOT?
- 適合的是想減少啟動、發布、執行環境前提條件的應用,例如啟動很重要的 CLI 工具、worker、容器裡的小型 Web API、職責分明的小元件。它在不想要求執行環境先安裝 .NET Runtime、希望容器映像盡量小的情境特別有用。反過來,重度依賴反射與動態產生程式碼的結構、以 plugin 載入為核心的結構,就比較不合拍。
- WPF 或 WinForms 可以用 Native AOT 嗎?
- 要謹慎看待。在 Windows 上 Native AOT 沒有 built-in COM,而 WPF 和 trimming 的搭配並不好,WinForms 也相當仰賴 built-in COM marshalling,所以直接把 WPF / WinForms 主體丟去 Native AOT、或以平常的感覺帶入 COM interop 都不建議。比較適合當入口的是 Console、worker、小型 Web API 這類職責分明的元件;Windows 桌面與 COM 味濃的世界,很多情境維持一般 JIT 會比較順。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。