Qt插件机制及加载流程

简介

​ 插件实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows下以.dll结尾,Linux 下以.so结尾。那么开发插件其实就是开发一个动态库,该动态库能够很好的加载进主程序、访问主程序资源、和主程序之间进行通信

Qt Creator插件理解起来其实很简单,定义一个接口类作为基类,其他插件需要继承该类实现对应的虚方法,每个插件作为独立子工程编译后生成对应的动态库

img

主函数加载每个插件对象,然后转化为对应插件实例

QPluginLoader loader(pluginName);
loader.load();
IPlugin *pluginObject = qobject_cast<IPlugin*>(loader.instance());

// 比如转为核心插件实例
CorePlugin *pCorePluginObj = qobject_cast<CorePlugin*>(loader.instance());

然后每个插件各自根据对应业务逻辑调用接口就行了

当然了,Qt Creator 在实现过程当中肯定不止这么简单,插件的加载、解析、卸载等管理还是比较复杂的,非常值得我们去学习

插件组成

整个插件系统由插件管理器、核心插件、其它插件组成,其中核心插件是系统中不可缺少的,其它插件都要依赖核心插件来进行开发通信

我们先打开 Qt Creator 插件菜单看看都包含那些插件

img

可以看到所有的插件根据类型进行了分组,同一个类型插件同属一个树节点,每个插件后面有个复选框可以控制加载/卸载该插件

每个插件还包含了版本信息以及归属作者信息,这些信息都可以通过对象元数据来配置,插件的版本也很有用,我们编写的插件可以限定在某个版本之间兼容,这个时候版本号就起作用了,详细实现后面会讲解到

我们可以加载、卸载某个插件,但是无论怎么选择,核心Core插件是不能卸载的,why? 因为整个插件系统是建立在 Core 核心插件基础之上的,离开核心插件其它插件无法存活

插件管理

插件的核心其实就是对插件的管理,这个是本篇的重点,是我们阅读源码时需要重点关注的部分,为什么这么说呢,我举个栗子大家就清楚了

插件管理器

插件管理器实现主要在PluginManager 类当中实现,该类管理了所有的插件加载、卸载以及释放

对象管理池

class EXTENSIONSYSTEM_EXPORT PluginManager : public QObject
{
    Q_OBJECT
public:
    static PluginManager *instance();
    static void addObject(QObject *obj);
    static void removeObject(QObject *obj);
    
    ......
    
    friend class Internal::PluginManagerPrivate;
}

这个类是一个单例类,主要管理插件对象,可以理解为对象池,详细实现都封装在了 d指针类里面,

我们继续进去看看

pluginmanager_p.h

class EXTENSIONSYSTEM_EXPORT PluginManagerPrivate : public QObject
{
    Q_OBJECT
public:
    ......
    QHash<QString, QList<PluginSpec *>> pluginCategories;
    QList<PluginSpec *> pluginSpecs;
    QList<QObject *> allObjects; // ### make this a QList<QPointer<QObject> > > ?
    
    ......
}

可以看到底层存储每个对象用的容器是 QList,从Qt Creator 4.10版本开始换成了 QVector来存储,说起来这两个容器的区别让我想到了,现在最新版本的 Qt当中,已经把两者合二为一了

template<typename T> using QVector = QList<T>;

所以使用哪个无所谓了,不过我们还是要搞清楚这两个容器的区别,什么时候用Vector,什么时候用 List

添加对象

void PluginManagerPrivate::addObject(QObject *obj)
{
    {
        QWriteLocker lock(&m_lock);
        if (obj == 0) {
            qWarning() << "PluginManagerPrivate::addObject(): trying to add null object";
            return;
        }
        if (allObjects.contains(obj)) {
            qWarning() << "PluginManagerPrivate::addObject(): trying to add duplicate object";
            return;
        }

        allObjects.append(obj);
    }
    emit q->objectAdded(obj);
}

这块核心代码其实很好理解,每次添加对象前先加锁,由于使用的是读写锁,不用担心函数返回死锁问题,判断对象是否合法以及是否已经存在,不存在则追加到 list 当中,最后抛出一个信号,这个信号在外部需要使用的地方可以绑定,比如模式切换里面就使用到了

void ModeManager::init()
{
    QObject::connect(ExtensionSystem::PluginManager::instance(), &ExtensionSystem::PluginManager::objectAdded,
                     m_instance, &ModeManager::objectAdded);
}

添加就对应的删除,原理和添加一样

- 删除对象

void PluginManagerPrivate::removeObject(QObject *obj)
{
    if (obj == 0) {
        qWarning() << "PluginManagerPrivate::removeObject(): trying to remove null object";
        return;
    }

    if (!allObjects.contains(obj)) {
        qWarning() << "PluginManagerPrivate::removeObject(): object not in list:"
            << obj << obj->objectName();
        return;
    }

    emit q->aboutToRemoveObject(obj);
    QWriteLocker lock(&m_lock);
    allObjects.removeAll(obj);
}

同样的把对象从list 当中进行了删除,在删除之前也向外抛出了信号,用法和添加信号配对使用

这里有个疑问,为啥锁不在函数最开头加呢?

插件管理

每个插件对象对应到底层是由 PluginSpec 来实例化的,每个插件使用 list容器存储,如下所示

QList<PluginSpec *> pluginSpecs;

插件核心类实现

class EXTENSIONSYSTEM_EXPORT PluginSpec
{
public:
    QString name() const;
    QString version() const;
    QString compatVersion() const;
    QString vendor() const;
    QString copyright() const;
    ......
    bool isRequir   ed() const;
    
    ......
    QVector<PluginDependency> dependencies() const;
    
private:
    PluginSpec();
}

阅读代码就可以发现,这个类主要是记录了每个插件的一些基本信息,那么这些信息是如何赋值的呢?通过插件描述文件来进行自动加载的,后面学习核心插件会看到

有个核心部分代码,插件依赖项dependencies,这个主要解决插件之间依赖关系使用,这个类也很简单很好理解

/*
 * 插件依赖相关信息
*/
struct EXTENSIONSYSTEM_EXPORT PluginDependency
{
    enum Type {
        Required,       // 必须有此依赖
        Optional,       // 此依赖不是必须的
        Test
    };

    PluginDependency() : type(Required) {}

    QString name;           //被依赖的插件名字
    QString version;        //对应的版本号
    Type type;              //依赖类型
    bool operator==(const PluginDependency &other) const;
    QString toString() const;
};

比如插件A依赖插件BC,那么在插件A加载的时候对应的list当中就包含了B,C插件信息,必须等到这两个插件加载完成后才能加载插件A,这一点很重要

插件加载流程

前面学习了插件管理器当中的一些基本数据结构,现在来看看这些插件是怎么加载进去的,加载顺序和流程是怎么样的,下面我们来详细看看每个步骤都干了哪些工作,源码面前了无秘密

设置插件 IID

setPluginIID(const QString &iid)

这个id 是全局唯一,加载插件时会首先判断插件 ID 合法性,用于确定是你自己编写的插件,这样可以防止其它插件恶意注册加载

大家可以想想一下,如果别人也写了一个类似的插件,那么如果没有 ID 区分是不是就能加载进插件系统当中,从而破坏软件结构

设置全局配置类

setGlobalSettings(QSettings *settings)

“ 全局配置,一般存放的是默认值,用于恢复设置使用

设置局部配置类

setSettings(QSettings *settings)

“ 存放程序当前配置参数类。比如我们设置某个参数配置保存后会存在某个配置文件中,程序加载时会从该文件加载到QSettings对象当中供我们调用

设置插件路径

setPluginPaths(const QStringList &paths)

“ 插件路径一般是我们 exe 程序相邻路径下的,比如plugins/xxx.dll,当然也可以为任意路径下的动态库,只要路径正确合法都可以加载的,可以设置多条插件路径

读取插件信息

“ 用于读取插件原对象信息,主要包含三个过程

readMetaData()
resolveDependencies()
pluginsChanged()
  • 读元数据:这里会挨个读取每个插件,初始化 QPluginLoader,设置名字,为后面加载做准备,可以叫预加载,创建插件实例对象 PluginSpec,存储到 List 结构当中
  • 检测依赖关系::用于重新加载分析每个插件依赖关系,是一个双重循环,每个插件会和其它插件比较一次,最后按照插件名字进行排序
  • 插件改变:向外抛出信号,插件管理窗口用来刷新 view 列表信息

加载插件

“ 到了这里才开始真正加载插件了,主要包括下面几个流程

loadQueue()
loadPlugins()
(PluginSpec::Loaded)
(PluginSpec::Initialized)
(PluginSpec::Running)
  • 依赖初始化
  • 加载插件:这里里面才会真真去加载初始化每个插件,计算获取插件加载队列
  • 加载(PluginSpec::Loaded):
loadPlugin(PluginSpec *spec, PluginSpec::State destState)

调用 QPluginLoader.load(),真正加载插件,加载成功才可以获取每个插件方法,存储插件实例:

IPlugin *pluginObject = qobject_cast<IPlugin*>(loader.instance());
  • 初始化(PluginSpec::Initialized)
loadPlugin(PluginSpec *spec, PluginSpec::State destState)

这里会调用每个插件的初始化函数:initialize(),该函数是纯虚函数,每个插件必须重新实现

  • 运行(PluginSpec::Running)
loadPlugin(PluginSpec *spec, PluginSpec::State destState)

调用每个插件扩展初始化函数:extensionsInitialized(),此时会挨个判断每个插件状态是否在运行,是的话加入到延迟队列

  • 延迟初始化
nextDelayedInitialize()

“ 从延迟队列当中取出每个插件,调用各自延迟初始化函数:delayedInitialize()

插件加载结束

到此整个插件加载结束了,可以看出来,整个插件的加载过程说白了就是动态库加载解析然后调用每个动态库里面的虚函数来实现的,所有的插件都继承自共同的基类(接口)

  • 7
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值