Qt使用插件QPluginLoader 机制开发

简介:

插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。

Qt 提供了2种APIs来创建插件:

一种高级API,用于为Qt本身编写插件:自定义数据库驱动程序,图像格式,文本编解码器,自定义样式等。

一种用于扩展Qt应用程序的低级API。也就是说扩展我们自己使用Qt编写的程序。这要求应用程序使用 QPluginLoader 检测和加载插件。在这种情况下,插件可以提供任意功能,不限于数据库驱动程序、图像格式、文本编解码器、样式以及扩展Qt功能的其他类型的插件。本文主要通过示例来展示第二种方式创建插件。

原理:

重载了虚函数的dll,这跟抽象工厂类类似,这便是插件的原理。qt的插件可以说是一种动态库

Qt插件特点:

核心技术:QPluginLoader

Qt插件存储在共享库(DLL)中(即以动态库的方式存在),与使用QLibrary访问的共享库相比,它具有以下优势:

面向Interface编程,内部封装,模块和整体流程开发分离,提高开发效率。

QPluginLoader 检查插件是否与应用程序相同版本的 Qt 链接。

QPluginLoader 提供对根组件对象 (instance()) 的直接访问,而不是强制您手动解析 C 函数

插件

插件主要面向接口编程,无需访问.lib文件,热插拔、利于团队开发。即使在程序运行时.dll不存在,也可以正常启动,只是相应插件功能无法正常使用而已;

动态库

动态库需要访问.lib文件,而且在程序运行时必须保证.lib存在,否则无法正常启动

开发测试环境:QT5.15.2 + MSVC 2019

在容器端:

  1. 定义一组接口(只有纯虚函数的类)。
  2. 在QT_BEGIN_NAMESPACE和QT_END_NAMESPACE之间使用Q_DECLARE_INTERFACE()宏告诉Qt的元对象系统有关接口的信息。
  3. 在程序中使用 QPluginLoader加载插件。
  4. 使用qobject_cast()测试插件是否实现给定的接口。

Q_DECLARE_INTERFACE

Q_DECLARE_INTERFACE(类名,标识符)

此宏用于把标识符与类名接口关联起来。这个标识符是唯一的,这个宏通常在被放到一个类被定后的位置。

这个是Qt实现插件必须要有的一个宏, 把标识符与类名接口关联起来。

在插件端:

声明一个插件类,该类继承自QObject和插件要提供的接口类。

使用Q_INTERFACES()宏告诉Qt的元对象系统有关接口的信息。

使用Q_PLUGIN_METADATA()宏导出插件。

Q_PLUGIN_METADATA
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.SubPlugin" FILE "SubPlugin.json")

这个宏第一次参数定义了一个uuid,保证唯一即可,第二个json是必须要有的,当无法找到指定的文件时,moc 会出现错误,即使是空的文件也行

SubPlugin.json文件存放在源代码目录下,可以记录一下插件的信息(如插件名称、插件版本、插件依赖库),如下

{

    "name": "testSubPlugin",

    "version": "1.0",

    "dependencies": []

}

这个宏被用于声明元数据,这个元数据是被实例化插件的一部分。

这个宏需要通过对象声明被实例化接口的IID,并且要引用包含元数据内容的文件。

需要获取这些元数据(即上面的json数据),可在在通过下面的办法。

        QPluginLoader *loader = new QPluginLoader(this);

        loader->setFileName(plugin.absoluteFilePath());

        qDebug()<<loader->metaData().keys();

        QJsonObject json = loader->metaData().value("MetaData").toObject();

        QVariant var = json.value("name").toVariant();

Q_INTERFACES

Q_INTERFACES(AppInterface)   //声明这个插件实现是基于哪个插件接口的,使qobject_cast()能正确进行QObject*到接口指针的转换

QPluginLoader 

~QPluginLoader()

销毁 QPluginLoader 对象。

除非显式调用 unload(),否则插件会一直保留在内存中,直到应用程序终止。

Load

加载插件,如果插件加载成功则返回true; 否则返回false。

unload

卸载插件,如果插件可以卸载则返回true,否则返回false。

这在应用程序终止时自动发生,因此通常不需要调用此函数。

如果 QPluginLoader 的其他实例正在使用相同的插件,则调用将失败,并且只有在每个实例都调用了 unload() 时才会发生卸载。

不要尝试删除根组件。 而是依靠 unload() 会在需要时自动删除它

Instance

返回插件的根组件对象,组件对象是一个 QObject。 使用 qobject_cast() 可将其转成所需的对象。如果根组件对象被销毁,则调用此函数会创建一个新实例。

该函数返回的根组件在 QPluginLoader 销毁时不会被删除。 如果要确保删除根组件,则应在不再需要访问核心组件时立即调用 unload()。 当库最终卸载时,根组件将自动删除。

metaData

返回此插件的元数据。 元数据是在编译插件时使用 Q_PLUGIN_METADATA() 宏以 json 格式指定的数据。

例子

首先创建一个Qt Subdirs Project工程PluginDemo,并添加一个app应用程序端并在app.pro添加输出目录DESTDIR = $$PWD/../bin

  1. 然后添加一个appinterface.h的头文件,该类作为插件接口类。
#ifndef APPINTERFACE_H#define APPINTERFACE_H#include <QList>#include <QAction>class AppInterface{public:virtual ~AppInterface() {}// 插件的名字virtual QString name() const = 0;// 插件返回的QAction列表virtual QList<QAction *> actions() const = 0;};QT_BEGIN_NAMESPACEQ_DECLARE_INTERFACE(AppInterface, "plugindemo_app_interface")QT_END_NAMESPACE#endif // APPINTERFACE_H
  1. 在工程上右键,选择新子项目,选择Library->C++ Library类型选择“shared Library”,qt module 选择“widgets”.插件名称为subplugin.
  2. 修改subplugin.pro

添加CONFIG+=plugin  //目的就是为是该dll作为插件

target.path = $$PWD/../bin/pluginsINSTALL+=target //把插件dll拷贝到容器的bin目录下。需构建步骤添加 make install

  1. 实现接口类SubPlugin.h,注意添加Q_INTERFACESQ_PLUGIN_METADATA
#ifndef SUBPLUGIN_H#define SUBPLUGIN_H#include "subPlugin_global.h"#include "../app/appinterface.h"#include <QObject>class SUBPLUGIN_EXPORT SubPlugin : public QObject, public AppInterface{public:SubPlugin();Q_OBJECTQ_INTERFACES(AppInterface)   //声明这个插件实现是基于哪个插件接口的Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.SubPlugin" FILE "SubPlugin.json")  //使用Q_PLUGIN_METADATA宏导出插件// AppInterface interfacepublic:virtual QString name() const override;virtual QList<QAction *> actions() const override;};#endif // SUBPLUGIN_H#include "subplugin.h"SubPlugin::SubPlugin(){}QString SubPlugin::name() const{return "SubPlugin";}QList<QAction *> SubPlugin::actions() const{QAction *aboutQt = new QAction;aboutQt->setText(tr("About Qt"));QList<QAction *> result;result << aboutQt;return result;}
  1. 在容器的Dialog.cpp里面添加加载插件的代码
#include "dialog.h"#include "ui_dialog.h"#include "appinterface.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QPushButton>Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog){loadPlugins();ui->setupUi(this);}Dialog::~Dialog(){delete ui;}void Dialog::loadPlugins(){QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins";QStringList filters;#if defined(Q_OS_LINUX)filters << "*.so";#elif defined(Q_OS_WIN)filters << "*.dll";#endifQFileInfoList plugins = QDir(pluginPath).entryInfoList(filters,QDir::Files | QDir::NoSymLinks);for (int i = 0; i < plugins.count(); ++i) {QFileInfo plugin = plugins.at(i);QPluginLoader *loader = new QPluginLoader(this);loader->setFileName(plugin.absoluteFilePath());qDebug()<<loader->metaData().keys();QJsonObject json = loader->metaData().value("MetaData").toObject();QVariant var = json.value("name").toVariant();bool result = loader->load();qDebug() << QString("load plguin %1 result: ").arg(plugin.absoluteFilePath()) << result;if (result) {QObject *pluginInstance = loader->instance();AppInterface *appInterface = qobject_cast<AppInterface *>(pluginInstance);if (appInterface != nullptr) {qDebug() << "add actions from plugin:" << appInterface->name();QList<QAction *> actions = appInterface->actions();for (int k = 0; k < actions.count(); ++k) {QAction *ac = actions.at(k);QString strtext = ac->text();}}} else {qDebug() << loader->errorString();}}}

插件管理

自定义pluginmanager类,封装成dll

管理所有插件,包括加载、卸载等。并暴露方法给主程序。

插件管理器代码

插件接口

#ifndef PLUGININTERFACE_H#define PLUGININTERFACE_H#include <QtWidgets/qwidget.h>#include <QString>class PluginInterface{public:virtual ~PluginInterface() {}// 插件返回的QAction列表virtual QString datawrite() = 0;};Q_DECLARE_INTERFACE(PluginInterface, "PluginManager.PluginInterface")#endif // PLUGININTERFACE_H

插件管理器

#ifndef PLUGINMANAGER_H#define PLUGINMANAGER_H#include "PluginManager_global.h"#include <QObject>#include <QPluginLoader>class PLUGINMANAGER_EXPORT PluginManager:public QObject{Q_OBJECTprivate:PluginManager();public:static PluginManager *getinstance();//加载所有插件void loadAllPlugins();//卸载所有插件void unloadAllPlugins();QString datawrite();private:static PluginManager *m_instance;QHash<QString, QPluginLoader *>m_loaders; //插件路径--QPluginLoader实例};#endif // PLUGINMANAGER_H#include "pluginmanager.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QCoreApplication>#include"PluginInterface.h"PluginManager* PluginManager::m_instance=nullptr;PluginManager::PluginManager(){}PluginManager *PluginManager::getinstance(){if(m_instance==nullptr)m_instance= new PluginManager();return m_instance;}void PluginManager::loadAllPlugins(){QString pluginPath = QCoreApplication::applicationDirPath() + "/plugins";QStringList filters;#if defined(Q_OS_LINUX)filters << "*.so";#elif defined(Q_OS_WIN)filters << "*.dll";#endifQFileInfoList plugins = QDir(pluginPath).entryInfoList(filters,QDir::Files | QDir::NoSymLinks);for (int i = 0; i < plugins.count(); ++i){QFileInfo plugin = plugins.at(i);QPluginLoader *loader = new QPluginLoader(this);loader->setFileName(plugin.absoluteFilePath());qDebug()<<loader->metaData().keys();QJsonObject json = loader->metaData().value("MetaData").toObject();QVariant var = json.value("name").toVariant();bool result = loader->load();qDebug() << QString("load plguin %1 result: ").arg(plugin.absoluteFilePath()) << result;if (result){m_loaders.insert(plugin.absoluteFilePath(), loader);QObject *pluginInstance = loader->instance();PluginInterface *appInterface = qobject_cast<PluginInterface *>(pluginInstance);if (appInterface != nullptr){}}else{qDebug() << loader->errorString();}}}void PluginManager::unloadAllPlugins(){for(auto filepath : m_loaders.keys()){QPluginLoader *loader = m_loaders.value(filepath);//卸载插件,并从内部数据结构中移除if(loader->unload()){m_loaders.remove(filepath);delete loader;loader = nullptr;}}}QString PluginManager::datawrite(){QString str;for(auto filepath : m_loaders.keys()){QPluginLoader *loader = m_loaders.value(filepath);PluginInterface *Plugin = dynamic_cast<PluginInterface*>(loader->instance());if(Plugin)str = Plugin->datawrite();}return str;}

插件代码

#ifndef LOGPLUGIN_H#define LOGPLUGIN_H#include "logPlugin_global.h"#include "../PluginManager/PluginInterface.h"#include <QObject>class LOGPLUGIN_EXPORT LogPlugin: public QObject,public PluginInterface{Q_OBJECTQ_INTERFACES(PluginInterface)   //声明这个插件实现是基于哪个插件接口的Q_PLUGIN_METADATA(IID "org.qt-LogPlugin" FILE "LogPlugin.json")  //使用Q_PLUGIN_METADATA宏导出插件public:LogPlugin();virtual QString datawrite() override;};#endif // LOGPLUGIN_H#ifndef DBPLUGIN_H#define DBPLUGIN_H#include "DbPlugin_global.h"#include "../PluginManager/PluginInterface.h"#include <QObject>class DBPLUGIN_EXPORT DbPlugin : public QObject,public PluginInterface{public:   Q_OBJECTQ_INTERFACES(PluginInterface)   //声明这个插件实现是基于哪个插件接口的Q_PLUGIN_METADATA(IID "org.qt-DbPlugin" FILE "DbPlugin.json")  //使用Q_PLUGIN_METADATA宏导出插件public:DbPlugin();virtual QString datawrite() override;};#endif // DBPLUGIN_H

App代码

#include "dialog.h"#include "ui_dialog.h"#include <QDebug>#include <QPluginLoader>#include <QFileInfo>#include <QDir>#include <QPushButton>#include "../PluginManager/pluginmanager.h"Dialog::Dialog(QWidget *parent): QDialog(parent), ui(new Ui::Dialog){ui->setupUi(this);}Dialog::~Dialog(){delete ui;}void Dialog::on_pushButton_clicked(){PluginManager::getinstance()->loadAllPlugins();PluginManager::getinstance()->datawrite();PluginManager::getinstance()->unloadAllPlugins();}

插件通信

插件的通信通过插件管理器来管理,插件管理器转发插件的消息

  1. 在插件管理器的接口头文件增加一个插件间通信的结构体,增加插件发送消息和接收消息的纯虚函数

Q_DECLARE_METATYPE(Type)

作用:向Qt元系统注册一些非基本类型

这个宏能使Qt元系统的QMetaType知道Type类型,但前提是Type类型必须提供一个公有的默认构造函数、公有的默认拷贝构造函数和公有的默认析构函数

  1. 在插件重写插件管理器声明的接收函数插件管理器声明的发送函数声明为信号。

  1. 在插件管理器的加载插件处加上connect函数,是信号槽关联起来。其中把插件发送信号关联插件管理器的接收槽函数。

同时让插件管理器的接收槽函数转发给对应的插件

  1. 在app程序中调用调用插件的信号从而发送消息

完整代码参见:

链接:https://pan.baidu.com/s/1fWK_oSYQTRNip4FLnTOLwg

提取码:qc0t

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/801823.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络-运输层

运输层 湖科大计算机网络 参考笔记&#xff0c;如有侵权联系删除 概述 运输层的任务&#xff1a;如何为运行在不同主机上的应用进程提供直接的通信服务 运输层协议又称端到端协议 运输层使应用进程看见的好像是在两个运输层实体之间有一条端到端的逻辑通信信道 运输层为应…

导出加入域控的电脑清单

要导出加入域控的所有电脑清单&#xff0c;包括计算机名等基本信息&#xff0c;可以使用 PowerShell 中的 Get-ADComputer cmdlet。下面是一个基础示例脚本&#xff0c;用于从Active Directory (AD)中批量导出所有加入域的计算机名称及可能的其他相关信息&#xff0c;并将其保存…

嘉轩智能工业科技诚邀您参观2024第13届生物发酵展

参展企业介绍 自2005年成立以来&#xff0c;嘉轩一直致力于工业智能永磁滚筒的研发、制造及销售&#xff0c;具有十多年的从业经验&#xff0c;公司主营产品包括工业智能永磁滚筒、机电智能诊断、工业智能电机等&#xff0c;高效智能自驱动永磁滚筒为我公司目前主导产品&#x…

thinkphp6使用阿里云SDK发送短信

使用composer安装sdk "alibabacloud/dysmsapi-20170525": "2.0.24"封装发送短信类 发送到的短信参数写在env文件里面的 #发送短信配置 [AliyunSms] AccessKeyId "" AccessKeySecret "" signName"" templateCode"&…

第一部分 Vue讲解(代码版)

1.第一个vue实例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-w…

Android Activity 介绍

Activity Activity 是一个应用组件&#xff0c;用户可与其提供的屏幕进行交互&#xff0c;以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕&#xff0c;但也可小于屏幕并浮动在其他窗口之…

性能优化 - 你知道开发React项目中,可以做哪些性能优化吗

难度级别:中高级及以上 提问概率:75% 在React项目开发中,面临着比较大的问题就是组件更新以及重复渲染的问题,基于这两点,我们可以在日常开发工作中,可以通过以下几点,来提升React的性能,加快组件更新对比,避免过多的重复渲染问题。 …

刷题DAY48 | LeetCode 198-打家劫舍 213-打家劫舍II 337-打家劫舍III

198 打家劫舍&#xff08;medium&#xff09; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统…

Linux笔记-对xargs的理解(2023-12-13)

此篇记录在个人笔记库的时间是2023-12-13&#xff0c;公布到互联网上的时间是2024-04-09 这个命令最开始是在5年前看人用过&#xff0c;当时不太理解&#xff0c;最近又看到有人在用&#xff0c;突然间就通了&#xff0c;也查阅了点资料&#xff0c;记录下这个笔记&#xff1a…

吴恩达深度学习笔记:深层神经网络(Deep Neural Networks)4.5-4.8

目录 第一门课&#xff1a;神经网络和深度学习 (Neural Networks and Deep Learning)第四周&#xff1a;深层神经网络(Deep Neural Networks)4.5 为什么使用深层表示&#xff1f;&#xff08;Why deep representations?&#xff09; 第一门课&#xff1a;神经网络和深度学习 (…

CKA 基础操作教程(五)

Kubernetes Ingress 理论学习 Ingress 提供从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源所定义的规则来控制。 Ingress 资源示例&#xff1a; apiVersion: networking.k8s.io/v1 # 指定 Kubernetes 中使用的 API 版本 kind: Ingress # 指定对象…

c语言 :柔性数组与c/c++内存领域的划分

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一 柔性数组&#xff08;1&#xff09;什么是柔性数组1&#xff09;柔性数组的声明2&#xff09;柔性数组的特性 &#xff08;2&#xff09;柔性数组的使用 二 c/c内…

APx500音频分析仪硬件简介

两通道模拟输出&#xff0c;两通道或以上的模拟输入接口 线性编码数字音频接口&#xff08;AES/EBU,TOSLINK,SPDIF&#xff09;Linear PCM 脉冲密度调制码流&#xff08;需要APx-PDM选件支持&#xff09; Bluetooth蓝牙音频码流&#xff08;需APx-BT选件支持&#xff09; 最…

用二八定律分析零售数据,不就更直观了吗?

20%的商品贡献了80%的销售金额&#xff0c;你会不会想知道这些商品的销售金额、毛利、销售金额累计占比、毛利累计占比&#xff0c;会不会想知道这些商品在各个门店的销售表现&#xff1f;看是否能进一步提高销售金额&#xff0c;提高毛利。这样的报表该怎么做&#xff1f;奥威…

Ceph学习 -4.Ceph组件介绍

文章目录 1.Ceph组件介绍1.1 组件介绍1.2 流程解读1.2.1 综合效果图1.2.2 数据存储逻辑 1.3 小结 1.Ceph组件介绍 学习目标&#xff1a;这一节&#xff0c;我们从组件介绍、流程解读、小结三个方面来学习。 1.1 组件介绍 无论是想向云平台提供 Ceph 对象存储和 Ceph 块设备服务…

Xinstall助力提升用户体验:一键打开App用户页面

在移动互联网时代&#xff0c;App已经成为我们日常生活中不可或缺的一部分。然而&#xff0c;随着App数量的激增&#xff0c;如何让用户更便捷地打开和使用App&#xff0c;提升用户体验&#xff0c;成为了开发者和广告主们亟待解决的问题。此时&#xff0c;Xinstall作为国内专业…

[Linux][权限][粘滞位]详细解读,深入了解Linux权限

1.Linux权限的概念 Linux下有两种用户&#xff1a;超级用户(root)、普通用户 超级用户&#xff1a;可以在Linux系统下做任何事情&#xff0c;不受限制普通用户&#xff1a;在Linux下做有限的事情&#xff0c;可以有多个超级用户的命令提示符是**“#”&#xff0c;普通用户的命…

Linux中磁盘管理

一.磁盘管理的概括和简要说明 磁盘空间的管理&#xff0c;使用硬盘三步&#xff1a; &#xff08;1&#xff09;分区&#xff1a; &#xff08;2&#xff09;安装文件系统格式化 &#xff08;3&#xff09;挂载&#xff1a; 硬盘的分类&#xff1a; &#xff08;1&#x…

基于单片机自动增益放大电路0-3倍系统

**单片机设计介绍&#xff0c;基于单片机自动增益放大电路0-3倍系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机自动增益放大电路0-3倍系统是一个电子工程领域的创新设计&#xff0c;它结合了单片机技术和自动增益…

安卓开机启动流程

目录 一、整体框架二、流程代码分析2.1 Boot ROM2.2 Boot Loader2.3 Kernel层Kernel代码部分 2.4 Init进程Init进程代码部分 2.5 zygote进程zygote代码部分 2.6 SystemServer进程SystemServer代码部分 2.7 启动Launcher与SystemUI 三、SystemServices3.1 引导服务3.2 核心服务3…