阅读本文大概需要 6 分钟

在上一篇大概了解了关于Qt Creator 基础知识后[1],本篇先学习下框架基本结构,这样能够清晰的知道这个框架当中包含哪些文件、文件夹、工程文件,这些文件分别代表什么意思以及有什么作用

文件结构

打开下载好的源码,如下目录所示

可以看出来,文件和文件夹很多,不要被这些表面吓着,我们真正需要关心的没有几个,需要重点关注的我加粗显示了

  • bin文件夹
  • dist 文件夹
  • doc 文件夹
  • qbs 文件夹
  • scripts 文件夹
  • share 文件夹
  • src 文件夹
  • tests 文件夹
  • docs.pri
  • qtcreator.pri
  • qtcreator.pro
  • qtcreator.qbs
  • qtcreatordata.pri
  • README.md

这里我们主要要关注src文件夹,这个下面是这个框架的源码,其它的文件夹先不看

qtcreator.pri文件是项目工程中的一些通用配置,比如版本号,一些库的输出路径定义,每个插件或者子工程都会包含该配置文件,方便直接配置工程一些变量(具体怎么配置,后面会讲解到)

qtcreator.pro文件是主工程文件,要打开编译源码也是需要打开该工程文件进行加载的

PS: 涉及到 qbs 相关内容可以不用关注了,Qt Build Suite 也是一种跨平台的编译工具,目前使用较少无需关注

框架结构

下面来详细看下工程结构是如何管理的,以及整个框架原理

使用 Qt Creator 打开工程后你会发现有很多子工程项目,这个时候不要乱、不要怕,我们目前只需要关心三个部分就可以了

  • libs
  • plugins
  • app

libs部分

libs工程下面包含了常用的一些通用方法,我们目前关注三个即可

Aggregation工程

这个类提供了「打包」功能,可以将很多组件打包成一个整体,整个理解起来有点抽象,你可以理解为将多个对象封装成一个对象,这个对象对外提供了所有对象的接口属性和方法

  • Aggregation 集合内部每个组件对象都可以互相转化
  • Aggregation 集合内每个对象的生命周期被绑定在了一起,即一个在全部在,一个被删除析构那么其余的组件也就会被析构

extensionsystem工程

这个类实现了插件的管理功能,是整个框架的核心部分,所有的插件生命周期管理都在这个类里面实现

  • IPlugin 插件基类,后面所有的插件都是继承自它来实现所有功能的,有三个重点方法需要关注
    virtual bool initialize(const QStringList &arguments, QString *errorString) = 0;
virtual void extensionsInitialized() = 0;
virtual bool delayedInitialize() { return false; }

插件的初始化,外部依赖初始化,延迟初始化,这三个虚函数用来初始化每个插件各自的一些资源信息。外部依赖那个也尤为重要,比如我们某个插件同时依赖多个其它插件,那么就需要在这里处理等待其它插件加载完成才算完成

  • PluginManager 插件管理单例类,整个框架只有一份,负责框架插件的管理,随着程序退出它的声明周期才结束
  • PluginManagerPrivate 插件管理具体实现逻辑类,看名字就很清楚,典型的P-D指针关系,这样是为了把插件系统扩展的具体实现隐藏不给外部暴露,这种技巧在后面很多代码中经常会见到,也是值的我们去学习
  • PluginSpec 插件核心类,该类实现插件的所有属性
class EXTENSIONSYSTEM_EXPORT PluginSpec
{
public:
enum State { Invalid, Read, Resolved, Loaded, Initialized, Running, Stopped, Deleted}; ~PluginSpec(); // 插件名字
QString name() const;
// 插件版本
QString version() const;
// 插件兼容版本
QString compatVersion() const; // 插件提供者
QString vendor() const; // 插件版权
QString copyright() const; // 插件协议
QString license() const; // 插件描述
QString description() const; // 插件主页 URL
QString url() const; // 插件类别,用于在界面分组显示插件信息
QString category() const;
}

每个插件(每个动态库)都有一份该对象,用来记录该插件的所有属性信息,这些属性信息是通过 json配置文件读入的,这些信息被称为插件的「元信息」,后面关注插件实现会提到

utils 工程

这个工程里面封装了一些基础功能算法类,比如文件操作、数据排序操作、json交互操作、字符串操作集合等,还有一些基础封装控件实现也在这个里面

比如后面要提到的核心插件主窗口QMainWindow类,基类就在在这里

class QTCREATOR_UTILS_EXPORT AppMainWindow : public QMainWindow
{
Q_OBJECT
public:
AppMainWindow(); public slots:
void raiseWindow(); signals:
void deviceChange(); #ifdef Q_OS_WIN
protected:
virtual bool winEvent(MSG *message, long *result);
virtual bool event(QEvent *event);
#endif private:
const int m_deviceEventId;
};

这里主要是一些事件变化后通知外部处理,比如这里如果主题发生改变发送对应信号出去,设备发生改变(插拔光驱等)发出一个设备改变事件到 Qt 事件队列去处理

plugin 部分

这部分是每个插件实现部分,重点需要关注核心插件corePlugin的实现,其它插件都是要依赖核心插件来实现业务功能

在这个插件里面主要初始化了主窗口、菜单管理类实例以及一些模式管理对象初始化

后面我们会看到各种各样的插件,比如你打开Qt Creator的时候首页显示的内容,也是单独的一个插件,名字叫做weilcome

每个插件都有一个标识ID,用来区分是你自己写的插件,防止别人恶意修改插件

Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Core.json")
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Welcome.json")

每个插件还有一个对应的元数据描述配置文件,这个文件配置了该插件的一些基本信息,比如插件名字、版本、所有权、依赖那些插件等,这些配置信息在编译时会写进该插件动态库当中,采用的是Qt的元对象技术来实现的,这样在插件加载运行时就能通过反射动态获取这些信息,继而用来进行一些插件之间加载关系的验证

一个简单的配置描述如下所示

{
\"Name\" : \"Welcome\",
\"Version\" : \"$$QTCREATOR_VERSION\",
\"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
\"Vendor\" : \"The Qt Company Ltd\",
\"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
\"License\" : [ \"Commercial Usage\",
\"\",
\"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
\"\",
\"GNU General Public License Usage\",
\"\",
\"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
],
\"Category\" : \"Qt Creator\",
\"Description\" : \"Secondary Welcome Screen Plugin.\",
\"Url\" : \"http://www.qt.io\",
$$dependencyList
}

其中很关键的是一些变量值,比如$$QTCREATOR_VERSION,通过这个变量直接可以读取到我们在工程qtcreator.pri中定义的变量值,继而快速统一加载显示,其它变量值获取类似

其次,需要关注的是每个插件的配置依赖文件比如welcome_dependencies.pri,该文件中包含了依赖那些库那些插件

# 插件名字
QTC_PLUGIN_NAME = Welcome # 插件依赖的库
QTC_LIB_DEPENDS += \
extensionsystem \
utils # 插件依赖的插件
QTC_PLUGIN_DEPENDS += \
coreplugin

某个插件依赖那些插件和动态库,只需要在对应位置追加其名字即可,工程配置文件会自动进行加载,这样编写可以减少很多重复工作,而且插件依赖关系也很清楚的看到

app 部分

这个部分是程序入口实现部分,这里主要是获取插件路径,初始化插件、配置文件,加载每个插件,如果都没有错误,那么初始化完成后主界面就会显示出来,直接看主函数入口看就行

关键部分是插件管理器的初始化,设置插件搜索路径后对每个插件进行初始化操作

    PluginManager pluginManager;
PluginManager::setPluginIID(QLatin1String("org.qt-project.Qt.QtCreatorPlugin"));
PluginManager::setGlobalSettings(globalSettings);
PluginManager::setSettings(settings);
......
const QStringList pluginPaths = getPluginPaths() + customPluginPaths;
PluginManager::setPluginPaths(pluginPaths); ......
PluginManager::loadPlugins();

在这里还有一个需要注意的地方,就是这个文件app_version.h.in

这个是一个模板文件,qmake加载执行完毕后,会在临时目录下生成对应的头文件app_version.h

#pragma once

namespace Core {
namespace Constants { #define STRINGIFY_INTERNAL(x) #x
#define STRINGIFY(x) STRINGIFY_INTERNAL(x) const char IDE_DISPLAY_NAME[] = \"$${IDE_DISPLAY_NAME}\";
const char IDE_ID[] = \"$${IDE_ID}\";
const char IDE_CASED_ID[] = \"$${IDE_CASED_ID}\"; #define IDE_VERSION $${QTCREATOR_VERSION}
#define IDE_VERSION_STR STRINGIFY(IDE_VERSION)
#define IDE_VERSION_DISPLAY_DEF $${QTCREATOR_DISPLAY_VERSION} #define IDE_VERSION_MAJOR $$replace(QTCREATOR_VERSION, "^(\\d+)\\.\\d+\\.\\d+(-.*)?$", \\1)
#define IDE_VERSION_MINOR $$replace(QTCREATOR_VERSION, "^\\d+\\.(\\d+)\\.\\d+(-.*)?$", \\1)
#define IDE_VERSION_RELEASE $$replace(QTCREATOR_VERSION, "^\\d+\\.\\d+\\.(\\d+)(-.*)?$", \\1) const char * const IDE_VERSION_LONG = IDE_VERSION_STR;
const char * const IDE_VERSION_DISPLAY = STRINGIFY(IDE_VERSION_DISPLAY_DEF);
const char * const IDE_AUTHOR = \"The Qt Company Ltd\";
const char * const IDE_YEAR = \"$${QTCREATOR_COPYRIGHT_YEAR}\"; #ifdef IDE_REVISION
const char * const IDE_REVISION_STR = STRINGIFY(IDE_REVISION);
#else
const char * const IDE_REVISION_STR = \"\";
#endif
... } // Constants
} // Core

这个模板文件定义了一些常量,某些变量值引用的是宏定义,最后编译后宏定义会被替换掉真正的值,在我们代码中引入时真正起作用,更加详细使用过程后面统一分析pro文件技巧时会提到

总结

学习到这里,已经大概清楚了Qt Creator框架的基本结构了,首先是一些基本库,这些动态库封装了一些基本功能和用法,方便在多个模块重复调用使用,其次是插件管理系统的实现,主要包含插件对象声明周期管理,插件加载、插件卸载、插件直接依赖关系处理

比如有插件A、B、C,C插件现在同时依赖于插件A和B,那么在加载时就需要特殊考虑

最后就是多个插件的初始化,主窗口和菜单组件管理类,方便拓展到其它插件进行访问管理

整个QTC插件系统是由一个个动态库构成的,每个插件互相配合实现了这样一个复杂的跨平台的IDE,仔细研究下就可以发现很多奇妙的用法和知识

相关阅读

  • Qt Creator 学习笔记01,初识 QTC[1:1]

  1. http://kevinlq.com/2021/11/01/learn01_studyQTC/

Qt Creator 源码学习笔记02,认识框架结构的更多相关文章

  1. Qt Creator 源码学习笔记03,大型项目如何管理工程

    阅读本文大概需要 6 分钟 一个项目随着功能开发越来越多,项目必然越来越大,工程管理成本也越来越高,后期维护成本更高.如何更好的组织管理工程,是非常重要的 今天我们来学习下 Qt Creator 是如 ...

  2. Qt Creator 源码学习笔记04,多插件实现原理分析

    阅读本文大概需要 8 分钟 插件听上去很高大上,实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows下以.dll结尾,Linux 下以.so结尾 开发插件其实就是开发一个动态 ...

  3. Qt Creator 源码学习笔记01,初识QTC

    阅读本文大概需要 4 分钟 Qt Creator 是一款开源的轻量级 IDE,整个架构代码全部使用 C++/Qt 开发而成,非常适合用来学习C++和Qt 知识,这也是我们更加深入学习Qt最好的方式,学 ...

  4. Qt Creator 源码学习 03:qtcreator.pro

    当我们准备好 Qt Creator 的源代码之后,首先进入到它的目录,来看一下它的源代码目录有什么奥秘. 这里一共有 9 个文件夹和 9 个文件.我们来一一看看它们都是干什么用的. .git: 版本控 ...

  5. 一行一行分析JQ源码学习笔记-02

    1.防止冲突    设置新变量保存

  6. JUC源码学习笔记2——AQS共享和Semaphore,CountDownLatch

    本文主要讲述AQS的共享模式,共享和独占具有类似的套路,所以如果你不清楚AQS的独占的话,可以看我的<JUC源码学习笔记1> 主要参考内容有<Java并发编程的艺术>,< ...

  7. JUC源码学习笔记5——线程池,FutureTask,Executor框架源码解析

    JUC源码学习笔记5--线程池,FutureTask,Executor框架源码解析 源码基于JDK8 参考了美团技术博客 https://tech.meituan.com/2020/04/02/jav ...

  8. Underscore.js 源码学习笔记(下)

    上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...

  9. Underscore.js 源码学习笔记(上)

    版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}());  这样的东西,我们应该知道这是一个 IIFE(立即执行 ...

随机推荐

  1. 为什么'\x1B'.length===1?\x与\u知识延伸

    背景 先讲一下背景,再说原因 大多数库都会在日志中使用chalk库为console的内容进行上色 被chalk处理后,其原本的内容会被'\x1B...'所包裹 console.log(chalk.bl ...

  2. Linux从头学15:【页目录和页表】-理论 + 实例 + 图文的最完全、最接地气详解

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

  3. Jenkins REST API 实例

    背景:    Jenkins具有丰富的插件生态,足以满足我们日常工作的需求,但如果我们想通过具体的Jenkins任务直接对外提供服务,而不想将内部的具体实现对外暴露(否则,需添加对应的用户权限,通过页 ...

  4. Java(22)常用API一

    1 API 1.1 API概述 什么是API ​ API (Application Programming Interface) :应用程序编程接口 java中的API ​ 指的就是 JDK 中提供的 ...

  5. Verilog的数据流、行为、结构化与RTL级描述

    Verilog语言可以有多种方式来描述硬件,同时,使用这些描述方式,又可以在多个抽象层次上设计硬件,这是Verilog语言的重要特征. 在Verilog语言中,有以下3种最基本的描述方式: 数据流描述 ...

  6. Vulnhub实战-FALL靶机👻

    Vulnhub实战-FULL靶机 下载地址:http://www.vulnhub.com/entry/digitalworldlocal-fall,726/ 1.描述 通过描述我们可以知道这个靶机枚举 ...

  7. 2021.1.8 NKOJ 周赛总结

    意料之中..... A:nkoj 3900 AC小程序 http://oi.nks.edu.cn/zh/Problem/Details/3900 A题比较简单,单独分析一下A和C,其实就是一个斐波那契 ...

  8. 按照工业标准1英寸=25.4mm,而在电子元件成像领域Sensor尺寸1英寸=16mm。

    按照工业标准1英寸=25.4mm,而在电子元件成像领域Sensor尺寸1英寸=16mm. 我们平常所说的CCD/CMOS的尺寸,实际上是指Sensor对角线的长度,这一点跟我们平常所说的屏幕尺寸是一样 ...

  9. 力扣 - 剑指 Offer 66. 构建乘积数组

    题目 剑指 Offer 66. 构建乘积数组 思路1 按照一般的思路就是将所有的相乘,然后除以每一位数字就是答案,但是题目要求我们不能使用除法,因此我们会想到每次遍历到每个数字的时候,在遍历一遍数组, ...

  10. 全面!总结BQ系列阻抗跟踪电量计化学Chemical ID配置和Golden学习方法

    BQ系列阻抗跟踪电量计SOC最高能达到1%,功能强大,应用起来也比较复杂.不仅要配置好参数,匹配好化学ID,并且进行好Golden学习和相关测试.本文就讲述ID匹配,Golden学习和测试的终极方法流 ...