阅读本文大概需要 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. Bert文本分类实践(二):魔改Bert,融合TextCNN的新思路

    写在前面 ​ 文本分类是nlp中一个非常重要的任务,也是非常适合入坑nlp的第一个完整项目.虽然文本分类看似简单,但里面的门道好多好多,博主水平有限,只能将平时用到的方法和trick在此做个记录和分享 ...

  2. CentOS7下Hadoop伪分布式环境搭建

    CentOS7下Hadoop伪分布式环境搭建 前期准备 1.配置hostname(可选,了解) 在CentOS中,有三种定义的主机名:静态的(static),瞬态的(transient),和灵活的(p ...

  3. Linux下关于用户账户的几个文件解析

    Linux下关于用户账户的几个文件解析 Linux是一个多用户系统,但是对于一个多用户共存的系统中,当然不能够出现用户相互越权等一系列的安全问题,所以如何正确的管理账户成为了Linux系统中至关重要的 ...

  4. 无法解析的外部符号之_cvLoadImage,_cvCreateMat,_cvReleaseImage之类

    一个错误可能是:附加依赖项少添加了库函数: 还有一个可能是:配置设置错误了,比如该是64位,却设置成win32了.改过来就好了. 要注意opencv的使用中 在Debug.Release模式以及x64 ...

  5. 每日总结:Java基本语法 (2021.9.23)

         对象:对象是类的一个实例,有状态和行为. 类:类是一个模板,它描述一类对象的行为和状态. 方法:方法就是行为,一个类可以有很多方法. 实例变量:每个对象都有独特的实例变量,对象的状态由这些实 ...

  6. 题解 「BZOJ4919 Lydsy1706月赛」大根堆

    题目传送门 题目大意 给出一个 \(n\) 个点的树,每个点有权值,从中选出一些点,使得满足大根堆的性质.(即一个点的祖先节点如果选了那么该点的祖先节点的权值一定需要大于该点权值) 问能选出来的大根堆 ...

  7. L1-017 到底有多二 (15 分) java解题

    1 import java.util.Scanner; 2 3 public class Main { 4 public static void main(String args[]){ 5 doub ...

  8. FastAPI 学习之路(十五)响应状态码

    系列文章: FastAPI 学习之路(一)fastapi--高性能web开发框架 FastAPI 学习之路(二) FastAPI 学习之路(三) FastAPI 学习之路(四) FastAPI 学习之 ...

  9. 【UE4 C++】关卡切换、流关卡加载卸载

    切换关卡 基于 UGameplayStatics:: OenLevel UGameplayStatics::OpenLevel(GetWorld(), TEXT("NewMap") ...

  10. 【c++ Prime 学习笔记】第1章 开始

    1.1 编写一个简单的程序 int main() { return 0; } 函数 包含4部分: 返回类型(return type) 函数名(function name) 形参列表(parameter ...