公众号:张小飞那些事儿
前言
终于开篇了呢!
先写个前言吧。
一些经历
还是简单介绍下自己
我叫张小飞,目前在金山负责WPS For Linux的研发。
先说下自己的经历吧。刚毕业的我去了方正,当时维护一个桌面端阅读器的开发。名称是Apabi Reader
是个PDF+私有格式(CEBX)的阅读器
这个东西我维护了半年。现在回想起来。这个东西还是很有意思的。
Apabi Reader
凭印象说吧。先说GUI框架吧。当时这个软件的GUI框架是自己封装的。但是给我的感觉就是一个MFC+Win32API+GTK的集合。然后所有的编码没有任何规范,当时还改过几个Bug,而且还非常恶心,所以真的是见识到了什么叫恶心(垃圾)框架。
如果有对财务清楚的话,可能比较清楚,大家在打印发票的时候,使用的是PDF格式。近几年国家大力推荐国产项目,所以OFD标准出现了。最近发现一些公司报销已经开始使用OFD格式来报销了。下一个项目,应该就是OFD了。
OFD
大家都知道PDF格式的标准
PDF(Portable Document Format的简称,意为“可携带文档格式”),是由Adobe Systems用于与应用程序、操作系统、硬件无关的方式进行文件交换所发展出的文件格式。PDF文件以PostScript语言图象模型为基础,无论在哪种打印机上都可保证精确的颜色和准确的打印效果,即PDF会忠实地再现原稿的每一个字符、颜色以及图象。
这种不随着字体变化排版的格式是版式文档。PDF本质上是一个二进制流,也就是你没有办法去编辑。只能通过各种pdf编辑器来搞。
ofd,也是版式文档。不同的是,ofd本质上是一个xml的压缩包。跟office中的docx很像。
我的第二个项目就是负责OFD内核的排版引擎还有OFD阅读器的开发。技术选型当时是使用的跨平台框架GTK
GTK
当时由于项目原因,需要一个支持npapi的浏览器插件,记得当时研发组长选择了firebreath,这个跟gtk集成的很好,所以当时的项目选择了gtk。
GTK+(GIMP Toolkit)是一套源码以LGPL许可协议分发、跨平台的图形工具包。最初是为GIMP写的,已成为一个功能强大、设计灵活的一个通用图形库,是GNU/Linux下开发图形界面的应用程序的主流开发工具之一。并且,GTK+也有Windows版本和Mac OS X版。
gtk目前最新版本是gtk4.0。附上最近的更新日志(2020.12.18)
GTK 4.0 部分更新亮点:
- 新的 widget 小部件以及对现有元素的重新设计
- 集成媒体播放支持
- 改进 GPU 加速功能(比如使用新的 Vulkan 渲染器)
- 更好的 macOS 支持
- 改进数据传输功能
- 对着色器的重大改动
- Vulkan 之外的 OpenGL 渲染改进
- 更好的 Windows 支持
GTK目前在开源界使用的比较多,比如Linux上大名鼎鼎的Desktop-Gnome桌面
但是,写界面使用C语言的方式真的是太繁琐了,可以看下gnome下开机占用内存多少,我记得至少1G起步,所以deepin祖传了gnome的资源占用。(fork的gnome)。 然而Arch KDE(基于Qt)开机内存350M。虽然开机内存并不是完全GUI库的锅,但是这也恰恰说明了Qt的先进性。
当时觉得GTK做的界面太丑了(windows).所以我就自学了Qt,来重写了个PDF阅读器。
也是靠着这个项目,进入金山也算是一个加分项了。
GTK2Qt
可能很多人可能都知道,我在刚学Qt的时候,在github上使用Qt写了个PDF的阅读器。
代码在这里
https://github.com/CryFeiFei/Reader
当时受写GTK的影响,所以写了很多个内存泄漏的代码出来,GTK的控件创建啥的是不需要指定父类的,那时候也着急.所以也写了不少内存泄漏的代码(我的黑历史),也不准备改了。
简单对比下两个库的写的方法
以下代码都在Qt的源码中
D:\Qt\5.15.2\Src\qtbase\src\plugins\platformthemes\gtk3\qgtk3dialoghelpers.cpp
//gtk
GdkWindow *gdkWindow = gtk_widget_get_window(gtkWidget);
//Qt
QWidget* widget = new QWidget(parent);
信号
GTK中也有信号的概念,可以看下GTK是怎么绑定事件的。
//gtk
g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
//qt (Qt5还支持了编译期判断与函数指针)
connect(d.data(), SIGNAL(reject()), this, SIGNAL(reject()));
可以发现,gtk的代码依旧有着浓烈的c语言风格,用gtk写消息回调是很痛苦的,它不会有任何IDE的提示这个控件有对应的消息。以及资源需要手动管理,更有浓烈的指针强转,比如上面的代码G_OBJECT(gtkWidget)
;而Qt提供了区分于c++标准库的qobject_cast
来专门用于控件转化。
这也是Qt为何从90年代到目前还是依旧有着很强的生命力。
小谈下GUI界面的资源管理
基于我自己的经验,我把这篇文章放到了这里。
众所周知,Qt是基于C++来拓展的一个GUI库。C++有的坑,在Qt中都会存在。
在Qt诞生的年代,并没有现代C++这么多特性,所以不得已,Qt造了很多自己优秀的轮子以及添加了很多自己的Features。
用很优雅的方式去解决了C++的很多痛点,针对GUI库的特性,又添加了很多有用的接口。
窗口资源管理
既然谈到C++,那最容易犯的错误之一那必然就是内存泄漏了。
最近这段时间,我也修复了WPS的不少内存泄露的问题。负责任的说,不管是多优秀的C++程序员,如果不注意,也是会写出来内存泄漏的。
Qt是一个GUI库,在程序员使用的时候,肯定会不可避免的去 对于这个问题,也有了自己的解决方案。
好在Qt在设计之初,就很好的解决了Widget控件的资源管理这个问题。
例子
一个简单的应用。
举个例子
比如我们在创建一个控件对象的时候。需要这样
Widget* pWidget = new Widget()
如果不释放,这个在堆上的空间就浪费掉了。
在使用完这个类之后,我们需要手动删除释放。
delete pWidget ;
pWidget = nullptr;
更高级一点的操作,那就是使用C++的RAII特性。这当然也是可以的。
理想很丰满,现实很骨感。事实上,我们的一个完整的GUI程序,对于所有的控件(比如窗体,按钮,画布)这种资源。如果都是这种手动管理的话,那是相当麻烦的。而且,啥时候释放,先释放谁,比如父窗口释放,其子控件资源怎么办。子控件释放,是不是还得通知父控件来删除掉其指针等等等。这样手动管理窗口资源的话
太TM浪费程序员心智了。
元对象的简单应用
所幸的是,Qt提供了元对象系统。其中的一个应用场景即:
只要继承QObject的类指定了父类,最后释放资源的时候,就不需要担心其资源问题。父类在释放资源的时候,子类会一起跟着删除。(当然你手动删除也可以,比如我想提前释放其资源。也没关系,Qt删除前会自动判断的)
Qt所有的基础控件,基本全都继承自QObject。比如QWidget
那么Qt的方法就是
QWidget* pWidget = new QWidget(parent)
其中parent就是其父控件指针。
当然你可能会疑问,如果,我就一个窗口,怎么可能会有父窗口呢。
QApplication
,即Qt的主程序,也是继承自QObject的,如果没有合适的父类,父类指定为QApplication
就可以了。
这样,我们就完美的解决了窗体控件内存泄露的问题。
本篇教程系列
本系列教程准备分为三个部分。
-
Qt的基础教程
-
Qt的高级教程
-
Qt源码剖析
我不准备把Qt的API都讲了,如果都展开,真的是篇幅太长了。我们要带着一个目的性的来学习一个新的框架。所以,我会带着目的性来给大家做几个桌面应用程序来一起学习Qt的相关API以及一些方法论。
我希望通过我的文章,能够给大家带来一些新的思路。
但是由于本人技术能力有限,难免本篇文章会有错误疏漏。希望大家给我多提一些意见。