Qt插件机制及加载流程
简介
插件实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows
下以.dll
结尾,Linux
下以.so
结尾。那么开发插件其实就是开发一个动态库,该动态库能够很好的加载进主程序、访问主程序资源、和主程序之间进行通信
Qt Creator
插件理解起来其实很简单,定义一个接口类作为基类,其他插件需要继承该类实现对应的虚方法,每个插件作为独立子工程编译后生成对应的动态库
主函数加载每个插件对象,然后转化为对应插件实例
QPluginLoader loader(pluginName);
loader.load();
IPlugin *pluginObject = qobject_cast<IPlugin*>(loader.instance());// 比如转为核心插件实例
CorePlugin *pCorePluginObj = qobject_cast<CorePlugin*>(loader.instance());
然后每个插件各自根据对应业务逻辑调用接口就行了
当然了,Qt Creator
在实现过程当中肯定不止这么简单,插件的加载、解析、卸载等管理还是比较复杂的,非常值得我们去学习
插件组成
整个插件系统由插件管理器、核心插件、其它插件组成,其中核心插件是系统中不可缺少的,其它插件都要依赖核心插件来进行开发通信
我们先打开 Qt Creator
插件菜单看看都包含那些插件
可以看到所有的插件根据类型进行了分组,同一个类型插件同属一个树节点,每个插件后面有个复选框可以控制加载/卸载该插件
每个插件还包含了版本信息以及归属作者信息,这些信息都可以通过对象元数据来配置,插件的版本也很有用,我们编写的插件可以限定在某个版本之间兼容,这个时候版本号就起作用了,详细实现后面会讲解到
我们可以加载、卸载某个插件,但是无论怎么选择,核心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
依赖插件B
和C
,那么在插件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()
插件加载结束
到此整个插件加载结束了,可以看出来,整个插件的加载过程说白了就是动态库加载解析然后调用每个动态库里面的虚函数来实现的,所有的插件都继承自共同的基类(接口)
Qt插件机制及加载流程相关推荐
- QT程序启动加载流程简介
1. QT应用程序启动加载流程简介 1.1 QWS与QPA启动客户端程序区别 1.1.1 QWS(Qt Window System)介绍 QWS(Qt Windows System)是Q ...
- kettle插件加载流程
前言 kettle遵循着插件机制,基于插件使得kettle整个结构非常清晰,耦合性低,移植性强,特别是对kettle进行二次开发尤其方便,根据了解,扩展step类型的插件比较多,具体步骤可以参考:ht ...
- 源码解析 --skywalking agent 插件加载流程
1. 插件 目前很多框架,都采用框架 + 插件的模式开发. 如DataX.FlinkX通过插件支持众多异构数据源, Skywalking通过插件实现针对很多软件如redis.mysql.dubbo等方 ...
- 【Android 插件化】Hook 插件化框架总结 ( 插件包管理 | Hook Activity 启动流程 | Hook 插件包资源加载 ) ★★★
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
- Qt插件机制介绍及实现
Qt插件机制介绍及实现 创建应用程序主窗口 创建Qt项目 编辑项目文件ImageView.pro mainwindow.cpp main.cpp mainwindow.cpp 编译运行 插件接口 实现 ...
- 打卡学习Gradle深度解析 - kts脚本加载流程
kts脚本加载流程 和groovy脚本一样,kts脚本也分为2个阶段 stage 1 执行buildscript和plugins部分,执行结果会对stage2 program的classpath有影响 ...
- AngularJS 初始化加载流程
一.AngularJS 初始化加载流程 1.浏览器载入HTML,然后把它解析成DOM. 2.浏览器载入angular.js脚本. 3.AngularJS等到DOMContentLoaded事件触发. ...
- 【Android 插件化】Hook 插件化框架 ( 加载插件包资源 )
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
- android中常用的下拉刷新加载更多_如何设计“加载流程”
为什么要处理"加载"状态 在页面拉取数据.或提交某些数据时,需要一定的时间来等待服务端返回结果.如果不处理加载,用户可能会看到一片空白,以为你的软件出错:或者因没有建立心理预期,被 ...
最新文章
- c语言多线程mysql_多线程读写mysql数据库
- UNITY ET 框架
- python3爬虫初探(八)requests
- 第二十二篇:Spring简单定时任务
- $.ajax所犯的错误。success后面不执行
- 内存泄漏和内存溢出的优化
- java构造方法赋值内存图_java 面向对象(九):类的结构:构造器(一)简介;属性赋值顺序;JavaBean的概念...
- android P监听SD卡热插拔执行symlink软链接的实现
- 项目配置不当引发了数据泄露,人已裂开!!
- junit测试SSH基本环境
- 明略数据获 10 亿人民币 C 轮融资,华兴新经济基金、腾讯领投
- sqlite的联表查询-转
- Android中处理崩溃异常和分析日志的两种思路
- 第7章 PCA与梯度上升法 学习笔记上
- 谈谈2014年草根站长的出路
- php如何开发阅读器,微信小程序阅读器的简单实例开发
- Vue - 选择器拼音快速检索目标(pinyin-match)
- 求有限区间内素数个数
- win7电脑蓝屏没有修复计算机,Win7旗舰版系统电脑老是出现蓝屏的修复教程
- png转ico 或如何制作ico文件