深入理解qtcreator的插件设计架构(代码片段)

author author     2023-01-20     740

关键词:

+++
date = "2017-04-28T00:59:02+08:00"
draft = true
title = "深入理解QtCreator的插件设计架构"
blog ="blog.qizr.tech"
+++
基于插件的设计好处很多,把扩展功能从框架中剥离出来,降低了框架的复杂度,让框架更容易实现.扩展功能与框架以一种很松的方式耦合,两者在保持接口不变的情况下,可以独立变化和发布,将软件的复杂度限制在了单个的插件之中,比较适用与需求不定或是业务容易发生变化的软件设计.

1.架构描述
个人感觉,《Software Architecture Patterns》对该架构的描述比较准确,如下图所示.

技术分享图片
1.1核心系统
核心系统包含两部分功能,1.最小功能集合,提供给各个插件模块使用,也就是插件如何使用核心系统的功能进行功能扩展;2.插件模块的生命周期管理.

1.2插件模块
插件模块用于增强或扩展核心系统以产生额外的业务功能,插件模块应该是高度内聚,尽量避免产插件之间的依赖.

1.3契约
这里的契约包含了核心模块和插件模块的通信协议,模块之间不建议发生任何依赖.常见通信方式包含插件会提供一些虚函数,供核心系统中的模块加载器进行初始化,销毁等工作,核心系统提供一些函数,供具体插件模块使用,还可以通过soap等远程通信方式完成两者之间的通信.

2.qtcreator案例
提到插件架构总会想到eclipse,其实众多IDE,编辑器,都采取了插件架构,例如QTCreator,sumblime等,下面结合这些实际的例子,看下这些优秀的软件是如何将插件模式落地的.

qtcreator是一款适用于qt开发的跨平台IDE,这里重点关注这款ide的设计,拿这个作为例子是因为本人在工作期间用的比较多.

qtcreator(4.2)界面:长得多像一个IDE啊
技术分享图片
架构描述
QtCreator是由插件加载器和一堆插件构成的.如图所示.
技术分享图片
其中PluginManage负责插件的加载,管理,销毁等工作.插件中有一个叫core的插件,是QtCreator最基础的插件,提供了向界面增加菜单等功能,具体可以参考如何编写qt插件.

2.1核心系统
可以看出,QtCreator的核心系统是由PluginManager和Core插件构成.前者负责插件的管理工作,后者负责提供QtCreator的最小功能集合,在PluginManager面前Core是当做普通插件进行加载,在自定义插件面前Core就是一个基础功能库,使用该库可以扩展QtCreator的功能.

QtCreator的所有功能,全是由插件实现,这种思路的优点是简化了顶层业务,也就是插件管理工作的逻辑,在那里只有PlunginManager和Plugin,缺点是增加了加载插件的复杂度,因为基础库这个插件需要被其他插件依赖,所以creator在插件加载时就必须要考虑插件之间的依赖性,这个在后面可以看到qtcreator是如何解决这个问题的.

core插件:
技术分享图片
自定义插件使用core向界面添加菜单:
技术分享图片
这里可以做个验证,QtCreator有个"关于插件"的菜单项,可以尝试把所有插件都移除,可以发现其中core插件是灰的,不能进行移除.

关于插件:
技术分享图片
重启creator后,可以看到,基本就剩一个界面了.

裸体的qtcreator:
技术分享图片
有读者可能会问,这不还是有个界面嘛,这剩下眼睛能看到界面就是core插件完成的了,可以看到core插件初始化的代码中定义了MainWindow,所以说core插件是creator不能没有的.

core插件初始化:
技术分享图片
2.2插件模块
插件都需要继承IPlugin的接口,关于IPlugin的描述参见官方文档,只说下重点,插件是由描述文件和继承IPlugin的类库组成.

描述文件:
技术分享图片
描述文件描述了插件的基本信息,用于被插件管理器加载,最后一行描述了该插件所依赖的其他插件,PluginManage会根据插件之间的依赖关系决定加载顺序,
Plugin是插件的基类接口,列举下主要接口

//初始化函数,在插件被加载时会调用
bool IPlugin::initialize(const QStringList &arguments, QString *errorString)

//在所有插件的initialize函数被调用后,调用该函数,此时该插件依赖的插件已经初始化完成
void IPlugin::extensionsInitialized()

//在所有插件的extensionsInitialized函数调用完成以后进行调用
bool IPlugin::delayedInitialize()

关于以上两个函数的调用顺序可以参见官网的说法:

1.All plugin libraries are loaded in root-to-leaf order of the dependency
tree.

2.All plugins‘ initialize functions are called in root-to-leaf order of the
dependency tree. This is a good place to put objects in the plugin manager‘s object pool.

3.All plugins‘ extensionsInitialized functions are called in leaf-to-root order of the dependency tree. At this point, plugins can be sure that all plugins that depend on this plugin have been initialized completely (implying that they have put objects in the object pool, if they want that during the initialization sequence).

2.3契约
契约包含两部分,1.核心系统如何加载插件,2.插件如何使用核心系统为软件扩展功能

核心系统如何加载插件
结合上文中的分析,我们结合源码来看下,QtCreator的插件加载机制,从main函数开始看,能够找到下面这个函数.

PluginManager::loadPlugins();

让我们跟下去,看看loadPlugins()里面的源码,通过源码和注释基本能够看清qtcreator加载插件的机制.

void PluginManagerPrivate::loadPlugins()

    //1.获取待加载的插件,loadQueue函数将插件根据彼此的依赖关系进行了排序,
    //有兴趣的同学可以关注下这个函数
    QList<PluginSpec *> queue = loadQueue();
    //2.加载插件
    foreach (PluginSpec *spec, queue) 
        loadPlugin(spec, PluginSpec::Loaded);
    
    //3.初始化插件,调用每个插件的initialize函数
    foreach (PluginSpec *spec, queue) 
        loadPlugin(spec, PluginSpec::Initialized);
    
    //4.按照相反顺序调用每个插件的extensionsInitialized函数
    Utils::reverseForeach(queue, [this](PluginSpec *spec) 
        loadPlugin(spec, PluginSpec::Running);
        if (spec->state() == PluginSpec::Running) 
            delayedInitializeQueue.append(spec);
         else 
            // Plugin initialization failed, so cleanup after it
            spec->d->kill();
        
    );
    emit q->pluginsChanged();

    delayedInitializeTimer = new QTimer;
    delayedInitializeTimer->setInterval(DELAYED_INITIALIZE_INTERVAL);
    delayedInitializeTimer->setSingleShot(true);
    connect(delayedInitializeTimer, &QTimer::timeout,
            this, &PluginManagerPrivate::nextDelayedInitialize);
    //5.延时调用每个插件的delayedInitialize函数
    delayedInitializeTimer->start();

关于解决插件依赖问题可以查看上文源码中loadQueue函数,具体的插件加载机制可以查看源码中loadPlugin函数,QtCreator的源码还是比较容易理解的.
插件如何使用核心系统
通过官网的教程,我们可以创建一个自己的creator插件,并能够了解自己的插件如何使用核心系统提供的功能,但是总感觉不过瘾,所以让我们来分析一个creator自带的插件,这里我选取git插件.

git插件:
技术分享图片
该插件覆盖了git基本操作,我以基本的log命令操作入手,来分析与核心系统的交互,完成Log操作以后,界面显示如下,主编辑区显示了log信息.
技术分享图片
首先找到git插件代码,git插件对应下面这个类

class GitPlugin : public VcsBase::VcsBasePlugin

    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Git.json")

public:
...

找到log对应的函数对应的槽函数logRepository,调用了m_gitClient的log函数,继续跟踪进去.

void GitPlugin::logRepository()

    const VcsBasePluginState state = currentState();
    QTC_ASSERT(state.hasTopLevel(), return);
    m_gitClient->log(state.topLevel());

里面代码较多,只关注重点的几行

void GitClient::log(const QString &workingDirectory, const QString &fileName,
                    bool enableAnnotationContextMenu, const QStringList &args)

   ...
   //1.从核心系统获取Creator的主编辑区
    VcsBaseEditorWidget *editor = createVcsEditor(editorId, title, sourceFile,
                                                  codecFor(CodecLogOutput), "logTitle", msgArg);
   ...
  // 2.开始拼接git的Log命令参数
    QStringList arguments =  "log", noColorOption, decorateOption ;
    int logCount = settings().intValue(GitSettings::logCountKey);
    if (logCount > 0)
        arguments << "-n" << QString::number(logCount);

    auto *argWidget = editor->configurationWidget();
    argWidget->setBaseArguments(args);
    QStringList userArgs = argWidget->arguments();

    arguments.append(userArgs);

    if (!fileName.isEmpty())
        arguments << "--follow" << "--" << fileName;
    //3.执行git命令,并将命令结果显示在主编辑区
    vcsExec(workingDir, arguments, editor);


VcsCommand *VcsBaseClientImpl::vcsExec(const QString &workingDirectory, const QStringList &arguments,
                                       VcsBaseEditorWidget *editor, bool useOutputToWindow,
                                       unsigned additionalFlags, const QVariant &cookie) const

    //4.创建命令
    VcsCommand *command = createCommand(workingDirectory, editor,
                                        useOutputToWindow ? VcsWindowOutputBind : NoOutputBind);
    command->setCookie(cookie);
    command->addFlags(additionalFlags);
    if (editor)
    //5.得到编辑区指针
        command->setCodec(editor->codec());
    //6.进入命令队列执行,并将结果显示在编辑区内
    //典型的命令模式!
    enqueueJob(command, arguments);
    return command;

以上可以看出,log命令使用了核心系统的交互是在获取主编辑区那里,剩余就是插件内部的处理逻辑了.

3.扩展
以上了解了creator的设计思路,扩展一下,在消息中间件,微服务盛行的今天,核心系统和插件完全可以设计成不在一个进程当中,我们可以把核心系统和插件之间通过远程调用的方式进行联系,核心系统与插件均可设计为单个进程或者服务,让整个系统的部署更加灵活,单个插件的问题也不会影响到整个系统.当然,核心系统与插件之间的发现使用机制,是需要我们结合实际使用场景进一步深入思考的.

4.总结
本文首先介绍了插件设计架构的理论基础,然后又结合了QtCreator的源码对跟架构如何落地进行了分析,希望能够帮助到想要了解插件架构的同学们.

深入unreal蓝图开发:理解蓝图技术架构(代码片段)

前面几篇博客谈了几种常用的蓝图扩展方式,其中也对蓝图的底层机制进行了部分的解析,但是还不够整体。这篇文章谈一下目前我对蓝图技术架构的系统性的理解,包括蓝图从编辑到运行的整个过程。蓝图的发展历... 查看详情

《深入理解mybatis原理1》mybatis的架构设计以及实例分析

 《深入理解mybatis原理》MyBatis的架构设计以及实例分析MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个sele... 查看详情

深入理解jquery插件开发总结(代码片段)

1,开始可以通过为jQuery.fn增加一个新的函数来编写jQuery插件。属性的名字就是你的插件的名字:jQuery.fn.myPlugin=function()//开始写你的代码吧!;但是,那惹人喜爱的美元符号$哪里去了?她就是jQuery,但是为了确保你的插件与其他... 查看详情

spark设计理念与基本架构

《深入理解Spark:核心思想与源代码分析》一书前言的内容请看链接《深入理解SPARK:核心思想与源代码分析》一书正式出版上市《深入理解Spark:核心思想与源代码分析》一书第一章的内容请看链接《第1章环境准备》本文主要... 查看详情

深入理解java中23种设计模式(代码片段)

设计模式介绍设计模式(DesignPatterns):一套被反复使用,多数人知晓,经过分类编目,代码设计的总结使用设计模式是为了可重用代码,让代码更容易理解,保证代码可靠性项目中合理运用设计模式可以完美的解决很多问题,每种模式都有... 查看详情

第五篇深入理解httpsecurity的设计(代码片段)

深入理解HttpSecurity的设计一、HttpSecurity的应用  在前章节的介绍中我们讲解了基于配置文件的使用方式,也就是如下的使用。  也就是在配置文件中通过security:http等标签来定义了认证需要的相关信息,但是在SpringBoot... 查看详情

深入理解vsto,开发word插件的利器(代码片段)

  开发了vsto,客户那边也有一些反映插件安装失败或者加载不上的情况。于是我下定决定再理解下vsto的工作机制,如下图:   如上图所示,我把vsto的解决方案分为两部分,一部分是vsto Add-ins,另外一部分是Microsoft&... 查看详情

深入yarn系列1:窥全貌之yarn架构,设计,通信原理等(代码片段)

 深入YARN系列主要分为:深入YARN系列1:窥全貌之YARN架构,设计,通信原理等深入YARN系列2:剖析ResourceManager的架构与组件使用深入YARN系列3:剖析NodeManager架构,组件与生产应用深入YARN系列4:剖析... 查看详情

从原码,反码,补码的设计理念来深入理解其原理(代码片段)

从原码,反码,补码为什么当初要这样设计出发,让你更透彻的理解它们的原理原码,反码,补码大家都知道,下面通过解析为什么当初要这样设计,让你更透彻的理解它们的原理。文章参考:https://blog.csdn.net/afsvsv/article/details/... 查看详情

深入理解jquery插件开发总结(代码片段)

(入门级)一、插件的几种写法首先,在具体说明编写插件之前,我们先假定一个使用场景:有一个HTML页面,页面上放置了一个5行3列的表格。<tableid="newTable"><tr><td>1</td><td>1</td><td>1</td></tr&g... 查看详情

深入剖析优惠券核心架构设计(代码片段)

:::,::,::,::,::,:::,:::至于搜索数据的维护,尤其是商品销量定时写入,这些都是些常规的业务实现,这里就不一一累述了。用户的卡券包实现很简单,只需分页查询用户的券表即可,加上过滤条件”用户券的状态要是未使用“。如... 查看详情

深入理解单例设计模式(代码片段)

一、概述单例模式是面试中经常会被问到的一个问题,网上有大量的文章介绍单例模式的实现,本文也是参考那些优秀的文章来做一个总结,通过自己在学习过程中的理解进行记录,并补充完善一些内容,一方面巩固自己所学的... 查看详情

深入理解设计模式-桥接模式(代码片段)

文章目录前言一、定义二、使用场景三、代码样例1.类图2.抽象化角色3.扩展抽象化角色4.实现化角色5.具体的实现化角色6.客户端四、优缺点结尾前言现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不... 查看详情

深入理解设计模式:抽象工厂模式(代码片段)

接着上一次的工厂方法模式讲。假设目前你的程序里面有三个对象IphoneX、IphoneXs、IphoneXR的尺寸,那么你使用工厂模式就已经足够了,因为她们属于同一个品类,都属于苹果,如果在添加一个IPhone2019产品,也只需要把IPhone2019加入... 查看详情

qtcreator插件开发——qtcreator插件实例(代码片段)

QtCreator插件开发(一)——QtCreator插件实例版权声明:本系列文章翻译自:WritingQtCreatorPlugins。如果任何人或机构对于版权有异议,请联系我。本文将使用QtCreator-2.8.1版本进行插件开发,由于QtCreator-2.8.1的插件机制进行了部分更... 查看详情

qtcreator源码全方面分析(2-0)(代码片段)

目录ExtendingQtCreatorManual生成领域特定的代码和模板代码片段文件和项目模板自定义向导支持其他文件类型MIME类型高亮和缩进自定义文本编辑器其他自定义编辑器运行外部工具简单的外部工具复杂的外部工具所有主题ExtendingQtCreato... 查看详情

深入理解设计模式-享元模式(代码片段)

文章目录一、定义二、使用场景三、代码样例1.类图2.抽象享元角色3.具体享元角色3.享元工厂4.享元客户端5.输出四、优缺点五、拓展延伸1.JDK源码解析结尾一、定义运用共享技术来有效地支持大量细粒度对象的复用。它通过共享... 查看详情

理解underscore的设计架构(代码片段)

在一个多月的毕业设计之后,我再次开始了Underscore的源码阅读学习,断断续续也写了好些篇文章了,基本把一些比较重要的或者个人认为有营养的函数都解读了一遍,所以现在学习一下Underscore的整体架构。我相信很多程序员都... 查看详情