![]()
這些年,如果你還在認真考慮“要不要做一個原生 Windows 應用”,大概率會很快陷入一種說不清的困惑。
一方面,這個平臺看起來從不缺“新東西”:從 Win32、MFC,到 .NET、WPF,再到后來的 UWP、WinUI 3,技術棧不斷演進,概念也越來越現代;另一方面,真正動手做點事情時,卻總會遇到一種強烈的割裂感——新框架不完整,老能力又離不開,開發體驗在“過時”和“半成品”之間反復橫跳。
更現實的是,就連微軟自己,也在用行動投票:從 Visual Studio Code 到新版 Microsoft Outlook,再到系統里越來越多的界面,本質上都在向 Web 技術靠攏。原生開發,反而成了一種“理論上重要,但實踐中逐漸邊緣化”的選項。
這也就帶來一個耐人尋味的問題:當一個平臺連自己的核心應用都不再堅定使用“原生方案”時,開發者為什么還要堅持?
在這篇文章里,一位親自踩過坑的開發者嘗試寫一個再簡單不過的小工具,但正是在這個過程中,他把 Windows 原生開發這些年的斷層、重復和妥協,一層層掀開來看。
原文鏈接:https://domenic.me/windows-native-dev
作者 | Domenic Denicola 責編 | 蘇宓
出品 | CSDN(ID:CSDNnews)
我是一名 Windows 忠實用戶。《Beginning Visual C++ 6》是我最早接觸的編程書籍之一。這本書的關鍵在于它附帶了一個 Visual C++ 試用版,我十歲的時候就能在家里的電腦上自己裝起來。我還記得 .NET 1.0 發布時,我們正在度假,當時我一邊啃一本 C# 大全,一邊盤算著把自己寫的 Neopets 作弊程序從 MFC 重寫成 Windows Forms。甚至我大學畢業后的第一份工作,也是在一家做 .NET 的公司,只不過我當時負責的是前端開發。
這些年來,雖然我一直在關注著 Windows 開發生態系統,但職業生涯里其實從來沒真正寫過原生的 Windows 應用。(嚴格來說,Chromium 算是原生應用,但更像是一個自成體系的操作系統。)至于個人項目,Web 一直是更合適的選擇。不過,被童年記憶勾起的一點情懷驅動,我想著寫一個有趣的小型 Windows 工具,當作“退休項目”也不錯。
結果呢……我可以負責任地說,Windows 原生開發這個生態現在就是一團亂。我完全理解為什么如今幾乎沒人再寫原生 Windows 應用,大家都轉向了 Electron。
![]()
我做了個什么?
我寫的這個小工具叫 Display Blackout(https://github.com/domenic/display-blackout),主要是解決我自己的一個小需求:
我用的是三屏顯示器,在打游戲的時候,希望把左右兩塊屏幕“黑掉”。如果直接關掉顯示器,Windows 往往會“抽風”好幾秒,還會把當前所有窗口的位置打亂。但如果是 OLED 屏,只要蓋一層純黑窗口,就等于把像素全關掉,效果其實是一樣的。
需要說明的是,這并非我的原創想法。我一開始用的是一個叫 AutoHotkey 的腳本(https://github.com/Quorthon13/OLED-Sleeper/blob/eb6eb3e1432c9510899d1aedc345876245adbc72/src/OLED-Sleeper.ahk),寫這篇文章時才發現它已經發展成一個完整的 Windows 應用了。類似的工具在 Microsoft Store 上也能找到。不過,我還是想自己做一個界面更現代一點的小工具——而且本來也不是為了做產品,主要是為了學習。
從我們的角度來看,這個應用有意思的地方在于,它需要具備這些能力:
列舉當前機器上的所有顯示器及其邊界
創建無邊框、無標題欄、不會搶焦點的純黑窗口
攔截全局快捷鍵
(可選)開機自啟動
保存一些持久化設置
在系統托盤里放一個圖標,并帶有簡單菜單
先把這些需求記住,后面會用到。
![]()
看看我做的這個漂亮的界面。你肯定會同意,它比同類軟件都好。
![]()
Windows 編程簡史
一開始,Win32 API 是用 C 寫的。不幸的是,這套 API 到今天依然非常重要,包括我這個小工具也離不開它。
隨著時間推移,在此基礎上出現了一系列抽象層。.NET 之前最主要的是 MFC(一個 C++ 庫),它利用當時算比較“現代”的語言特性,比如類和模板,在原始的 C 函數之上加了一點面向對象的特性。
真正的“抽象加速列車”,是在 .NET 出現之后才啟動的。
.NET 有很多層意義,但對我們來說最關鍵的是:它引入了一門新語言 C#,以類似 Java 的方式運行在一個新的虛擬機上(JIT 字節碼)。這帶來了自動內存管理(也就是內存安全),也讓微軟的整個開發生態有了更現代的基礎。
同時,.NET 還提供了一整套新的 Windows API。UI 方面,.NET 1.0(2002 年)帶來了 Windows Forms,本質上還是對 Win32 窗口和控件 API 的一層封裝,和 MFC 很像。
到了 .NET 3.0(2006 年),微軟推出了 WPF。這時候不再只是用 C# 對象來創建控件,而是引入了一種獨立的標記語言 XAML,有點像 HTML + JavaScript 的關系。與此同時,這也是他們第一次徹底重寫控件——用 GPU 渲染,而不是簡單封裝系統自帶的 Win32 控件。當時看起來,這像是一個全新的起點,也像是未來 Windows 應用的長期基礎。
下一次大的轉折點,是 Windows 8(2012 年)發布時引入的 WinRT。它和 .NET 類似,試圖為開發 Windows 應用提供一整套新的 API。如果開發者完全遵循 WinRT 的規則,那么應用就能符合“現代應用”的標準:沙箱化(類似 Android 和 iOS),并且可以同時部署在桌面、平板和手機上。UI 仍然基于 XAML,但相比 WPF 做了不少調整,以適應跨設備的限制環境。
這個策略在 Windows 10(2015 年)里被“重做”了一次,變成了 UWP。它放松了一些沙箱限制,讓應用能覆蓋桌面 / 手機 / Xbox / HoloLens,同時能力比 WinRT 更強,但仍然達不到 WPF 那種完整 .NET 應用的自由度。與此同時,WinRT / UWP 還帶來一個問題:某些系統級功能(比如推送通知、動態磁貼、Microsoft Store 分發)只開放給這些框架。這導致像 Chrome 或 Microsoft Office 這樣的應用,不得不在舊核心外面套一層 WinRT/UWP 外殼,通過 IPC 等方式通信,架構變得很別扭。
到了 Windows 11(2021 年),微軟基本放棄了把所有人遷移到“更現代、更沙箱化平臺”的嘗試。Windows App SDK 把原本只屬于 WinRT/UWP 的那些能力開放給所有 Windows 應用——無論是標準 C++(也不再需要 C++/CLI),還是 .NET。這個 SDK 里還包含了 WinUI 3,又一套基于 XAML、完全重寫的 UI 控件庫。
所以你看懂了嗎?光是 UI 框架這條線,就已經走過了:
Win32 C APIs → MFC → WinForms → WPF → WinRT XAML → UWP XAML → WinUI 3
![]()
路線分叉
既然這是個學習項目,我一開始就決定用“最新、最官方”的技術棧,也就是基于 Windows App SDK 的 WinUI 3 應用。
但具體怎么選,又是一個三選一的問題:
C++
C#/XAML + “framework-dependent deployment”(依賴系統運行時)
C#/XAML + .NET AOT
這是個很痛苦的選擇。
用 C++ 可以做出很輕量的應用,運行時只依賴 Windows App SDK,自帶和 Win32 API 的無縫互操作。但在 2026 年,用一個內存不安全的語言從零開始寫新項目,多少有點“逆時代”。
理想情況是:直接用系統自帶的 .NET,只分發 C# 字節碼,就像 Web 應用共享瀏覽器一樣。這就是所謂的 “framework-dependent deployment”。但問題在于——我完全無法理解的一個決定是:即便是最新的 Windows 11,也只預裝了 .NET 4.8.1,而當前版本已經是 .NET 10。結果就是,只要有一個應用需要新版本 .NET,系統就會彈窗提示用戶去下載運行時。這體驗顯然很糟糕。
于是只剩下 .NET AOT 這一條路:把整個 .NET 運行時——包括虛擬機、垃圾回收器、標準庫——全部編譯進一個可執行文件。雖然編譯器會盡量裁剪沒用的代碼,但最后一個“只是把屏幕變黑”的小工具,體積也有 9MB。
(“那 Rust 呢?”你可能會問。微軟周邊社區曾嘗試維護 Windows App SDK 的 Rust 綁定,但后來放棄了。)
此外,分發方式也一樣讓人頭疼。雖然 Windows 支持傳統的 setup.exe 安裝器(無論手寫還是第三方工具生成),但微軟推薦的“現代方案”是 MSIX——一個帶容器化安裝/卸載能力的包格式。
問題是,MSIX 非常依賴代碼簽名證書,而這個東西對非美國開發者來說,每年大概要 200–300 美元。沒有簽名的話,側載體驗極其糟糕:需要在管理員 PowerShell 里輸入一長串晦澀命令。
你可能會想,那直接上 Microsoft Store 不就好了?不好意思,我試了——被拒了,理由是“沒有提供獨特且持久的價值”。
最讓人難受的是,這一切其實都不是技術上做不到,而是完全可以更簡單:
.NET 本可以通過 Windows Update 分發,讓系統始終保持最新版本,這樣 framework-dependent deployment 就能成立
至少也可以提供一個 MSIX 版的 .NET,讓其他 MSIX 應用聲明依賴
未簽名 MSIX 本可以像 EXE 一樣使用基于用戶反饋的信譽系統
Windows 的代碼簽名證書,本可以像 Apple 生態那樣只要 100 美元/年,而不是 200+
但現實是——就像現在的 Windows 開發體驗一樣,這些東西都只做到一半,處處透著一種“差點意思”。
![]()
被“遺落”的能力
事實證明,每隔幾年就把操作系統和 UI API 重造一遍,是一件非常耗費精力的事情。再加上中途不斷嘗試做沙箱化、限制那些“過于強大”的能力,結果就是:每一層新框架都會留下缺口——一些在舊框架里能做的事情,在新框架里反而做不了了。
這其實不是什么新問題。早在 MFC 時代,你就經常不得不回退去直接調用 Win32 API;而 .NET 從 1.0 開始就有 P/Invoke 這種“逃生通道”。所以,從某種角度看,既然微軟現在也不再強制你必須只用最新框架才能獲得新能力,那么偶爾往下層調用也不算世界末日。
但問題在于,這很讓人挫敗:如果一半的代碼都只是用來做 interop、去調用那些老舊的 API,那用微軟最新最好的技術還有什么意義?如果最后還是要去封裝一堆 C API,那用 C# 編程又有什么意義?
讓我們重新審視一下我的應用程序需要完成的任務列表,對照一下 Windows App SDK 實際能做什么:
枚舉顯示器及其邊界:可以做到,但你得用 for 循環,不能用 foreach。而如果想監聽顯示器變化,就必須用 P/Invoke,因為現代 API 根本不好用。
創建無邊框、無標題欄、不會搶焦點的黑色窗口:大部分可以實現,但“不會搶焦點”這一點,還是得靠 P/Invoke。
攔截全局快捷鍵:不行,必須 P/Invoke。
開機自啟動(可選):這個可以,而且還提供了一個和系統設置集成、默認關閉的現代 API,算是做得不錯。
持久化存儲設置:可以做到。
顯示帶有少量菜單項的托盤圖標:完全沒有實現。托盤圖標本身要靠 P/Invoke;更麻煩的是,托盤菜單并沒有統一標準——你選不同的第三方封裝庫,最后出來的右鍵菜單風格都不一樣。
![]()
Windows IME 系統組件采用現代磨砂玻璃風格,與一些其他系統組件相匹配,但我找不到任何應用程序(包括 Microsoft 應用程序)與之匹配。
總結下來就是:看起來是“現代框架”,但很多關鍵能力不是缺失,就是半殘,最后還是繞回老 API。
但這些還只是“顯眼的問題”。甚至連一個很基礎的功能——根據內容自動調整窗口大小——也在從 WPF 走到 WinUI 3 的過程中,不知什么時候被弄丟了。
更麻煩的是,既然你經常需要回退調用 Win32 C API,那么 interop 本身也在“換代”,事情就更復雜了。
現在所謂的“現代方案”是一個叫 CsWin32 的東西,目標是降低 P/Invoke 的痛苦。但它連結構體里的字符串都沒法正確封裝。在我看來,這就是那種長期停留在 1.0 之前、資金和投入都不足、更新記錄也毫無亮點的項目——大概率再過幾年就會被放棄。
而且,CsWin32 的問題不只是實現不完整,有些甚至源于 C# 語言本身的缺陷。官方文檔里有一段讓人哭笑不得的說明:
Win32 中有些參數是 [optional, out] 或 [optional, in, out]。C# 沒有符合習慣的方式來表達這種概念,因此對于包含這類參數的方法,CsWin32 會生成兩個版本:一個包含所有 ref/out 參數,另一個則全部省略。
也就是說,C# 連 Win32 API 里一個非常基礎的參數類型都表達不了?這不過是現有兩種參數語義的組合而已。
按理說,既然微軟完全掌控 C#,那它應該是一個圍繞 Windows API 精心打磨、協同演進的語言。但現實顯然不是這樣。
實際上,不只是調用老的 Win32 API 時 C# 顯得力不從心,就連面對自身平臺需求,它也沒跟上。
2006 年 WPF 剛推出時,大力強調“雙向數據綁定”,大家很快就發現:為了讓一個類能綁定到 UI,需要寫大量樣板代碼,根本不可持續。基本上,每個屬性都要寫成 getter/setter,對 setter 做“值未變化則跳過”的判斷,還要手動觸發事件。(而在 C# 里,觸發事件本身就很啰嗦。)后來大家嘗試過各種“補丁式”的方案,比如基類、代碼生成器等等。但真正的解決辦法,其實應該是語言層面的支持——就像 JavaScript 通過 decorators 和 proxy 做到的那樣。
結果呢?
當我這次自己動手寫應用時,驚訝地發現:WPF 發布 20 年之后,這些樣板代碼幾乎沒怎么變。(唯一的改進,是 C# 允許在觸發事件時省略屬性名。)
這不禁讓人想問:這二十年來,C# 語言團隊到底在忙什么?為什么“原生可觀察類”這種需求從來沒被優先解決?
![]()
總結
說實話,我感覺微軟對原生 Windows 應用開發這件事根本就不重視。
相關的 issue 追蹤里,到處都是開發者遇到各種痛苦的 bug 和功能缺失,但微軟工程師的回應寥寥無幾。大多數的 Windows App SDK 更新日志也都是在新增機器學習 API。
而更諷刺的是,從 Visual Studio Code、Outlook,到甚至 Windows 開始菜單本身,很多微軟自家的應用,都是用 Web 技術寫的。
這或許也是為什么社區里很大一部分人選擇“另起爐灶”,轉向第三方 UI 框架,比如 Avalonia 和 Uno Platform。從它們的官網和 GitHub 倉庫來看,這些項目維護得更好,也更像是由一群真正熱愛 WPF、但希望 WinUI 能更強大的人在推動。同時,它們也擁抱跨平臺,這在不少場景下確實很重要。
但說到這里,很多人會好奇地直接問一句:那為什么不干脆用 Electron 呢?
說真的,C# 和 XAML 并沒有比 TypeScript / React / CSS 強到哪里去。就像我上文列出的那份需求列表所展示的,只要稍微超出基礎功能,你最終還是得用到 Win32 互操作。如果你使用的是像 Tauri 這樣的框架,甚至都不用打包整個 Chromium 二進制文件,其實只用系統自帶的 WebView 就夠了。然而,這個系統自帶的 WebView 每 4 周(甚至很快變成 2 周)就更新一次,而系統自帶的 .NET 卻永遠停在 4.8.1 版本。
當然,微軟也不是完全沒機會扭轉局面。
Windows App SDK 至少比當年繞進 WinRT / UWP 那一大圈要更靠譜一些。前面提到的打包和分發問題,其實也有不少“低垂的果實”可以改進。另外,他們最近也提到要提升 Windows 的整體質量,并且計劃在系統內部更多使用 WinUI 3——理論上,這可能會反過來推動 WinUI 本身的完善。
不過,我并不抱太大期待。從目前的情況看,大多數開發者也持相同的態度。
Hacker News 上的人總愛感嘆“原生應用的消亡”,但考慮到 Windows 應用平臺如今的混亂程度,我寧可每天用 Web 技術棧,再用 Electron 或 Tauri 去橋接必要的 Win32 能力。
110 萬美金懸賞!
AMD 2026 線上黑客松大賽來襲
從 MXFP4 MoE 算子爆改,到真實千倍并發下的吞吐量極限拉扯
不看資歷,只看絕對速度
挑戰DeepSeek?R1/KimiK2.5極致并發
入圍即能拿 1 萬美金
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.