“Windows 下的 GUI 开发,真的太难了!”(windows开发教程)
近日,开发者 Samuel Tulach 在个人博客上感叹道,“在 Windows 上编写 GUI 应用程序真是太难了!”在文中, Samuel Tulach 讨论了使用 C 编写 GUI 应用的各种困难。他详细列举了几种流行的库,但这些库通常会面临诸如依赖项管理、应用程序大小以及样式定制等问题,并没有完全契合的解决方案。
万万没想到,这个小吐槽引起了不少开发者的共鸣,甚至登上了 HN 热榜。接下来,我们将通过本文看看难点究竟在哪里?
过去几天里,来自捷克的程序员 Samuel Tulach,一直在寻找一个能够让他用 C 编写带有 GUI 程序的库。他给出的要求非常简单:
- 只需要支持 Windows;
- 允许商业用途;
- 易于设计,包括支持暗黑模式;
- 结果应该是一个单一的 .exe 文件,没有或只有极少的依赖项,大小不超过 40MB;
- 编写程序的 GUI 部分所花费的时间不应该比实际功能花费的时间多。
所以,Samuel Tulach 开始挑选一些称手的工具、框架,结果发现并没有想象中那么简单。
WinUI 3
首先,Samuel Tulach 从专为 Windows 应用程序开发而设计的现代化的用户界面(UI)框架——WinUI 3 入手。
乍一看,它是个不错的选择。它允许你使用现代的 Windows 组件,还可以自定义样式颜色。设计方面,你可以使用非常容易掌握的 XAML,或者直接使用 Visual Studio 设计器。
WinUI 3 控件库
问题:使用 WinUI 3 框架开发的应用程序在发布时不能以无包装(unpacked)的形式进行分发。这意味着无法将应用程序打包为一个单独的可执行文件(.exe)及其所需的所有依赖项,而是需要依赖于特定的分发方式,如打包为 AppX 包或者通过 Microsoft Store 进行分发。
对于 Samuel Tulach 而言,大多数情况下,当他尝试将应用程序迁移到虚拟机或不同计算机时,由于缺少一些不明依赖项而无法正常启动。更糟糕的是,你需要提供一堆处理 WinUI 功能的 .dll 文件,没有办法提供一个可移植的 .exe 文件。使用打包形式通常没有问题,但它们是以 AppX 包的形式安装的,这本身就会带来很多问题(尤其是在需要访问所有 Win32 API 的情况下)。
Win32 / MFC / 封装 Win32 的小型库
紧接着,Samuel Tulach 将目光转移到了 Win32 身上。Win32 是一种应用程序编程接口(API),它是微软 Windows 操作系统的核心组成部分。它提供了开发者与操作系统交互的一组标准接口和功能,用于创建和管理 Windows 应用程序的各个方面,包括窗口、消息处理、文件操作、网络通信等。
Samuel Tulach 表示,其需要高度的可移植性,因此使用操作系统的原生渲染是有道理的。这样的程序可以是一个单一的 .exe 文件(因为我们对 MFC 进行了静态链接),而且体积超小(只有几千字节)。
同时,Samuel Tulach 称,还可以使用别人已经编写好的更简约的库,这意味着从概念到可运行应用程序的快速实现将变得非常容易。
基本 Win32 形式
问题:对原生的 Win32 控件进行样式化处理是一项非常困难的任务。为了改变这些空间的外观和风格,Samuel Tulach 需要为每一个控件编写一个专门的自定义绘制函数,这个过程不仅需要详细了解 Win32 API 的绘制机制,还需要大量的时间和精力投入。
对此,Samuel Tulach 吐槽称,“有这个时间,我都可以直接去养家糊口了。”
此外,Windows 文件资源管理器(File Explorer)使用的一些原生 Win32 控件具有一个被称为 “隐藏” 的暗色模式。用户可以激活这个模式,但它只会对部分控件生效,并且在视觉效果上并不十分理想或完善。
QT
Qt 在 C 图形用户界面开发中被认为是一个非常优秀的选择。尽管它相对复杂,但它提供了 Qt 样式表(Qt Style Sheets),这使得开发者可以轻松地设计和定制界面的外观和样式。Qt 样式表使用一种类似于 CSS 的语言,让开发者可以用熟悉的方式来定义控件的外观、布局和交互效果。
OBS studio 使用 Qt 和自定义样式表
然而,问题在于,当使用动态链接方式时,运行 Qt 应用程序需要大量不同的 .dll 文件,这些文件总共的大小超过了 40MB。为了减少文件大小,可以选择将 Qt 静态链接到应用程序中,这样未使用的部分会被移除,从而减小应用程序的体积。
不过,Qt 使用的是 LGPL 许可证。根据 LGPL 的条款,如果选择静态链接 Qt,那么必须遵守 LGPL 的要求之一:要么开源应用程序的源代码,要么提供用于重新编译的目标文件。另外,Qt 也提供了商业许可证选项,可以以几千美元的价格购买,从而免除遵守 LGPL 条款的义务,只是这样成本又太高了。
wxWidgets
wxWidgets(https://www.wxWidgets.org/)是一个相对容易学习的库,还可以使用 wxFormBuilder。它所遵循的许可证要比 Qt 更宽松,可以静态链接成一个 3MB 的可执行文件。
wxWidgets 启用了实验性的 Windows 暗模式选项
问题:wxWidgets 是一个在 Windows 上使用原生的 Win32 组件的库。然而,与直接使用 Win32 或 MFC 相比,wxWidgets 并没有提供轻松覆盖绘制函数的选项,因此在样式定制方面甚至比直接使用 Win32 或 MFC 更为困难。
尽管 wxWidgets 支持应用 Windows 文件资源管理器的暗模式控件,但实际效果并不理想。
hikogui
hikogui(https://github.com/hikogui/hikogui)是一个相对较新的保留模式 GUI 库,它使用 Vulkan 作为后端技术。这意味着它利用 Vulkan 的强大功能来进行图形渲染和显示。hikogui 内置了暗模式的支持,开发者可以很容易地自行定制和样式化界面,以适应应用程序的需求和设计风格。
问题:要成功编译 hikogui,可能需要具备计算机科学博士学位,特别是在编译器开发方面的专业知识。在体验过程中,Samuel Tulach 尝试了超过 30 分钟去编译示例,包括尝试不同的代码分支和发布标签,但最终只得到了一个立即在某个 Vulkan 库内发生访问冲突的可执行文件,因此他决定放弃尝试。
不过,尽管 Samuel Tulach 不太喜欢大量使用 STL(标准模板库),甚至认为有时候并不是必需的,但他认为 hikogui 看起来确实有前景。
Sciter
Sciter 实际上是一个不错的 Electron 替代品,允许开发者使用 HTML/CSS 编写桌面应用程序的 GUI。
SVG 图标上抗锯齿效果不佳的示例
问题:你可能认为应用程序大小或称为一个问题,但实际上,包含所有必需的 .dll 文件的最终应用程序大小大约只有 25MB,这对 Samuel Tulach 来说完全可以接受。
Samuel Tulach 表示,如果 Sciter 能够完全开源,那将是更好的选择,因为这样开发者可以将静态链接版本用于商业用途(与 Qt 的问题相同)。同时,Sciter 的独立许可证价格相比 Qt 要低不少(目前为 310 美元),Samuel Tulach 也愿意为此支付费用。
然而,现在不选用这个框架的主要原因在于,如上图所示(看看标题栏图标),渲染效果不太好。在体验过程中,Samuel Tulach 透露其遇到了各种字体和图像的抗锯齿问题(启用了高分辨率选项,即使在预编译的 scapp.exe 中也存在这个问题)。而且,无论你做什么,窗口都会有一个相当厚(2-3 像素)的灰色边框,完全无法自定义或修改。
WinForms / WPF
如果你在一些论坛上询问关于在 Windows 上使用 C 编写 GUI 库的问题,大概率会收到一些“这不是一个好主意”等类似的反馈,甚至论坛上的专家们会直接建议你采用其他技术堆栈来编写应用程序的前端部分,然后将用 C 编写的功能作为组件或模块加载进来。
这种做法可以让你更轻松地定制和风格化界面,并显著加快开发速度。从技术角度来看,确实可以使用像 WinForms 或 WPF 这样的工具生成一个体积较小的单独可执行文件(.exe)。
详细来看,有两种方式可以实现:
- 将动态链接库(.dll 文件)打包到应用程序的资源中,然后在运行时从应用程序的资源中提取这些 .dll 文件到一个临时文件夹中。接下来,使用 P/Invoke 技术在 C#/.NET 应用程序中调用这些从 .dll 编译而来的库文件。这种做法允许将 C 编写的功能模块作为 .dll 文件嵌入到 C# 或 .NET 应用程序中,并在运行时动态加载和调用它们。
- 使用 C /CLI。
问题:.NET Framework 在 Windows 10 及更新版本中是预装的,因此从技术上来说,我们仍然可以满足无依赖项的标准。然而,如果我们选择将 C 编写的功能模块打包为 .dll 文件,我们仍然需要在运行时将这些 .dll 文件从打包的资源中提取到一个临时位置,并编写额外的 P/Invoke 代码来调用这些模块。
此外,如果使用 C /CLI 编译,生成的代码将转换为 .NET IL 代码。换句话说,生成的应用程序可以在调试工具(如 dnSpy)中打开,你可以看到 C 代码被翻译为等效的 C# 代码。这种情况下,生成的应用程序并不是纯粹的原生代码应用程序,而是包含了.NET Framework 的中间代码。
而正如文章伊始所提及的,Samuel Tulach 想要原生的代码,所以基于 WinForms 或 WPF 得到的代码并不他真正想要的。
解决方案?
以上是 Samuel Tulach 考虑过的几个方案。经过长时间尝试各种不同的库,甚至一度编写自己的 MFC 样式之后,Samuel Tulach 发现对于简单的应用程序来说,没有什么比 Dear ImGui 更合适的了。
Dear ImGui 是一个用于 C 的轻量级界面开发框架,它的主要目标是提供高效、灵活和易于使用的界面开发工具。Dear ImGui 是一个基于文本的界面开发框架,它使用简单的文本命令来创建和更新用户界面。
不过,Dear ImGui 在设计复杂的用户界面时也有一些缺点,而且它不是保留模式的用户界面,而是即时模式的用户界面,因此想要使用,必须运行像 DirectX 这样的 GPU 渲染器来渲染每秒 60 帧或更多帧的用户界面。
正如上图所示,Samuel Tulach 已经写了一个示例,如何使用内置的多视口功能来制作简单的图形用户界面应用程序。
编译后的程序大小只有500KB,非常小巧。这个程序不需要安装任何额外的依赖项,即使是将 MFC(Microsoft Foundation Classes)静态链接到程序中,也不需要安装 VC (Visual C )的运行时库。这使得部署和使用这个程序变得非常简便,用户可以直接执行这个程序而无需担心缺少运行时组件或依赖项。
对于 Samuel Tulach 这段经历,不少开发者感同身受。来自 HN 的网友 pshirshov 表示:
我也有过类似痛苦的经历。我需要一个真正跨平台(Windows、Linux/Wayland、Mac、iOS、Android)的 GUI 工具包,它要有丰富的控件库和合理的主题。事实上,唯一不错的选择是 QT,而使用 C 很难提高效率,因为它仍然缺乏基本功能,如类型解构和带穷举检查的 ADT/GADT。QT 与其他语言的绑定还不够成熟。
除了 QT 之外,还有漏洞百出的 Avalonia。对 Linux 的支持非常糟糕。
此外还有 Kotlin/Compose Multiplatform。它缺乏良好的文档和先进的控件,而且仍然充满 Bug。
我无法找到任何适用于 JS/TS 的优秀控件库,即使是付费的控件库(如 Telerik 和其他控件库)也非常混乱。因此,Electron、Capacitor、React Native——它们都认为即使是一个非常基本的应用程序也需要投入大量精力。
Flutter 给人的感觉极不成熟,尤其是全局单例模式的普及使得在使用 Flutter 时,即使是管理小型代码库也变得非常困难。
另一用户称:
在 Mac 上我遇到同样的困扰,我在 Mac 上 SwiftUI 的体验是,它仍需要大量工作。文档很差。如果你以直截了当的方式做事,性能可能会很差。支持旧版本的操作系统相当痛苦等等。
至此,你是否体验过 GUI 开发?有哪些工具推荐?
来源:
https://news.ycombinator.com/item?id=40839208
https://tulach.cc/writing-gui-apps-for-windows-is-painful/