这些年,如果你还在认真考虑“要不要做一个原生 Windows 应用”,大概率会很快陷入一种说不清的困惑。
一方面,这个平台看起来从不缺“新东西”:从 Win32、MFC,到 、WPF,再到后来的 UWP、WinUI 3,技术栈不断演进,概念也越来越现代;另一方面,真正动手做点事情时,却总会遇到一种强烈的割裂感——新框架不完整,老能力又离不开,开发体验在“过时”和“半成品”之间反复横跳。
更现实的是,就连微软自己,也在用行动投票:从 Visual 到新版 Microsoft Outlook,再到系统里越来越多的界面,本质上都在向 Web 技术靠拢。原生开发,反而成了一种“理论上重要,但实践中逐渐边缘化”的选项。
这也就带来一个耐人寻味的问题:当一个平台连自己的核心应用都不再坚定使用“原生方案”时,开发者为什么还要坚持?
在这篇文章里,一位亲自踩过坑的开发者尝试写一个再简单不过的小工具,但正是在这个过程中,他把 Windows 原生开发这些年的断层、重复和妥协,一层层掀开来看。
作者 | Domenic Denicola 责编 | 苏宓
出品 | (ID:news)
我是一名 Windows 忠实用户。《Beginning Visual C++ 6》是我最早接触的编程书籍之一。这本书的关键在于它附带了一个 Visual C++ 试用版,我十岁的时候就能在家里的电脑上自己装起来。我还记得 1.0 发布时,我们正在度假,当时我一边啃一本 C# 大全,一边盘算着把自己写的 Neopets 作弊程序从 MFC 重写成 Windows Forms。甚至我大学后的份工作,也是在一家做 的公司,只不过我当时负责的是前端开发。
这些年来,但职业生涯里其实从来没真正写过原生的 Windows 应用。(严格来说,Chromium 算是原生应用,但更像是一个自成体系的操作系统。)至于个人项目,Web 一直是更合适的选择。不过,被童年记忆勾起的一点情怀驱动,我想着写一个有趣的小型 Windows 工具,当作“退休项目”也不错。
结果呢……我可以负责任地说,Windows 原生开发这个生态现在就是一团乱。我完全理解为什么如今几乎没人再写原生 Windows 应用,大家都转向了 Electron。
我做了个什么?
我写的这个小工具叫 Display Blackout(
我用的是三屏显示器,在打游戏的时候,希望把左右两块屏幕“黑掉”。如果直接关掉显示器,Windows 往往会“抽风”好几秒,还会把当前所有窗口的位置打乱。但如果是 OLED 屏,只要盖一层纯黑窗口,就等于把像素全关掉,效果其实是一样的。
需要说明的是,这并非我的想法。我一开始用的是一个叫 AutoHotkey 的脚本( Windows 应用了。类似的工具在 上也能找到。不过,我还是想自己做一个界面更现代一点的小工具——而且本来也不是为了做产品,主要是为了学习。
列举当前机器上的所有显示器及其边界
拦截全快捷键
先把这些需求记住,后面会用到。
看看我做的这个漂亮的界面。你肯定会同意,它比同类软件都好。
一开始,Win32 API 是用 C 写的。不幸的是,这套 API 到今天依然非常重要,包括我这个小工具也离不开它。
随着时间推移,在此基础上出现了一系列抽象层。 之前最主要的是 MFC(一个 C++ 库),它利用当时算比较“现代”的语言特,比如类和模板,在原始的 C 函数之上加了一点面向对象的特。
真正的“抽象加速列车”,是在 出现之后才启动的。
有很多层意义,但对我们来说最关键的是:它引入了一门新语言 C#,以类似 Java 的方式运行在一个新的虚拟机上(JIT 字节码)。这带来了自动内存管理(也就是内存安全),也让微软的整个开发生态有了更现代的基础。
同时, 还提供了一整套新的 Windows API。UI 方面, 1.0(2002 年)带来了 Windows Forms,本质上还是对 Win32 窗口和控件 API 的一层封装,和 MFC 很像。
到了 3.0(2006 年),微软推出了 WPF。这时候不再只是用 C# 对象来创建控件,而是引入了一种的标记语言 XAML,有点像 HTML + JavaScript 的关系。与此同时,这也是他们次彻底重写控件——用 GPU 渲染,而不是简单封装系统自带的 Win32 控件。当时看起来,这像是一个全新的起点,也像是未来 Windows 应用的长期基础。
下一次大的转折点,是 Windows 8(2012 年)发布时引入的 WinRT。它和 类似,试图为开发 Windows 应用提供一整套新的 API。如果开发者完全遵循 WinRT 的规则,那么应用就能符合“现代应用”的标准:沙箱化(类似 Android 和 iOS),并且可以同时部署在桌面、平板和手机上。UI 仍然基于 XAML,但相比 WPF 做了不少调整,以适应跨设备的限制环境。
这个策略在 Windows 10(2015 年)里被“重做”了一次,变成了 UWP。它放松了一些沙箱限制,让应用能覆盖桌面 / 手机 / Xbox / HoloLens,同时能力比 WinRT 更强,但仍然达不到 WPF 那种完整 应用的自由度。与此同时,WinRT / UWP 还带来一个问题:某些系统级功能(比如推送通知、动态磁贴、 分发)只开放给这些框架。这导致像 Chrome 或 Microsoft Office 这样的应用,不得不在旧核心外面套一层 WinRT/UWP 外壳,通过 IPC 等方式通信,架构变得很别扭。
到了 Windows 11(2021 年),微软基本放弃了把所有人迁移到“更现代、更沙箱化平台”的尝试。 SDK 把原本只属于 WinRT/UWP 的那些能力开放给所有 Windows 应用——无论是标准 C++(也不再需要 C++/CLI),还是 。这个 SDK 里还包含了 WinUI 3,又一套基于 XAML、完全重写的 UI 控件库。
Win32 C APIs → MFC → WinForms → WPF → WinRT XAML → UWP XAML → WinUI 3
既然这是个学习项目,我一开始就决定用“最新、最官方”的技术栈,也就是基于 SDK 的 WinUI 3 应用。
但具体怎么选,又是一个三选一的问题:
C#/XAML + “framework-dependent deployment”(依赖系统运行时)
这是个很痛苦的选择。
用 C++ 可以做出很轻量的应用,运行时只依赖 SDK,自带和 Win32 API 的无缝互操作。但在 2026 年,用一个内存不安全的语言从零开始写新项目,多少有点“逆时代”。
理想情况是:直接用系统自带的 ,只分发 C# 字节码,就像 Web 应用共享浏览器一样。这就是所谓的 “framework-dependent deployment”。但问题在于——我完全无法理解的一个决定是:即便是最新的 Windows 11,也只预装了 4.8.1,而当前版本已经是 10。结果就是,只要有一个应用需要新版本 ,这体验显然很糟糕。
于是只剩下 AOT 这一条路:把整个 运行时——包括虚拟机、垃圾回收器、标准库——全部编译进一个可执行文件。虽然编译器会尽量裁剪没用的代码,但最后一个“只是把屏幕变黑”的小工具,体积也有 9MB。
此外,分发方式也一样让人头疼。虽然 Windows 支持传统的 setup.exe 安装器(无论手写还是第三方工具生成),但微软推荐的“现代方案”是 MSIX——一个带容器化安装/卸载能力的包格式。
问题是,MSIX 非常依赖代码签名,而这个东西对非美国开发者来说,每年大概要 200–300 美元。没有签名的话,侧载体验其糟糕:需要在管理员 PowerShell 里输入一长串晦涩命令。
你可能会想,那直接上 不就好了?不好意思,我试了——被拒了,理由是“没有提供且持久的价值”。
最让人难受的是,这一切其实都不是技术上做不到,而是完全可以更简单:
本可以通过 Windows Update 分发,让系统始终保持最新版本,这样 framework-dependent deployment 就能成立
至少也可以提供一个 MSIX 版的 ,让其他 MSIX 应用依赖
Windows 的代码签名,本可以像 Apple 生态那样只要 100 美元/年,而不是 200+
但现实是——就像现在的 Windows 开发体验一样,这些东西都只做到一半,处处透着一种“差点意思”。
事实证明,每隔几年就把操作系统和 UI API 重造一遍,是一件非常耗费精力的事情。再加上中途不断尝试做沙箱化、限制那些“过于强大”的能力,结果就是:每一层新框架都会留下缺口——一些在旧框架里能做的事情,在新框架里反而做不了了。
这其实不是什么新问题。早在 MFC 时代,你就经常不得不回退去直接调用 Win32 API;而 从 1.0 开始就有 P/Invoke 这种“逃生通道”。所以,从某种角度看,既然微软现在也不再强制你必须只用最新框架才能获得新能力,那么偶尔往下层调用也不算世界末日。
但问题在于,这很让人挫败:如果一半的代码都只是用来做 interop、去调用那些老旧的 API,那用微软最新的技术还有什么意义?如果最后还是要去封装一堆 C API,那用 C# 编程又有什么意义?
让我们重新审视一下我的应用程序需要完成的任务列表,对照一下 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 和功能缺失,但微软工程师的回应寥寥无几。大多数的 SDK 更新日志也都是在新增机器学习 API。
而更讽刺的是,从 Visual Windows 开始菜单本身,很多微软自家的应用,都是用 Web 技术写的。
这或许也是为什么社区里很大一部分人选择“另起炉灶”,转向第三方 UI 框架,比如 Avalonia 和 Uno Platform。从它们的官网和 GitHub 仓库来看,这些项目维护得更好,也更像是由一群真正热爱 WPF、但希望 WinUI 能更强大的人在推动。同时,它们也拥抱跨平台,这在不少场景下确实很重要。
但说到这里,很多人会好奇地直接问一句:那为什么不干脆用 Electron 呢?
说真的,C# 和 XAML 并没有比 TypeScript / React / CSS 强到哪里去。就像我上文列出的那份需求列表所展示的,只要稍微超出基础功能,你最终还是得用到 Win32 互操作。如果你使用的是像 Tauri 这样的框架,甚至都不用打包整个 Chromium 二进制文件,其实只用系统自带的 WebView 就够了。然而,这个系统自带的 WebView 每 4 周(甚至很快变成 2 周)就更新一次,而系统自带的 却永远停在 4.8.1 版本。
当然,微软也不是完全没机会扭转面。
SDK 至少比当年绕进 WinRT / UWP 那一大圈要更靠谱一些。前面提到的打包和分发问题,其实也有不少“低垂的果实”可以改进。另外,他们最近也提到要提升 Windows 的整体质量,并且计划在系统内部更多使用 WinUI 3——理论上,这可能会反过来推动 WinUI 本身的完善。
不过,我并不抱太大期待。从目前的情况看,大多数开发者也持相同的态度。
上的人总爱感叹“原生应用的消亡”,但考虑到 Windows 应用平台如今的混乱程度,我宁可每天用 Web 技术栈,再用 Electron 或 Tauri 去桥接必要的 Win32 能力。
110 万美金悬赏!
AMD 2026 线上松大赛来袭
从 MXFP4 MoE 算子爆改,到真实千倍并发下的吞吐量限拉扯
挑战DeepSeek−R1/KimiK2.5并发
全部评论