Qt项目架构经验总结
Posted 宁静致远2021
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt项目架构经验总结相关的知识,希望对你有一定的参考价值。
Qt项目架构经验总结
- Qt项目架构经验总结
- 架构设计(Qt项目)
- Qt5.9/C++项目开发架构理论
- QT创建包含多个项目的工程以及各项目之间的调用
- Qt使用.pri模块化工程
- Qt基础之子工程pri的使用:子工程中的widget类如何在其他地方提升使用
- Qt 模块化开发之 pro 子项目开发
- 使用Qt编写模块化插件式应用程序
- Qt - 使用子目录项目来 配置多个子工程/子模块
在模块模编程中,为了降低程序的复杂度以及后期的维护,Qt提供了pro和pri
Qt项目架构经验总结
原文链接:https://blog.csdn.net/feiyangqingyun/article/details/113985170
(一)通用规则
除了极小的微型demo级别项目外,其余项目建议用pri分门别类不同文件夹存放代码文件,方便统一管理和查找。
同类型功能的类建议统一放在一起,如果该目录下代码文件数量过多,也建议拆分多个目录存放。
比如就3-5个界面的项目,统一搞个form.pri存放这些界面,而当项目越来越大,界面可能也需要按照功能划分,比如系统配置的窗体放在一个目录下,日志管理的窗体放在一个目录下。
很多通用功能,多个项目都会用到,可以考虑封装成pri形式的模块,俗称轮子,不断完善这些轮子,多个项目共享该模块,一旦遇到BUG修复,只需要更改一个地方就行。
项目如果还更大或者项目组人员分配不同功能,可以考虑插件形式,插件一般会用到两种,一种是普通动态库形式的插件,必须和主程序放在一起;一种是Qt机制的插件,放在指定的目录。
(二)全局配置文件
全局配置文件管理类 appconfig.h 用来读写对应项目的配置文件。
格式可以是ini、xml、json等,小项目建议ini,怎么方便怎么来,相当于将配置文件的值映射到全局变量。
配置文件如果配置项较多建议分组存储方便查找,而不是全部放在一个大分组中。
读配置文件的时候可以判断配置文件是否存在、配置项是否缺失等情况,有问题则重新生成配置文件,避免恶意删除配置文件导致程序运行异常。
读配置文件的时候可以填入默认值(qt配置文件类QSettings的value方法的第二个参数,set.value(“Hardware”, App::Hardware)),避免初始时候读取不到节点而导致配置项值不符合预期值类型。
读配置文件完成后可以重新判断配置项的值是否符合要求,对值进行过滤和矫正,防止人为打开配置文件修改后填入了异常的值,比如定时器的间隔为0,要重新纠正设定为合法的值。
带中文的初始值用QString::fromUtf8包起来,比如QString::fromUtf8(“管理员”)。
带中文的配置项要设置配置文件编码为 utf-8,set.setIniCodec(“utf-8”)。
(三)全局变量
全局变量管理类 appdata.h 用来设置项目中用到的所有全局变量。
比如当前用户/系统是否锁定等,这样可以在任意的编码位置使用该变量进行判断处理。
可以将UI界面中的导航栏宽高、按钮大小、图标大小等变量放在这,系统启动后判断分辨率等来设定不同的值。
(四)全局事件中转处理
全局事件中转处理类 appevent.h 用来中转系统中各种跨多个UI以及多个类的事件。
此类必须是全局单例类,便于全局统一使用。
比如类a的父类是b,类b的父类是c,现在有个信号要发给类d,在没有事件中转处理的情况下的做法是将a信号发给b,b再发给c,c再发给d,如果父类嵌套层级越多越复杂,代码越难管理。
将类a的信号发给appevent类,然后类d直接关联appevent类进行处理就行。
项目越大,会越发现事件中转处理的必要性,代码清晰,管理方便。
(五)全局程序初始化
全局程序初始化类 appinit.h 用来做一些程序启动后的初始化处理。
读取配置文件。
设置全局字体。
设置全局样式表,建议先读取通用的样式表,然后将额外的样式表内容加到后面一起设置。
设置项目编码。
设置翻译文件,可以加载多个,包括qt内置的qt_zh_CN.qm,用户自己的翻译文件等。
初始化随机数种子。
新建项目中需要的目录,防止没有目录无法保存文件到目录。
初始化数据库,包括打开数据库,载入基础数据比如用户表、设备表等。
启动日志输出类用来启动日志服务。
启动运行时间记录类用来记录每次软件运行开始时间和结束时间。
关联全局事件过滤器处理自定义无边框UI拖动、全局按键处理等。
(六)全局通用类
调试日志输出类 savelog.h 用来启动日志服务,可以将日志输出到文件或者网络打印输出。
运行时间记录类 saveruntime.h 用来记录每次软件运行开始时间和结束时间。
图形字体类 iconfont.h 用来设置图形字体图标。
未完待续持续更新…
架构设计(Qt项目)
原文链接:https://blog.csdn.net/pzs0221/article/details/122138948
一、分类
1、可复用模块用pri分门别类不同文件夹存放代码文件。
2、同类型的代码放在一个文件夹中,如界面类、通信类、管理类、配置类等。
3、项目大时用插件组织,两种:一种是普通动态库形式的插件,必须和主程序放在一起;一种是Qt机制的插件,放在指定的目录。
二、架构
架构可细分为业务架构、应用架构、技术架构。业务架构是战略,应用架构是战术,技术架构是装备。
架构设计是从业务需求到系统实现的一个转换,是对需求进一步深入分析的过程,用于确定系统中实体与实体的关系,以及实体的形式与功能。架构可根据从业务需求到系统实现的不同需要分为:业务架构、应用架构、数据架构、技术架构。
1、业务架构
(1)业务架构的设计原则
①将业务平台化。◎ 业务平台化,相互独立,例如交易平台、物流平台、支付平台、广告平台等。◎ 基础业务下沉,可复用,例如用户、商品、类目、促销、时效等。
②将核心业务和非核心业务分离。将电商系统的核心业务和非核心业务如主交易服务和通用交易服务分离,将核心业务精简(利于稳定),并将非核心业务多样化。
③隔离不同类型的业务。◎ 交易平台的作用是让买家和卖家签订交易合同,所以需要优先保证高可用,让用户能快速下单。◎ 履约业务对可用性没有太高要求,但要优先保证一致性。◎ 秒杀业务对高并发要求很高,应该和常规业务分离。
④区分主流程和辅助流程。要清楚哪些是电商系统的主流程,在运行时优先保证主流程的顺利完成;对辅助流程可以采用后台异步的方式,避免辅助流程的失败影响主流程的失败回流。
2、应用架构
(1)应用架构的设计原则:
①稳定。◎ 一切以稳定为中心。◎ 架构尽可能简单、清晰,追求小而美,不要大而全。◎ 不过度设计。
②解耦。◎ 将稳定部分与易变部分分离。◎ 将核心业务与非核心业务分离。◎ 将电商主流程和辅助流程分离。◎ 将应用与数据分离。◎ 将服务和实现细节分离。
③抽象。◎ 应用抽象化:应用只依赖服务抽象,不依赖服务实现的细节和位置。◎ 数据库抽象化:应用只依赖逻辑数据库,不需要关心物理库的位置和分片。◎ 服务抽象化:应用虚拟化部署,不需要关心实体机的配置,动态调配资源。
④松耦合。◎ 跨域调用异步化:在不同的业务域之间尽量异步解耦。◎ 非核心业务尽量异步化:在核心业务和非核心业务之间尽量异步化。◎ 在必须同步调用时,需要设置超时时间和任务队列的长度。
⑤容错设计。◎ 服务自治:服务能彼此独立修改、部署、发布和管理,避免引发连锁反应。◎ 集群容错:应用系统集群部署,避免单点服务。◎ 多机房容灾:多机房部署、多活。
(2)类型:单体式、分布式、SOA架构
①单体式应用
系统只有一个应用、打包成一个应用;部署在一台机器;在一个DB里存储数据。
单体式应用采用分层架构,一般为表示层、业务层、数据访问层、DB层,表示层负责用户体验,业务层负责业务逻辑,数据访问层负责DB层的数据存取。
优点:开发、编译、调试一站式、一个应用程序包含所有功能点,容易测试和部署
缺点:系统逐渐庞大时,代码复杂度高,难以维护,应用扩展水平低,业务和模块职责区分不清晰。
②分布式架构
分布式应用架构中,相互独立,代码独立开发,独立部署,通过API接口互相通信。通讯协议一般使用HTTP,数据格式是JSON,应用集成方式比较简化。
优点: 应用内部高内聚,独立开发、测试和部署,应用之间松耦合,业务边界清晰,业务依赖明确,支持大项目并行开发。
缺点: API接口需求变化,应用就需要重新部署,通信可靠性和数据的封装性相对于进程内调用比较差。
③SOA架构
SOA也是分布式应用架构一种, SOA架构提供配套的服务治理,包括服务注册、服务路由、服务授权、服务降级、服务监控等等,
SOA架构既体现业务的分,又体现业务的合,更多地从业务整体上考虑系统拆分
优点:以服务层为主,聚焦核心业务,同时以提供整个系统共享,服务作为独立的应用,独立部署,接口清晰,很容易做自动化测试和部署, 服务是无状态的,很容易做水平扩展;通过容器虚拟化技术,实现故障隔离和资源高效利用。
缺点:系统依赖复杂,给开发/测试/部署带来不便,分布式数据一致性和分布式事务支持困难,一般通过最终一致性简化解决
3、技术架构
技术架构就是对在业务架构中提出的功能(或服务)进行技术方案的实现,包括软件系统实现、操作系统选择和运行时设计。
(1)设计原则:
① 无状态。即尽量不要把状态数据保存在本机上。
② 可复用。复用粒度是有业务逻辑的抽象服务,不是服务的实现细节。 服务引用只依赖服务抽象。
③ 松耦合。跨业务域调用,尽可能异步解耦。在同步调用时设置超时时间和队列大小。 将相对稳定的基础服务与易变流程服务分离。
④ 可治理。服务可降级、可限流、可开关、可监控、白名单机制、制定服务契约。
⑤ 基础服务。◎基础服务下沉、可复用,例如时效、库存和价格计算。◎ 基础服务自治、相对独立。◎ 对基础服务的实现要精简,并可水平扩展。◎ 对基础服务的实现要进行物理隔离,包括基础服务相关的数据。
4、数据架构
数据架构是对存储数据(资源)的架构,其设计原则和应用架构
设计大同小异,在设计时需要考虑系统的业务场景,需要根据不同的业务场景对数据进行异构设计、数据库读写分离、分布式数据存储策略等。
数据架构包括两部分内容:静态部分的内容和动态部分的内容。静态部分的内容的重点是数据元模型、数据模型,包括主数据、共享动态数据和所有业务相关的业务对象数据的分析和建模;动态部分的内容的重点则是对数据全生命周期的管控和治理。因此,不能单纯地将数据架构理解为纯静态的数据模型。业务架构中数据模型的分析重点是主数据和核心业务对象,应用架构中数据模型的分析重点则进一步转换为逻辑模型和物理模型,直到最终的数据存储和分布。
数据分两个层面的生命周期:单业务和跨业务。单业务对象数据的全生命周期,它往往和流程建模中的单个工作流或审批流相关;跨多个业务域对象数据的全生命周期,它体现的是多个业务对象数据之间的转换和映射,往往和端到端的业务流程 BPM 相关。这里要注意,数据虽然是静态层面的内容,但是数据的生命周期或端到端的数据映射往往间接反映了流程,这是很重要的内容。
将数据集成分析分解为两个层面的内容:业务层面的分析,以及应用和 IT 实现层面的分析。前者的重点是理清业务流程或业务域之间的业务对象集成和交互,而后者的重点是如何更好地共享数据或如何通过类似的 BI 工具或大数据平台实现数据的集成和交互。
数据架构的设计原则:
① 统一数据视图。即保证数据的及时性、一致性、准确性和完整性。
② 数据和应用分离。◎ 应用系统只依赖逻辑数据库。◎ 应用系统不直接访问其他应用的数据库,只能通过接口访问。
③ 数据异构。即在源数据和目标数据内容相同时做索引异构,在商品库不同维度的内容不同时(如订单数据中的买家库和卖家库)做数据库异构。
④ 数据库读写分离。◎ 将访问量大的数据库做读写分离,例如订单库。◎ 将数据量大的数据库做分库分表。◎ 将不同业务域的数据库做分区隔离。◎ 对重要的数据配置备库。
三、MVC和三层结构
三层架构和MVC目的一样的:分层,解耦!
四、总结
1、程序架构:
(1)UI模块:负责处理来自业务逻辑层或者其它模块的数据展示,把用户操作的发送给业务逻辑模块。
(2)通信模块:TCP、UDP、mqtt、串口等,采用单例模式负责外部通信。
(3)数据库模块:读取和保存数据。
(4)业务逻辑模块:处理通信模块的返回数据,并把结果通知UI模块。
(5)中间层:关联通信模块和业务逻辑模块。
(6) 独立模块(初始化配置模块、守护进程、更新模块、日志收集模块…)
Qt5.9/C++项目开发架构理论
https://blog.csdn.net/naibozhuan3744/article/details/82383683
最近博主单独负责一个比较大的项目,发现以前那种所有UI界面和功能逻辑全部写在一起的用法很混乱,不利于团队开发和产品迭代。于是,博主最终开始接触架构了,开始知道UI界面和业务逻辑需要尽可能的分离。
判断一个结构的解耦程度,一个简单的办法是离开了UI界面,业务逻辑是否可以正常调用和运行,如果可以,说明这个架构是比较成功的。同时,对UI界面和业务逻辑功能的每个模块,是否能够被替换,而不影响整个项目的功能,这点也是判断架构解耦性的一个指标。
博主的项目主要是用C/C++语言进行开发,用的界面库是Qt5.9.4,下面是博主对C++架构的初步认知和理解。
1.1架构有很多,但是用在C/C++后端集成管理项目上的架构,常用的是MVC,这也是主流的架构。所以博主重点学习和参考的也是MVC架构。下面是MVC架构的知识点讲解。
https://www.cnblogs.com/9A91/p/4241027.html(MVC初级理解)
https://blog.csdn.net/zch501157081/article/details/51967549(MVC深入理解)
https://blog.csdn.net/u012521552/article/details/51771318(Qt的MVC详解,重点参考)
https://blog.csdn.net/u012521552/article/details/51771318(Qt的MVC(MVD))
1.2思考总结
可以参考Qt的MVD模式,将UI界面和业务逻辑层完全分离开来,两者的链接用信号与槽机制。然后每个业务逻辑按照功能划分,封装成一个单独的函数或者类。
UI界面层(view)——>控制器层:当用户操作UI界面时,发射一个控制器层信号;
控制器层(controller)——>模型层:控制层调用模型层功能函数,实现对应业务逻辑功能;
模型层(model)——>控制器层:模型功能层,完成业务逻辑后,再发射一个控制器层信号,声明完成了该业务逻辑功能。
控制器层(controller)——>UI界面层:控制器层接收到该支线程完成了对应的业务逻辑,开启槽函数结束该支线程,然后发射一个控制器层完成业务逻辑信号到UI界面层。UI界面层收到信号,显示对应的结果UI界面。
注意:整个过程控制器有三个关键信号,启动业务逻辑功能信号,退出该业务逻辑线程信号,完成业务逻辑信号。而model层功能函数是用支线程来执行。也就是说,所有的模型功能函数都是用支线程完成,这样可以保证UI界面的流畅度。
参考内容:
https://www.cnblogs.com/9A91/p/4241027.html(MVC初级理解)
https://blog.csdn.net/zch501157081/article/details/51967549(MVC深入理解)
https://blog.csdn.net/u012521552/article/details/51771318(Qt的MVC详解,重点参考)
https://blog.csdn.net/u012521552/article/details/51771318(Qt的MVC(MVD))
数据唯一性
我们都知道,计算机最主要的功能就是储存数据和计算数据。而计算机中的数据就是0和1,因为计算机只认识0,1。它并不能像人一样可以认识十进制数,认识很多不同的事物,但是人却不认识0和1,或者说很难读懂0和1表示的事物。这是人和计算机最大的差别之一。
然而,计算机是要为人类服务的,它必须将它所认识的0和1转化成人类所熟知的数字,字符,图表等具体表象。这些表象能够形象的,或者说更加直观的为研究人员提供线索或是某种规律。
例如,我可以通过公众号后台查看到粉丝在全国的分布情况,或是男女比例,手机型号等数据。
如果公众号关联了两份不同的数据,在全国各地的粉丝总共1000人,而男女总和是900(850+50),那从我的角度来看,我该选择相信哪条数据。所以公众号的数据分析功能也就失去了意义,数据的可信度也会大打折扣。
同理,在软件开发中,如果你界面显示了两份数据,即使你很小心的维护着两份或多份数据保持同步,无论是存在缓存,文件或数据库中,但你也无法保证数据会时刻保持同步(例如,设计好的两张表中保存了同一份儿数据,当一张表的数据被更新,而另一张表却忘记更新),而且这种情况对于初学者来说也是时有发生。
所以保持数据的唯一性是一个非常重要的话题,而MVC就是这样的一个解决方案,让数据和展示数据的界面相分离,所有数据的更改就通过控制器来实现。
数据的唯一性这一标准大家一定要深深地刻在脑海中,无论你是使用MVC架构,C++中的const,设计模式中的单例模式等,都是用来保持数据唯一的有效手段,如果你能把这一标准保持好,我想你将会受益无穷。
QT创建包含多个项目的工程以及各项目之间的调用
https://blog.csdn.net/shallysweet/article/details/118494005
Qt使用.pri模块化工程
https://blog.csdn.net/BadAyase/article/details/103767431
Qt基础之子工程pri的使用:子工程中的widget类如何在其他地方提升使用
https://blog.csdn.net/ConsiseRabbit/article/details/105320937
Qt 模块化开发之 pro 子项目开发
https://blog.csdn.net/sinat_27382047/article/details/122531169
使用Qt编写模块化插件式应用程序
https://blog.csdn.net/flyoxs/article/details/5546591
动态链接库技术使软件工程师们兽血沸腾,它使得应用系统(程序)可以以二进制模块的形式灵活地组建起来。比起源码级别的模块化,二进制级别的模块划分使得各模块更加独立,各模块可以分别编译和链接,模块的升级不会引起其它模块和主程序的重新编译,这点对于大系统的构建来说更加实用。另一方面,对于商业目的明显的企业,各模块可以独立设置访问权限,开发成员只能访问自己负责的模块,其它模块是不能也不给看到的,这样减少了整个系统泄漏技术的风险。
一、动态链接库技术概况
动态链接库技术用得很多。事实上,整个Windows就是由一个个动态链接库(DLL)构建起来的,不管是系统内核,或是系统调用的API封装,还是通用工具(如控制面板、ActiveX插件等),都是一个个动态链接库文件。动态链接库并不是微软独有的技术,它是软件工程发展到一定阶段的必然产物。在类Unix系统中,这种二进制可执行模块技术不叫动态链接库,而被称为共享对象或共享库,后缀名一般为.so(即Share Object的简写)。为简便,下文将统称这种动态链接的技术为DLL或共享库。
其实,DLL文件跟普通的可执行文件差别不大,都是可执行文件嘛,装载到进程空间后,都是一些机器指令(函数代码)、内存分配(变量)等。在Windows中,这些可执行文件被称作PE/COFF格式文件,在Linux则称为ELF文件。从CPU的角度看来,程序中的各个要素,不管是函数还是变量,它们都是一个个地址,函数是入口地址,变量是访问地址;而C++的所谓类或对象,最后也被编译器肢解成了一个个变量和函数代码(这里是形象的说法,严谨技术解说请搜索C++对象模型)。DLL的装载(指导入进程空间,然后执行)方式比可执行文件的装载稍微复杂,因为它把模块链接过程推迟到了运行时。在动态链接库的装载过程中,首要任务就是解决地址重定向问题。我们知道,DLL装载到进程空间的位置(基址)是不确定的(动态装载嘛),即使DLL内部使用的函数调用和全局变量引用,在装载时都要重新计算其地址。Windows采用基址重定向(Rebasing)技术解决这一问题,而Linux采用地址无关代码(PIC,通过GOT和PLT表实现)技术。这两种技术各有优缺点。
二、Qt中的动态链接库编程
使用C++面向对象的类编写DLL是要注意很多细节的,主要是二进制(ABI)兼容问题。COM是一个很成功的例子,只要符合COM的规范,我们就能编写出很好的DLL来,然而COM是微软私生的,要想跨平台,我们还得另找它路。
Qt的跨平台特性同样令人(至少是我)兽血沸腾。如果你认为QT仅仅是一个跨平台界面库,那就小看它了。我要说的是,它不但是一个通用的跨平台的面向对象的应用程序接口库(包括GUI、数据库、网络、多线程、XML、数据容器和算法等,常用的编辑资源都有封装,就是说,这些都可以跨平台,而不仅仅是界面),更是一种C++语言的扩展,一种编程平台和应用程序框架。信号和槽的机制简化了对象之间的通信,比MFC的消息映射直观多了;界面的布局管理机制使开发人员可以很轻松地编出优雅的窗体;界面语言翻译机制也很方便实用;QObject容器管理可以看到Qt在内存管理方面的努力;扩展的foreach循环结构也向现代语言靠拢……
Qt的跨平台特性很好,对于本文的主题——动态链接库的支持也很好。QT对各种平台的动态链接库编程技术都有包装,QT把这种技术统一命名为共享库(Shared Libraries)。通过使用Qt包装过的类和宏,可以编写跨平台的共享库和插件——当然,这只是源代码级别的跨平台,你不要指望用MSVC编译出来的DLL,能集成到ARM平台的Linux程序上面——这是一个很美很美的理想哦。
QT使用以下两个宏来实现符号(函数或全局变量/对象)的导出和导入(跨平台不能用def文件了):
QT使用 QLibrary 类实现共享库的动态加载,即在运行时决定加载那个DLL程序,插件机制使用。
三、QT共享库和插件范例
本节通过例子,实现一个共享库和一个插件。在Windows平台上开发,使用VS2005编译,QT库版本为4.6.2。
本例了将编写以下三类项目:
Bil 项目:共享库项目,输出Bil.dll和Bil.lib,基础接口类库,定义一个公共的接口IAnimal(抽象类),供客户项目和插件项目使用;
Plugin 类项目:插件类项目,现编写BilDog和BilPanda两插件项目,实现IAnimal的功能,供客户项目加载和测试。两项目输出BilDog.dll和BilPanda.dll;
Test 项目:客户应用程序项目,输出Test.exe,界面中可以选择要加载的Animal插件,然后调用Animal的功能函数,完成测试;
- 编写共享库——Bil 项目的实现
该项目定义一个抽象的 IAnimal 类作为导出接口,供客户项目和插件项目使用。项目类型为共享库,将生成Bil.lib和Bil.dll两个文件,Bil.lib供Plugin项目和Test 项目引用,而Bil.dll将给Test.exe运行时动态加载。
新建一个头文件Bil.h,输入如下代码:
你现在可能不知道BIL_SHARE宏有何用处。没关系,请继续看下面的IAnimal接口定义代码:
现在知道BIL_SHARE宏的妙用了吧。BIL_SHARE宏会根据项目编译选项BIL_LIB有没有定义,自动声明IAnimal是导出类,还是导入类。所以,使用BIL_SHARE宏,我们只需要向IAnimal插件的开发者提供同一份IAnimal定义文件(IAnimal.h)即可。
当然,我们得先在Bil项目的编译选项中定义BIL_LIB宏,使得在Bil项目内,BIL_SHARE就是导出符号的声明。插件项目就不要定义BIL_LIB了,因为在Animal插件项目中,IAnimal是导入符号。
编译选项如何定义宏?如果使用Visual Studio工程文件,依次展开:项目属性->配置属性->C/C+±>预处理器,在预处理器定义中添加宏BIL_LIB即可;如果是QT工程文件,请在QT工程文件Bil.pro中加入如下定义:
在IAnimal接口中,我们定义了三个纯虚函数Eat()、Run()和Sleep(),表示吃、跑和睡眠的动作,这是抽象的,因为不同的动物有不同的吃相和睡眠姿态,而世间的动物何止千千万——无所谓,让这些具体动物的不同表现交给IAnimal插件的编写者发挥吧——这就是接口的魅力,加上插件的思想,整个应用程序就变成开放的,可扩展的了!
继续编写IAnimal类的实现文件IAnimal.cpp:
虽然只实现了构造和析构函数,并且什么工作也不做,但这是必要的,我们暂时不要使用内联的构造和析构函数,否则在插件项目实现IAnimal时可能会出现链接错误。
好了,我们开始编译吧,生成整个Bil项目。最终我们得到两个输出文件:Bil.lib 和 Bil.dll。
我们向Animal插件开发者提供:
两个头文件:Bil.h 和 IAnimal.h
两个库文件:Bil.lib 和 Bil.dll
下面的插件类项目和客户项目就是依赖这些文件实现的,也许你更愿意把Bil看作是一个通用的DLL类库,就像QT或MFC一样——事实上也是如此,Bil就是这样一个动态的共享类库。
- 编写Animal插件——BilDog和BilPanda项目的实现
现在,让我们来实现两个小插件。BilDog插件很简单,只是汇报下“我是Dog,我正在啃骨头”;BilPanda也是如此——这里仅仅是测试而已,实现的项目中,你可以尽情的发挥——没错,是在遵循IAnimal接口的前提下。
创建BilDog项目,把Bil项目输出的Bil.h、IAnimal.h和Bil.lib加入到工程。
创建Dog类的头文件Dog.h:
创建Dog类的实现文件Dog.cpp:
调用QT的QMessageBox::information()函数弹出一个信息提示框。
还有一个非常重要的工作,我们得提供一个能够创建(释放)Animal具体对象(这里是Dog)的接口,并且把这些函数导出,让主程序(Test.exe)能够解析这个接口函数,动态创建Animal对象,并访问其功能。
新建BilDog.h文件,输入下面的代码:
这两个函数的工作很简单,直接创建和释放对象即可。
下面是BilDog.cpp的代码:
至此,一个Animal插件总算完成了。编译,生成BilDog项目,输出BilDog.dll插件文件,以供主程序Test.exe动态调用。
BilPanda项目和BilDog项目类似,在这里就不把代码贴出来了。以后开发Animal插件(即使是第三方)的过程都是如此。
我们不打算输出该项目的.lib文件和那些头文件,因为我们打算让主程序在运行时刻根据需要装载dll插件和调用插件的功能,而不是让主程序项目在编译时就指定具体的插件。
- 编写客户程序——Test项目的实现
Test项目是一个测试程序项目,但它的角色是主程序,是能使用Animal插件的客户程序。
同样,这个项目用到了Bil共享库,所以得先把Bil项目的几个输出文件导入到Test项目。
我们假设Test主程序是一个对话框,上面有一个编辑框和一个“加载并调用”按钮,终端用户在编辑框中输入Animal插件的文件名(比如BilDog,后缀名可省略,Qt会根据平台判断该查找.dll还是.so),点击“加载并调用”进行共享库的加载,并调用动态创建的IAnimal对象的Eat()函数(当然你可以调用Run()函数或Sleep(),这里仅仅是一个示例)。
下面的函数将被“加载并调用”按钮的触发事件调用:
生成Test项目,输出Test.exe。我们把Test.exe、Bil.dll、BilDog.dll、BilPanda.dll放在同一目录,双击运行Test.exe,赶快试下效果吧!注意BilDog.dll或BilPanda.dll依赖于基础接口库Bil.dll,如果系统找不到Bil.dll,将不能加载BilDog.dll或BilPanda.dll,所以请把它们放在同一目录。
四、一些遗憾
DLL的愿望是美好的,只要接口一致,用户可以任意更换模块。但如果不注意细节,很容易陷入它的泥潭中,这就是传说中的DLL Hell(DLL地狱)!
引起DLL地狱问题的主要原因有以下几点:
-
版本控制不好(主要是接口的版本)
DLL是共享的,如果某程序更新了一个共享的DLL,其它同样依赖于该DLL的程序就可能不能正常工作了!
-
二制兼容问题(ABI)
即使同一平台,不同编译器(甚至同一编译器的不同版本)编出来的共享库和程序也可能不能协同工作。
二制兼容问题对于C++来说尤其严重。C++的标准是源代码级别的,标准中并没有对如何实现C++作出统一的规定,所以不同的编译器,对标准C++采用不同的实现方式。这些差异主要有:对象在内存中的分配(C++)、构造和析构函数的实现(C++)、重载和模板的实现(C++)、虚函数表结构(C++)、多重继承和虚基类的实现(C++)、函数调用约定(C)、符号修饰(C/C++)等。此外,不同的运行时库(CRT、STL等标准库)也会引起ABI兼容问题。可以说,如果你在编写基于类的共享库,如果接口(指导出类)稍有改变,新的DLL与原程序就可能不协同工作了。
关于二进制兼容问题,大家可以参考KDE官网上的一篇文章《Policies/Binary Compatibility Issues With C++ 》
不过这些都不是大问题,毕竟我们不是编写像Qt一样的通用库。我们引入DLL划分应用程序的模块,目的是减小系统开发和后期升级维护的难度,同时方便项目的管理。如果用户想自己编写插件模块,就得使用我们指定的编译平台和类接口。所以我们仍能从DLL技术中得到很大的实惠。
Qt - 使用子目录项目来 配置多个子工程/子模块
https://blog.csdn.net/zhaotianyu950323/article/details/100015082
我们在构建QT项目的时候,有时候希望每个界面分开编写,或者逻辑功能和界面分开编写,来解耦合,让我们的项目更多模块能够同时并行,而不是只有项目来完成所有的功能。咱们平时在写小项目的时候,直接使用一个QWidget Application就可以用来实现全部功能。
那么我们现在需要把某些功能编译成一个库,然后在主程序中对相关的库进行调用,我们需要用什么方式来实现呢?
就一起来思考一下这个问题吧,这篇文章也是记录我学习如何构建Qt多模块项目的一个过程。
关于Qt数据库相关开发的一些经验总结
一、前言
近期花了两个多月时间,将数据库相关的代码重新封装成了各种轮子(这条路必须打通,打通以后,相关项目只需要引入这个组件pri即可),测试了从Qt4.7到Qt6.1的各种版本,测试了odbc、sqlite、mysql、postgresql、sqlserver、oracle、人大金仓等数据库,测试了本地连接、远程连接、阿里云连接等,测试了windows、linux、mac等系统,将所有项目数据库相关的代码全部更新了一遍。能够兼容这么多Qt版本和数据库插件以及测试验证系统,估计全网也没几个人,全国11W Qter开发者中应该也是最多不超过10人。
二、数据库开发经验总结
- 在数据库相关的应用中,如果仅仅是单机版本,没有特别的需要(比如领导指定,或者需要远程存放数据),强烈建议使用sqlite数据库,这是本人经过无数次的对比测试和N个商业项目应用得出的结论。
- Qt天生内置了sqlite数据库,只需要发布的时候带上插件就行(可以看到插件动态库文件比其他几种都要大,那是因为直接将数据库的源码都编译进去了,而其他只编译了中间通信交互的插件源码),其他数据库要么还要带上动态库,要么还需要创建数据源;
速度上,绝对无与伦比的出类拔萃,同样的数据库结构(表结构、索引等完全一致),查询速度和批量更新速度、数据库事务等,速度都是其他几种的至少3倍以上,而且随着数据量的增大对比越发明显; - 几千万的数据量完全没问题,而且速度和性能都还可以,不要以讹传讹网上部分菜鸡说的不支持百万以上的数据量,本人亲测亿级别,数据量建议千万级别以下,着重注意数据库表和索引的设计;
- 其他数据库还要注意版本的区别,ODBC数据源形式还容易出错和执行失败;
- sqlite数据库也有几个重大缺点:不支持加密,不支持网络访问,不支持部分数据库高级特性,不支持海量数据(亿级别以上),但是对于绝大部分Qt项目还是足够;
- 数据库支持友好度大致是 sqlite > postgresql > mysql > odbc ;
- 以上都是在Qt环境中个人测试得出的结论,结果未必正确,作为参考即可,其他编程环境比如C#、JAVA请忽略,也许差别可能在中间通信的效率造成的;
三、数据库连接说明
- Qt5默认提供的数据库插件包括了QSQLITE、QMYSQL、QPSQL、QODBC四种,后期版本比如5.12开始把mysql也移除了(可能是因为开源协议的问题),其中驱动打印中还有个QMYSQL3是表示mysql3旧版本,现在默认一般都mysql5以上,QPSQL7表示postgres7旧版本,现在默认一般都postgres9以上。
- 根据字面意思很容易理解QSQLITE用来连接sqlite数据库,QMYSQL用来连接mysql数据库,QPSQL用来连接postgres数据库,QODBC理论上可以用来连接任何支持ODBC数据源的数据库,比如access、sqlserver、mysql、postgres、oracle等。
- Qt4默认提供的数据库插件只有QSQLITE、QODBC两种,因为QODBC理论上可以用来连接任何支持ODBC数据源的数据库,只是通过了微软的数据源中间件,效率上可能会有损耗,所以在Qt5又新增了其他几个常用数据库的插件比如QMYSQL、QPSQL,而其他数据库由于协议的要求并没有提供对应的插件需要自行编译比如oracle。
- Qt内置了sqlite数据库,可以观察到qsqlite4.dll文件大小明显比其他数据库插件大很多,理论上光一个插件应该小很多才对,毕竟sqlite属于小型数据库,所以肯定是将sqlite的源码直接编译到插件了,所以我们在使用sqlite数据库的时候无需带一个sqlite.dll,而使用mysql则需要带上libmysql.dll。
- 使用mysql、postgres等支持远程访问的数据库的时候,并不需要本地安装数据库,只需要发布程序的时候带上对应数据库的动态库即可,比如mysql对应带上libmysql.dll即可,这样程序指定数据库主机地址就可以连接上,比如阿里云的mysql、postgres等云端数据库。
- mysql、posgrest等支持远程连接的数据库,默认安装以后出于安全性考虑只支持本地访问,需要做设置才能支持远程访问,mysql需要增加用户root@%即主机设置为%,postgres需要打开安装目录下的C:\\PostgreSQL\\10\\data\\pg_hba.conf文件,增加一行 host all all 192.168.1.0/24 md5 表示支持192.168.1.1到192.168.1.255的IP访问,同时将C:\\PostgreSQL\\10\\data\\postgresql.conf改成listen_addresses = '*'表示支持所有地址,具体这个的含义可以自行搜索。
- mysql数据库通信的默认端口是3306,postgres的是5432,这些端口都可以在安装的时候或者后期更改。
- 数据库也有位数的区别,比如你连接的是64位的数据库那就需要用64位的Qt以及64位的数据库插件和对应的动态库文件,位数一定要完全一致才行,否则连不上,很多人会在这个地方摔一跤。除了位数的区别可能还要注意版本的区别,毕竟数据库一直在更新升级换代,有些版本变动比较大,未必Qt发布版本的时候对应就支持最新的数据库,所以一般建议用稍微老一点的数据库版本,比如mysql本人一直用5.6,测试到现在Qt5.13版本都支持。
- 一般的软件默认都只需要连接一个数据库,所以建议直接在程序启动以后就打开好数据库,然后其他需要用到数据库的地方就执行即可,最后程序关闭的时候关闭数据库。很多初学者每次增删改查都打开数据库执行完成操作以后然后关闭数据库,这样效率极其低下。如果需要连接多个数据库,则以数据库连接名称作为区分,Qt支持同时多个数据库连接的,数据库跨线程不安全,要加锁,所以建议在哪个线程使用到的数据库就在那个线程中打开,而不要主线程打开数据库子线程使用数据库,很可能会出问题。Qt5.10开始增加了数据库跨线程使用的安全性检查,运行时会打印提示。
- 创建数据库、创建表、创建索引、初始化数据等这些都可以通过执行sql语句来实现,强烈建议在对常用的数据量比较多的表创建表的时候要创建索引,在大量的数据查询更新操作的时候先启动数据库事务,执行完成以后提交数据库事务。
四、数据库操作流程代码
void MainWindow::testDb()
{
//打印当前Qt对应支持的数据库驱动名称
qDebug() << QSqlDatabase::drivers();
//创建数据库对象,驱动名称根据打印的填写,"QSQLITE", "QMYSQL", "QMYSQL3", "QODBC", "QODBC3", "QPSQL", "QPSQL7"
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL");
//设置数据库参数,要查看Qt文档是否支持该数据库,一般建议默认的就好不用设置
//db.setConnectOptions("MYSQL_OPT_RECONNECT=1;MYSQL_OPT_CONNECT_TIMEOUT=1;");
//设置数据库的主机地址
db.setHostName("127.0.0.1");
//设置数据库通信端口,默认值 mysql:3306 postgres:5432 sqlserver:1433
db.setPort(5433);
//设置数据库名称,默认值 mysql:mysql postgres:postgres sqlserver:master
//如果是sqlite数据库只需要设置这个参数即可,其余参数都不用设置,因为sqlite不需要主机端口和用户
//参数内容为数据库文件的路径 db.setDatabaseName("c:/test.db");
db.setDatabaseName("postgres");
//设置登录用户名称,默认值 mysql:root postgres:postgres sqlserver:sa
db.setUserName("postgres");
//设置登录用户密码
db.setPassword("admin");
//打开数据库,如果失败打印错误信息
if (!db.open()) {
qDebug() << db.lastError();
return;
}
//执行增删改查
//常规查询语句
QString sql = "select UserName,UserPwd from UserInfo";
//带条件+排序+分组的查询语句
sql = "select UserName,UserPwd from UserInfo where UserName='admin' order by UserName asc group by UserGroup";
//构建查询对象,传入sql语句查询,可以先判断执行成功与否再来取值
QSqlQuery query;
if (query.exec(sql)) {
//循环取出所有查询结果,对应结果是QVariant类型可以自行to到其他类型
while(query.next()) {
qDebug() << query.value(0).toString() << query.value(1).toString();
}
}
//添加数据,拼接字符串的形式比较通用,还有占位符的形式
sql = "insert into UserInfo(UserName,UserPwd) values('ceshi', '12345')";
//删除数据,如果不加where条件则表示删除整个表的数据
sql = "delete from UserInfo where UserName='ceshi'";
//更新数据,如果不加where条件则表示更新整个表的数据
sql = "update UserInfo set UserPwd='admin123' where UserName='ceshi'";
//可以复用上面的QSqlQuery对象,也可以重新new,复用的话需要先调用clear
query.clear();
//添加+删除+更新 数据只需要知道执行成功与否就行
if (!query.exec(sql)) {
qDebug() << "执行sql语句失败";
}
//关闭数据库,程序自动关闭的时候也会关闭,所以只是用一个数据库的情况下无需手动关闭
db.close();
}
五、数据库综合应用组件
(一)功能特点
- 同时支持多种数据库比如odbc、sqlite、mysql、postgresql、sqlserver、oracle、人大金仓等。
- 一个数据库类即可管理本地数据库通信,也支持远程数据库通信等。
- 数据库线程支持执行各种sql语句,包括单条和批量。
- 组件中的所有类打印信息、错误信息、执行结果都信号发出去。
- 集成数据库通用翻页类(负责具体处理逻辑),搭配分页导航控件(负责外观),形成超级牛逼的翻页控件。
- 集成数据库自动清理类,设定最大记录数后台自动清理早期数据。
- 集成自定义委托类,支持复选框、文本框、下拉框、日期框、微调框、进度条等。
- 同时支持Qt4-Qt6,亲测Qt4.6到Qt6.1任意版本,任意系统和编译器。
- 本组件无故障 360天7乘24小时 运行在至少上万个现场,商业级别品质保证。
- 每个类都对应完整详细的使用示例,注释详细,非常适合阅读学习。
- 可以作为独立的程序运行,比如自动清理早期数据,同步数据到云端。
- 全部线程处理,不卡界面,自动重连数据库。
- 普通测试情况,sqlite数据库,数据库发生器每秒钟插入1000条记录约0.003秒钟,同时自动清理数据类每秒钟删除1000条记录约0.13秒,不同线程互不干扰。
(二)数据库通信管理线程类
- 可设置数据库类型,支持多种数据库类型。
- 数据库类型包括但不限于odbc、sqlite、mysql、postgresql、sqlserver、oracle、人大金仓等。
- 可设置数据库连接信息包括主机地址、用户信息等。
- 具有自动重连机制,可设置是否检查连接以及检查间隔。
- 支持单条sql语句队列,一般用于查询返回数据,每次插入一条执行一条。
- 支持多条sql语句队列,一般用于远程提交数据,每次插入一条执行多条。
- 支持批量sql语句队列,一般用于批量更新数据,每次插入多条执行多条。
- 可设置队列最大数量,限定排队处理的sql语句集合。
- 通过信号发出 打印信息、错误信息、查询结果。
(三)数据库通用翻页类
- 可设置每页多少行记录,自动按照设定的值进行分页。
- 可设置要查询的表名、字段集合、条件语句、排序语句。
- 可设置第一页、上一页、下一页、末一页、翻页按钮。
- 可设置当前页、总页数、总记录数、每页记录数、查询用时标签页。
- 多线程查询总记录数,数据量巨大时候不会卡主界面。
- 建议条件字段用整型类型的主键,速度极快。
- 提供查询结果返回信号,包括当前页、总页数、总记录数、查询用时等信息。
- 可设置所有列或者某一列对齐样式例如居中或者右对齐。
- 可增加列用于标识该条记录,设定列的位置、标题、宽度。
- 提供函数直接执行第一页、上一页、下一页、末一页。
- 提供函数直接跳转到指定页。
- 根据是否第一页、末一页自动禁用对应的按钮。
- 本控件是翻页功能类,和翻页控件navpage完美搭配,形成超级牛逼的翻页控件。
(四)分页导航控件
- 可设置页码按钮的个数。
- 可设置字体大小。
- 可设置边框圆角角度、大小、颜色。
- 可设置正常状态背景颜色、文字颜色。
- 可识别悬停状态背景颜色、文字颜色。
- 可设置按下状态背景颜色、文字颜色。
- 可设置选中状态背景颜色、文字颜色。
- 可设置导航位置居中对齐、左对齐、右对齐。
- 可设置是否显示提示标签控件。
- 自动计算总页码数显示隐藏多余按钮。
- 自动计算切换页码导航。
- 和分页导航功能类无缝对接完美融合。
(五)自动清理数据线程类
- 可设置要清理的对应数据库连接名称和表名。
- 可设置条件字段。
- 可设置排序字段。
- 可设置最大保留的记录数。
- 可设置执行自动清理的间隔。
- 后期支持多个数据库和多个表。
- 建议条件字段用数字类型的主键,速度极快。
- 增加统计用字段名称设置。
- 增加自动清理文件夹,超过大小自动删除文件夹中早期文件。
(六)自定义委托全家桶
- 可设置多种委托类型,例如复选框、文本框、下拉框、日期框、微调框、进度条等。
- 可设置是否密文显示,一般用于文本框。
- 可设置是否允许编辑,一般用于下拉框。
- 可设置是否禁用,一般用来禁用某列。
- 可设置数据集合,比如下拉框数据集合。
- 提供值变化信号,比方说下拉框值改动触发。
- 可设置数据校验自动产生不同的图标。
- 支持设置校验列、校验规则、校验值、校验成功图标、校验失败图标、图标大小。
- 可设置校验数据产生不同的背景颜色和文字颜色。
- 校验规则支持 == > >= < <= != contains,非常丰富。
- 复选框自动居中而不是左侧,切换选中状态发送对应的信号。
- 可设置颜色委托,自动根据颜色值绘制背景颜色,自动设置最佳文本颜色。
- 可设置按钮委托,自动根据值生成多个按钮,按钮按下发送对应的信号。
- 当设置了委托列时自动绘制选中背景色和文字颜色。
- 可设置关键字对照表绘制关键字比如原始数据是 0-禁用 1-启用。
- 可设置复选框对应的映射选中不选中关键字。
- 根据不同的委托类型绘制,可以依葫芦画瓢自行增加自己的委托。
- 所有功能封装成1个类,核心代码不到500行,使用极其方便友好。
(七)效果图
(八)体验地址
- 体验地址:https://pan.baidu.com/s/1ZxG-oyUKe286LPMPxOrO2A 提取码:o05q 文件名:bin_dbtool.zip
- 国内站点:https://gitee.com/feiyangqingyun
- 国际站点:https://github.com/feiyangqingyun
- 个人主页:https://blog.csdn.net/feiyangqingyun
- 知乎主页:https://www.zhihu.com/people/feiyangqingyun/
以上是关于Qt项目架构经验总结的主要内容,如果未能解决你的问题,请参考以下文章