文章目录

  • 一、插件概念
    • 优点
  • 二、插件框架
    • 1. 插件框架要素
    • 2. 插件系统的构成
      • 主系统
      • 插件管理器
      • 插件
    • 程序流
  • 二、qt框架下的插件
    • 2.0 插件路径
    • 2.1 Qt提供了两个用于创建插件的API:
    • 2.2 通过插件使应用程序可扩展包括以下步骤:
    • 2.3 编写插件包括以下步骤:
    • 2.4 正确的插件框架系统
  • 一个最简单的完整的实例
  • 参考demo
  • 参考的博客

看了很多相关qt plugins的文章,现简单记录下

一、插件概念

插件(Plug-in,又称addin、add-in、addon或add-on,又译外挂)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。因为插件需要调用原纯净系统提供的函数库或者数据。很多软件都有插件,插件有无数种。例如在IE中,安装相关的插件后,WEB浏览器能够直接调用插件程序,用于处理特定类型的文件。

优点

其实插件的优点也是常说的设计模式的设计原则;
比如 易扩展、低耦合、热更新、面向接口等。对于大型系统来说,可以多人同时开发,互不干扰等优点。

插件都是关于接口的,以插件为基础的系统,其基本概念是:系统可以加载插件,但它不知道任何东西,并且通过一组定义良好的接口和协议与它们进行通信。

二、插件框架

1. 插件框架要素

要实现一个插件框架,需要考虑以下要素:

  • 如何注册插件

  • 如何调用插件

  • 如何测试插件 :框架要支持自动化测试:包括单元测试,集成测试。

  • 插件的生命周期管理
    插件的生命周期由插件框架控制,需要考虑以下问题:

    1. 插件的生命周期如何转换?
    2. 一旦插件的生命周期发生转变,引用此插件的类是否能得到通知。
  • 插件的管理和维护
    -对于插件框架而言,这属于基础功能。主要包括:

    1. 为插件提供名称、版本、状态等信息,并可以获取插件列表,记录插件的处理日志等。
    2. 提供插件加载、启动、停止、卸载等功能。
  • 插件的组装(附加考评要素)
    插件的组装是指可以灵活的将多个插件组装为一条链,然后链式的调用。

  • 插件的出错处理
    当插件在处理过程中发生错误时,最理想的结果是插件的调用停止,并记录相关的日志,另外的插件对此情况做出纠错处理(注意:不能影响插件框架和其他插件的正常运转)。

2. 插件系统的构成

插件系统,可以分为三部分:

主系统

通过插件管理器加载插件,并创建插件对象。一旦插件对象被创建,主系统就会获得相应的指针/引用,它可以像任何其他对象一样使用。

插件管理器

用于管理插件的生命周期,并将其暴露给主系统。它负责查找并加载插件,初始化它们,并且能够进行卸载。它还应该让主系统迭代加载的插件或注册的插件对象。

插件

插件本身应符合插件管理器协议,并提供符合主系统期望的对象。

实际上,很少能看到这样一个相对独立的分离,插件管理器通常与主系统紧密耦合,因为插件管理器需要最终提供(定制)某些类型的插件对象的实例。

程序流

框架的基本程序流,如下所示:

二、qt框架下的插件

2.0 插件路径

Qt应用程序自动知道哪些插件可用,因为插件存储在标准插件子目录中。正因为如此,应用程序不需要任何代码来查找和加载插件,因为Qt会自动处理插件。

在开发过程中,插件的目录是QTDIR/plugins(其中QTDIR是安装Qt的目录),每种类型的插件都位于该类型的子目录中,例如样式。如果希望应用程序使用插件,而不希望使用标准插件路径,请让安装过程确定要用于插件的路径,并保存路径(例如,通过使用QSettings),以便应用程序在运行时读取。然后,应用程序可以使用此路径调用QCoreApplication::addLibraryPath(),应用程序将可以使用您的插件。请注意,路径的最后一部分(例如,样式)无法更改。

如果希望插件可以加载,那么一种方法是在应用程序下创建一个子目录,并将插件放在该目录中。如果分发Qt附带的任何插件(位于插件目录中的插件),则必须将插件所在的插件下的子目录复制到应用程序根文件夹(即,不包括插件目录)。

2.1 Qt提供了两个用于创建插件的API:

  • 一个用于为Qt本身编写扩展的高级API:自定义数据库驱动程序、图像格式、文本编解码器、自定义样式等。

  • 用于扩展Qt应用程序的低级API。

例如,如果您想编写一个定制的QStyle子类,并让Qt应用程序动态加载它,那么可以使用更高级的API。

由于更高级别的API是在较低级别的API之上构建的,因此一些问题对两者都是常见的。

  • High level plugin

用来扩展qt本身

  • Low Level plugin

用来扩展你的appliction

详细内容可参考: https://doc.qt.io/qt-5/plugins-howto.html

2.2 通过插件使应用程序可扩展包括以下步骤:

  1. 定义一组用于与插件对话的接口(只有纯虚拟函数的类)。

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

  3. 在应用程序中使用QPluginLoader加载插件。

  4. 使用qobject_cast()测试插件是否实现了给定的接口。

2.3 编写插件包括以下步骤:

  1. 声明一个插件类,该类继承自QObject和该插件想要提供的接口。
  2. 使用Q_INTERFACES()宏告诉Qt的元对象系统有关接口的信息。
  3. 使用Q_plugin_METADATA()宏导出插件。

Q_PLUGIN_METADATA(IID IPerson_iid FILE "programmer.json") 用该宏导出插件,programmer.json文件描述插件的属性

{"author" : "wzx","date" : "2019/11/28","name" : "personPlugin","version" : "1.0.0","dependencies" : []
}
  1. 使用合适的 .pro 文件构建插件
TEMPLATE = lib
CONFIG += plugin

例如,以下是接口类的定义:

  class FilterInterface{public:virtual ~FilterInterface() {}virtual QStringList filters() const = 0;virtual QImage filterImage(const QString &filter, const QImage &image,QWidget *parent) = 0;};

下面是实现该接口的插件类的定义:

  #include <QObject>#include <QtPlugin>#include <QStringList>#include <QImage>#include <plugandpaint/interfaces.h>class ExtraFiltersPlugin : public QObject, public FilterInterface{Q_OBJECTQ_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface" FILE "extrafilters.json")Q_INTERFACES(FilterInterface)public:QStringList filters() const;QImage filterImage(const QString &filter, const QImage &image,QWidget *parent);};

2.4 正确的插件框架系统

推荐的插件系统应该是下面的工程结构

TEMPLATE = subdirsSUBDIRS += \MainApp \plugin1 \plugin2 \plugin3 \...

一个最简单的完整的实例

官方实例:C:\Qt\Qt5.14.2\Examples\Qt-5.14.2\widgets\tools\echoplugin

整体结构如图:

接口类的定义 echointerface.h

#ifndef ECHOINTERFACE_H
#define ECHOINTERFACE_H#include <QObject>
#include <QString>//! [0]
class EchoInterface
{public:virtual ~EchoInterface() = default;virtual QString echo(const QString &message) = 0;
};QT_BEGIN_NAMESPACE#define EchoInterface_iid "org.qt-project.Qt.Examples.EchoInterface"Q_DECLARE_INTERFACE(EchoInterface, EchoInterface_iid)
QT_END_NAMESPACE//! [0]
#endif

plugin.pro

#! [0]
TEMPLATE        = lib
CONFIG         += plugin
QT             += widgets
INCLUDEPATH    += ../echowindow
HEADERS         = echoplugin.h
SOURCES         = echoplugin.cpp
TARGET          = $$qtLibraryTarget(echoplugin)
DESTDIR         = ../plugins
#! [0]EXAMPLE_FILES = echoplugin.json# install
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/tools/echoplugin/plugins
INSTALLS += targetCONFIG += install_ok  # Do not cargo-cult this!

echoplugin.h

#ifndef ECHOPLUGIN_H
#define ECHOPLUGIN_H#include <QObject>
#include <QtPlugin>
#include "echointerface.h"//! [0]
class EchoPlugin : public QObject, EchoInterface
{Q_OBJECTQ_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.EchoInterface" FILE "echoplugin.json")Q_INTERFACES(EchoInterface)public:QString echo(const QString &message) override;
};
//! [0]#endif

echoplugin.cpp

#include "echoplugin.h"//! [0]
QString EchoPlugin::echo(const QString &message)
{return message;
}
//! [0]

主窗口 echowindow.h

#ifndef ECHODIALOG_H
#define ECHODIALOG_H#include <QWidget>#include "echointerface.h"QT_BEGIN_NAMESPACE
class QString;
class QLineEdit;
class QLabel;
class QPushButton;
class QGridLayout;
QT_END_NAMESPACE//! [0]
class EchoWindow : public QWidget
{Q_OBJECTpublic:EchoWindow();private slots:void sendEcho();private:void createGUI();bool loadPlugin();EchoInterface *echoInterface;QLineEdit *lineEdit;QLabel *label;QPushButton *button;QGridLayout *layout;
};
//! [0]

echowindow.cpp

#include "echowindow.h"#include <QCoreApplication>
#include <QDir>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QMessageBox>
#include <QPluginLoader>
#include <QPushButton>//! [0]
EchoWindow::EchoWindow()
{createGUI();setLayout(layout);setWindowTitle("Echo Plugin Example");if (!loadPlugin()) {QMessageBox::information(this, "Error", "Could not load the plugin");lineEdit->setEnabled(false);button->setEnabled(false);}
}
//! [0]//! [1]
void EchoWindow::sendEcho()
{QString text = echoInterface->echo(lineEdit->text());label->setText(text);
}
//! [1]//! [2]
void EchoWindow::createGUI()
{lineEdit = new QLineEdit;label = new QLabel;label->setFrameStyle(QFrame::Box | QFrame::Plain);button = new QPushButton(tr("Send Message"));connect(lineEdit, &QLineEdit::editingFinished,this, &EchoWindow::sendEcho);connect(button, &QPushButton::clicked,this, &EchoWindow::sendEcho);layout = new QGridLayout;layout->addWidget(new QLabel(tr("Message:")), 0, 0);layout->addWidget(lineEdit, 0, 1);layout->addWidget(new QLabel(tr("Answer:")), 1, 0);layout->addWidget(label, 1, 1);layout->addWidget(button, 2, 1, Qt::AlignRight);layout->setSizeConstraint(QLayout::SetFixedSize);
}
//! [2]//! [3]
bool EchoWindow::loadPlugin()
{QDir pluginsDir(QCoreApplication::applicationDirPath());
#if defined(Q_OS_WIN)if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")pluginsDir.cdUp();
#elif defined(Q_OS_MAC)if (pluginsDir.dirName() == "MacOS") {pluginsDir.cdUp();pluginsDir.cdUp();pluginsDir.cdUp();}
#endifpluginsDir.cd("plugins");const QStringList entries = pluginsDir.entryList(QDir::Files);for (const QString &fileName : entries) {QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));QObject *plugin = pluginLoader.instance();if (plugin) {echoInterface = qobject_cast<EchoInterface *>(plugin);if (echoInterface)return true;pluginLoader.unload();}}return false;
}
//! [3]

main.cpp

#include <QApplication>#include "echowindow.h"
#include "echointerface.h"//! [0]
int main(int argv, char *args[])
{QApplication app(argv, args);EchoWindow window;window.show();return app.exec();
}
//! [0]

效果:

参考demo

参考1 :良好结构的插件系统:
https://download.csdn.net/download/u011370855/10699687

参考2:定义了插件间的通信结构

下载地址:https://download.csdn.net/download/kenfan1647/12650208

#ifndef PLUGINMANAGER_H
#define PLUGINMANAGER_H#include <QObject>
#include <QHash>
#include "PluginInterface.h"class QPluginLoader;class PluginManager : public QObject
{Q_OBJECTpublic:static PluginManager *instance(){if(m_instance == nullptr)m_instance = new PluginManager();return m_instance;}void loadAllPlugins();void loadPlugin(const QString &filepath);void unloadPlugin(const QString &filepath);void unloadAllPlugins();QPluginLoader* getPlugin(const QString &name);QVariant getPluginName(QPluginLoader *loader);public slots:void recMsgfromPlugin(PluginMetaData metadata);private:explicit PluginManager(QObject *parent = nullptr);~PluginManager();QHash<QString, QPluginLoader *> m_loaders; //插件路径--QPluginLoader实例QHash<QString, QString> m_names; //插件路径--插件名称static PluginManager *m_instance;class GarbageCollector{~GarbageCollector(){if (PluginManager::instance()){delete PluginManager::instance();PluginManager::m_instance = nullptr;}}};static GarbageCollector gc;
};#endif // PLUGINMANAGER_H
struct PluginMetaData
{QString from;//消息来源QString dest;//消息目的地QString msg;QObject *object = nullptr;QJsonObject info = QJsonObject();
};
Q_DECLARE_METATYPE(PluginMetaData);//确保类型可以通过信号槽传递class PluginInterface
{public:virtual ~PluginInterface() {}virtual QString get_name() const = 0;virtual QString show_text() const = 0;virtual void recMsgfromManager(PluginMetaData) = 0;//接收到来自创建管理器的消息virtual void sendMsg2Manager(PluginMetaData)   = 0;//给插件管理器发消息
};Q_DECLARE_INTERFACE(PluginInterface,"org.galaxyworld.plugins.PluginInterface/1.0")

插件管理器代码

#include "pluginmanager.h"
#include <QPluginLoader>
#include <QDir>
#include <QDebug>PluginManager* PluginManager::m_instance;
PluginManager::PluginManager(QObject *parent) : QObject(parent)
{}PluginManager::~PluginManager()
{unloadAllPlugins();
}//加载所有插件
void PluginManager::loadAllPlugins()
{QDir pluginsdir(QDir::currentPath());pluginsdir.cd("debug");//打开文件夹pluginsdir.cd("plugins");QFileInfoList pluginsInfo = pluginsdir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot);//加载插件for(QFileInfo fileinfo : pluginsInfo){qDebug()<<fileinfo.absoluteFilePath();loadPlugin(fileinfo.absoluteFilePath());}
}//加载其中某个插件
void PluginManager::loadPlugin(const QString &filepath)
{if(!QLibrary::isLibrary(filepath))return;//加载插件QPluginLoader *loader = new QPluginLoader(filepath);QString plugin_name;if(loader->load()){PluginInterface *plugin = qobject_cast<PluginInterface *>(loader->instance());if(plugin){plugin_name = plugin->get_name();m_loaders.insert(filepath, loader);m_names.insert(filepath,plugin_name);qDebug()<<"插件名称:"<<plugin->get_name()<<"插件信息:"<<plugin->show_text();connect(loader->instance(),SIGNAL(sendMsg2Manager(PluginMetaData)),this,SLOT(recMsgfromPlugin(PluginMetaData)));}else{delete loader;loader = nullptr;}}else{qDebug()<<"loadPlugin:"<<filepath<<loader->errorString();}
}//卸载所有插件
void PluginManager::unloadAllPlugins()
{for(QString filepath : m_loaders.keys())unloadPlugin(filepath);
}void PluginManager::unloadPlugin(const QString &filepath)
{QPluginLoader *loader = m_loaders.value(filepath);//卸载插件,并从内部数据结构中移除if(loader->unload()){m_loaders.remove(filepath);delete loader;loader = nullptr;}
}//获取某个插件名称
QVariant PluginManager::getPluginName(QPluginLoader *loader)
{if(loader)return m_names.value(m_loaders.key(loader));elsereturn "";
}//根据名称获得插件
QPluginLoader *PluginManager::getPlugin(const QString &name)
{return m_loaders.value(m_names.key(name));
}void PluginManager::recMsgfromPlugin(PluginMetaData metadata)
{auto loader = getPlugin(metadata.dest);//目标插件if(loader){auto interface = qobject_cast<PluginInterface*>(loader->instance());;if(interface){interface->recMsgfromManager(metadata);//转发给对应插件}}
}

参考的博客

  1. https://blog.csdn.net/kenfan1647/category_9967854.html

  2. https://blog.csdn.net/liang19890820/article/details/78134253

  3. 基于Qt插件实现的项目:https://github.com/nitroshare/nitroshare-desktop/wiki

  4. Qt 插件框架:https://gitee.com/penghongbin/QFrameWork

  5. Pluma 迷你c++插件框架:http://pluma-framework.sourceforge.net/?page_id=17
    http://pluma-framework.sourceforge.net/?page_id=120

qt plugins 插件框架相关推荐

  1. 魔改 Qt Creator 插件框架(附源码)

    星标/置顶 公众号????,硬核文章第一时间送达! 几年前,我曾写过一些插件相关的文章,其中最成体系的当属 CTK 系列,一共 18 个章节,从 0 到 1 讲述了模块化编程在 C++/Qt 中的实际 ...

  2. qt 从零开始搭建插件框架

    系统设计 搭建插件框架的好处 略 分析 至少分为以下几个模块 需要至少一个界面插件,用于展示界面 需要一些功能插件,为界面提供所需功能,例如FTP功能,网络服务,数据库等等 需要一个插件用于管理其他插 ...

  3. Qt开发技术:Qt的动态静态插件框架介绍和Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105481285 长期持续带来更多项目与技术分 ...

  4. 在ASP.NET MVC应用中开发插件框架(中英对照)

    [原文] Developing a plugin framework in ASP.NET MVC with medium trust [译文] 在ASP.NET MVC应用中开发一个插件框架 I'v ...

  5. 初识OSGI.NET插件框架

    OSGI全称为Open Service Gateway Initiative,OSGI是一个开放服务规范,基于该平台可以使得很多人来共同使用和协作. 基于OSGI.NET的插件框架提供了模块化与插件化 ...

  6. android插件框架机制的选择,Android插件开发初探——基础篇

    Android插件开发初探 对于Android的插件化其实已经讨论已久了,但是市面上还没有非常靠谱成熟的插件框架供我们使用.这里我们就尝试性的对比一下Java中,我们使用插件化该是一个怎么样的流程,且 ...

  7. Qt Creator添加Qt Designer插件

    Qt Creator添加Qt Designer插件 添加Qt Designer插件 查找Qt Designer插件 在macOS上配置Qt Designer插件 匹配的构建密钥 添加Qt Design ...

  8. ctk编译linux,CTK插件框架学习5-插件间通信(Netlink实现热拔插监控)

    本章来写一个插件,插件功能为通过NETLINK读取linux系统中的hotplug信息,比如usb.SD卡.磁盘等设备的插拔事件产生的信息,将读到的信息通过插件间通信的方式发出. 1. eventad ...

  9. 一个.NET Core下的开源插件框架Pluginfactory

    插件模式历史悠久,各种中大型软件基本上都会实现插件机制,以此支持功能扩展,从开发部署层面,插件机制也可实现功能解耦,对于并行开发.项目部署.功能定制等都有比较大的优势. 在.NET Core下,一般我 ...

  10. arm qt mysql插件_Ubuntu下编译ARM平台Qt的MySQL插件

    最近需要将一个程序移植到arm平台上,程序调用了MySQL数据库,所以就牵扯到将MySQL数据库移植到ARM平台上面,所以在网上大量查阅资料.在baidu文库发现了一篇文档,是wlzxlc上传的文档名 ...

最新文章

  1. 关于Unity四元数相乘先后顺序的问题
  2. 自学MVC——添加一个控制器
  3. 哈希链表的原理及算法实现
  4. java内部类文件,Java内部类学习
  5. SpringMVC 的总结
  6. FusionCharts破解版导出图片步骤
  7. Moods of Norway扩大RFID系统使用范围,保证库存准确率
  8. Java:转换列表 String 到一个字符串
  9. 局域网访问虚拟机服务器桥接,虚拟机让局域网访问的方法---桥接模式
  10. 一个c3p0的数据库连接池的多线程测试
  11. extern ,extern C 与 __cplusplus
  12. ssl免费证书获取,并在nginx服务器上安装ssl证书,以及docker安装nginx需注意的细节。
  13. 6步讲解应对ESD基本方法
  14. android与后台交互,Android客户端与服务端交互
  15. 写论文需要使用一个Github上的模型取数据,具体要求在代码里
  16. 删除右键新建多余菜单
  17. 细胞器标记物丨FUS抗体高品质结果展示
  18. android 微信分享 源码,记录Android微信分享功能的吐槽与思考
  19. 矩阵键盘逐行扫描C语言,(原创)51单片机C语言程序设计--速学教程实例(入门篇)之矩阵键盘(逐行扫描法).pdf...
  20. Flink核心篇,四大基石、容错机制、广播、反压、序列化、内存管理、资源管理...

热门文章

  1. 使用GoodSync备份服务器文件
  2. RS422接线 z-tek RS232 TO RS485/RS422
  3. DUILIB相对位置修改为锚概念
  4. 视音频处理大神-雷霄骅
  5. Android 系统FaceDetector人脸识别检测,圆形相机预览框,截取图片中的人脸图片(宽高自定义),圆形图片显示。
  6. 软件安全测试都有哪些内容,如何选择软件安全测评机构
  7. phpQuery占用过多内存的解决方法
  8. python调节电脑音量_python如何调节音量大小
  9. matlab 计算hog特征,Matlab计算图像HOG特征
  10. Django官方文档