Qt项目架构经验总结

  • Qt项目架构经验总结
    • (一)通用规则
    • (二)全局配置文件
    • (三)全局变量
    • (四)全局事件中转处理
    • (五)全局程序初始化
    • (六)全局通用类
  • 架构设计(Qt项目)
    • 一、分类
    • 二、架构
      • 1、业务架构
      • 2、应用架构
      • 3、技术架构
      • 4、数据架构
    • 三、MVC和三层结构
    • 四、总结
  • Qt5.9/C++项目开发架构理论
    • 数据唯一性
  • QT创建包含多个项目的工程以及各项目之间的调用
  • Qt使用.pri模块化工程
  • Qt基础之子工程pri的使用:子工程中的widget类如何在其他地方提升使用
  • Qt 模块化开发之 pro 子项目开发
  • 使用Qt编写模块化插件式应用程序
    • 一、动态链接库技术概况
    • 二、Qt中的动态链接库编程
    • 三、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的功能函数,完成测试;

  1. 编写共享库——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就是这样一个动态的共享类库。

  1. 编写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插件和调用插件的功能,而不是让主程序项目在编译时就指定具体的插件。

  1. 编写客户程序——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地狱问题的主要原因有以下几点:

  1. 版本控制不好(主要是接口的版本)

    DLL是共享的,如果某程序更新了一个共享的DLL,其它同样依赖于该DLL的程序就可能不能正常工作了!

  2. 二制兼容问题(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多模块项目的一个过程。