开发者的奇爱,或者我如何学会停止焦虑并爱上Windows
· 小村 豪 · Windows, Windows应用开发, 向后兼容性, COM, ActiveX, QA, 思想
1. 先说结论
开发 Windows 应用程序时,需要担心的事情很多。
这台电脑能不能顺利启动。 在没有管理员权限的环境下也能运行吗。 32bit / 64bit 的差异会不会导致程序崩溃。 中文路径、超长路径、网络驱动器、老旧 DLL、COM 组件、ActiveX、打印机、串口通信、防病毒软件、Windows Update。
一旦开始思考,担心就没有尽头。
但从某个时候开始,我渐渐这样想。
与其说这是缺点,不如说这是 Windows 长年背负着现实业务的证据。
Windows 不是一座干净的理想乡。 但它承载着在现场运行了数十年的软件、设备、业务流程,以及人们的使用习惯。
那种混沌,恰恰是 Windows 的有趣之处,也是身为开发者值得去面对的价值所在。
2. Windows 应用程序开发,令人担心的事很多
在 Windows 应用程序开发中,仅靠「代码是对的」是不够的。
在本地环境能运行。 在开发者的电脑上能运行。 在验证用的虚拟机上也能运行。
即便如此,也不代表在客户的电脑上一定能运行。
举例来说,会发生这样的事:
- 找不到 DLL
- 没有安装 Visual C++ 运行库
- .NET Framework 版本不匹配
- 想从 64bit 进程加载 32bit DLL 却失败
- COM 注册已损坏
- 搞混了
regsvr32.exe的 32bit 版本和 64bit 版本 - 假设以当前目录为前提的代码,在任务计划程序中出错
- 想写入
Program Files目录下却出现权限错误 - 搞错了
AppData\Roaming和AppData\Local的用途 - 遇到中文用户名或中文文件夹名就崩溃
- 超长路径无法打开文件
- 在网络驱动器上能运行,但在 UNC 路径下就不能运行
- 在高 DPI 环境下界面显示错乱
- 多台显示器之间坐标计算对不上
- 打印机驱动不同,打印结果就不同
- USB 设备、摄像头、测量仪器、串口通信在某些环境下无法识别
- 防病毒软件拦截了部分处理
- Windows Update 之后行为发生变化
这样列出来,会让人有点不知所措。
如果是 Web 应用,问题大多被局限在浏览器与服务器之间;但在 Windows 应用程序里,问题会扩散到整台电脑。 文件系统、注册表、用户配置文件、打印机、设备、网络、安全策略、安装程序、运行时环境。
与其说应用程序是运行在操作系统之上,不如说它是被放进了整个业务环境之中。
所以 Windows 应用程序开发很难。 不过,把这一切都归结为「Windows 就是不行」,我觉得不太准确。
架在上面的,其实是现实世界的复杂性。
3. Windows 是「现场的操作系统」
Windows 并不是只为最新技术而存在的操作系统。
工厂、医院、政府机关、学校、研究室、广播、检测设备、前台接待终端、报表终端、仓库、店铺、会计、薪资、库存管理。 在这些地方,至今仍有大量 Windows 电脑在运行。
那里并非只有最新的网络服务。
有 Excel 宏。 有 Access。 有老旧的 VB 应用程序。 有 .NET Framework 业务系统。 有用 C++ 编写的常驻程序。 有 COM 组件。 有 ActiveX。 有 ODBC 连接。 有打印机驱动。 有条码扫描仪。 有串口通信的测量仪器。 有厂商提供的 SDK。
而且,这些东西并不是各自独立存在的。
人员操作、打印报表、更换 USB 设备、把 CSV 放进共享文件夹、夜间批处理运行、第二天早上业务接续开始。 它们就在这样一连串的流程里运行。
Windows 不只是用来启动应用程序的底座。 它是一个把现场作业、既有资产、周边设备,乃至人们的使用习惯都一并接住的平台。
所以它有时看起来很老旧。 有时很复杂。 有时会让人疑惑「为什么这东西还留着」。
但这也说明,它没有随便丢弃那些旧东西。
4. 向后兼容性,是一种爱
如果只想让操作系统看起来美观,把旧机制砍掉会容易得多。
删掉旧的 API。 删掉旧的运行时。 删掉旧的设置界面。 把旧的应用程序当作「本来就不该能运行」来处理。
这样一来,架构或许会变得清爽。 文档或许也会变短。 开发者的心情或许也会轻松一些。
但在这背后,会有业务系统因此无法运行。 会有设备因此停下来。 会有现场因此陷入困境。
Windows 的向后兼容性,在技术上相当麻烦。
为了让 32bit 应用程序能在 64bit Windows 上运行,有 WOW64 这套机制。
在 32bit 与 64bit 之间,注册表或 DLL 看到的内容有时会不一样。
C:\Windows\System32 是 64bit 那一侧,而 C:\Windows\SysWOW64 才是 32bit 那一侧——这种初次接触会觉得混乱的结构也存在。
COM 也很麻烦。 光把 DLL 放进去并不会运行,有时还需要注册。 注册的位置是用户级还是机器级。 是 32bit COM 还是 64bit COM。 需不需要管理员权限。
.NET Framework 也是一样。
现在虽然有 .NET 8 / .NET 9 这类新版 .NET 可以选择。
但现场至今仍存在 .NET Framework 4.x 的应用程序。
那并不单纯是「旧」,而代表着这个版本已经支撑业务多年。
向后兼容性,并不是一个纯粹美好的概念。 反而是一项沉重的责任。
让旧东西持续运行,同时也意味着背负着技术债。 但同时,这也是一种不让用户的业务中断的诚实态度。
我认为,Windows 值得被爱的地方,正在这里。
5. Windows 是一个像地层一样的操作系统
Windows 里,堆积着各个时代的技术,就像地层一样。
Win32。 COM。 ActiveX。 Windows Forms。 WPF。 .NET Framework。 UWP。 WinUI。 Windows App SDK。 MSIX。 PowerShell。 Windows Terminal。 WSL。
每一项技术都有它出现的时代、擅长的领域,以及被使用过的现场。
只看新技术的话,旧技术或许会显得像是障碍。 但在 Windows 应用程序开发中,经常会遇到无法忽视旧有层次的场合。
举例来说,即使在开发 WPF 应用程序,打印时也可能碰到 GDI 或打印机驱动的问题。 即使用 C# 编写程序,若厂商 SDK 是原生 DLL,就会碰到 P/Invoke 或位数的问题。 即使正在创建新界面,也可能需要保留既有的 COM 组件或 Excel 集成。
Windows 不是一座已经整理完善的庭园。 反而更接近一座反复扩建改建的巨大城市。
有大马路。 有地下通道。 有老旧的小巷。 也有新建的高楼大厦。
容易迷路。 但在那些小巷深处,至今仍有重要的业务在运行。
开发者需要做的,不是判断「因为旧了就不需要」。 而是要看清楚,是哪一层的技术,正支撑着哪一项业务。
6. 把麻烦,转化为设计
标题里写的是「停止焦虑」。
但停止焦虑,不代表变得草率。 也不代表对问题视而不见。
停止焦虑,指的是把焦虑转化为设计。
举例来说,如果担心程序能不能启动,就把启动确认做成一个冒烟测试(smoke test)。
Start-Process .\MyApp.exe
不只是单纯确认能不能启动,也要在没有配置文件的状态、没有日志文件夹的状态、标准用户权限、未连接网络、打印机未配置、高 DPI、超长路径、中文路径等场景下都尝试一遍。
如果担心 DLL,就确认依赖关系。 如果需要 Visual C++ 运行库,就决定是要打包进安装程序,还是明确写成前置条件。 如果要用原生 DLL,就不要让 x86 / x64 / AnyCPU 之间的关系含糊不清。
如果担心 COM,就把注册流程写成操作手册,并在验证用电脑上从零重现一次。
# 64bit COM DLL 的注册示例
C:\Windows\System32\regsvr32.exe .\SomeComponent64.dll
# 32bit COM DLL 的注册示例
C:\Windows\SysWOW64\regsvr32.exe .\SomeComponent32.dll
如果担心权限,就避免设计成只有管理员权限才能运行。
把用户设置放进 AppData,整体设置放进 ProgramData。
避免设计成要把日志写进 Program Files 目录下。
如果担心路径,就把中文、空格、超长文件夹名、UNC 路径都纳入测试。
New-Item -ItemType Directory -Path "C:\测试 用\非常长的文件夹名\更深一层的文件夹" -Force
如果担心 DPI,就在 100%、125%、150%、200% 下确认。 在多台显示器、且各自缩放比例不同的情况下,也要确认界面不会崩坏。 WinForms、WPF、WinUI 各自对 DPI 的处理方式不同,所以要按每种界面技术分别确认。
如果担心内存或句柄,就使用 Application Verifier、WinDbg、ProcDump、Process Explorer 等工具。 如果担心崩溃,就保留事件日志、转储文件、应用程序日志。
Get-EventLog -LogName Application -Newest 20
或者,在较新的环境下,可以像这样查看事件日志。
Get-WinEvent -LogName Application -MaxEvents 20
如果担心在现场无法重现的问题,就提高日志的精细程度。 「发生错误」这种信息是不够的。 到底想打开哪个文件。 到底在寻找哪个设备。 到底是以哪个用户运行的。 到底想写入哪个路径。 到底哪个 DLL 加载失败。
把担心,转化成可以被观察的形式。
这在 Windows 应用程序开发中非常重要。
7. 「在客户的电脑上能运行」才是最终目标
在开发环境能运行,很重要。 CI 上测试通过,也很重要。 在干净的虚拟机上能运行,同样很重要。
但对于 Windows 应用程序来说,最后挡在前面的问题,是「在客户的电脑上能不能运行」。
那台电脑里,早就装了各种东西。 可能装着老旧的打印机驱动。 可能装着自家的安全软件。 可能已经配置了网络驱动器。 用户可能没有管理员权限。 可能因为某些原因无法立即应用 Windows Update。 可能是无法连接互联网的环境。
所以,发布方式也变得很重要。
单纯交付一个 exe 就够了吗。 要不要做成 MSI。 要不要用 ClickOnce。 要不要做成 MSIX。 能不能用 winget 发布。 要不要放上内部发布系统。 是否需要离线安装程序。 是否需要代码签名。 是否会被 SmartScreen 或防病毒软件拦截。
不只是应用程序本身,还要包括安装、更新、卸载、日志采集、恢复步骤,才能真正接近「在现场可以使用」。
在 Windows 应用程序开发中,软件交付并不是终点。 它需要在客户的环境里,今天、明天,持续启动运行。
8. 即便如此,我依然爱 Windows 的理由
Windows 有麻烦的地方。 这是事实。
DLL。 COM。 注册表。 UAC。 DPI。 打印机。 运行库。 32bit / 64bit。 网络驱动器。 超长路径。 中文环境。 安全软件。 Windows Update。
要列举麻烦的东西,可以列出无数个。
但那些麻烦,大多来自于 Windows 长年背负着现实业务的缘故。
不舍弃旧资产,也吸纳新技术,同时拥有 GUI 与 CLI,同时承接本地应用与网络应用,周边设备与业务终端,个人电脑与企业电脑。
那并不是一套优美的单一思想。
但,很强悍。
而且,身为开发者,这是值得挑战的事。
要在 Windows 上做出确实能运行的软件,并不只是调用 API 那么简单。 而是要读懂现场的限制,理解既有资产,找出容易出问题的地方,测试、观察,并在必要的地方修正。
这件事很朴实。 但,有价值。
9. 结语
在 Windows 应用程序开发中,令人担心的事情永远不会少。
但那些担心,大部分也是因为 Windows 这个操作系统,用了很长的时间,接住了现实的业务、现场、设备,以及人们的操作。
所以我不认为 Windows 只是一个老旧的操作系统。
它是一个一边与现实妥协、一边持续运行至今的,庞大、复杂,又有点笨拙的平台。
而在这个平台上做出确实能运行的软件,至今仍具有很大的价值。
停止焦虑。
但这不代表可以掉以轻心。
而是把焦虑,转化为设计、测试与观察。
就这样,我渐渐爱上了 Windows。
参考链接
- Application Verifier - Overview - Microsoft Learn
- High DPI Desktop Application Development on Windows - Microsoft Learn
- Setting the default DPI awareness for a process - Microsoft Learn
- Running 32-bit Applications - Microsoft Learn
- Maximum Path Length Limitation - Microsoft Learn
- MSIX documentation - Microsoft Learn
- Version compatibility in .NET Framework - Microsoft Learn
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
委托开发 Windows 应用程序前该梳理的事项
在委托外包开发 Windows 应用程序之前,梳理现有软件改造、设备联动、COM/ActiveX、发布与更新、维护等需要注意的要点。
Office 2024/Microsoft 365 中 ActiveX 无法运行的原因与排查步骤
梳理在 Office 2024/Microsoft 365 中 ActiveX 无法运行时,按默认禁用、32bit/64bit、COM 注册、依赖 DLL、IE 模式、Click-to-Run 日志的顺序进行排查的方法。
Windows 的进程间通信该怎么选 ── 命名管道 / TCP / gRPC / 共享内存 / COM 判断表
整理 Windows 应用程序之间应该如何选择通信方式。用判断表整理命名管道、本地 TCP、gRPC、共享内存、文件对接、COM 各自的优势与陷阱,并从实务角度说明 UI+服务分离・32bit/64bit 桥接・权限边界等常见架构,以及命名管道的实现示例。
IE 模式之后换成 WebView2 就够了吗 ── ActiveX 无法运行的限制与现实的迁移设计
本文从内部系统的角度,梳理 WebView2 的基本结构、Evergreen 与 Fixed Version 两种分发策略、用户数据文件夹的陷阱、原生与 Web 的联动方式,并在「ActiveX 无法运行」这一限制的前提下,整理出摆脱 IE 模式依赖系统的现实迁移顺序。
COM/OCX/ActiveX 开发中容易踩坑的注册与位数陷阱
本文从实务角度整理 COM、OCX、ActiveX 开发中容易踩坑的 32bit/64bit、Visual Studio 2022、regsvr32/Regasm、管理员权限、HKCR、STA/MTA 等问题。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。