qt plugins 插件框架

2023-11-02


看了很多相关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_OBJECT
      Q_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 = subdirs

SUBDIRS += \
    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 += target

CONFIG += 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_OBJECT
    Q_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_OBJECT

public:
    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();
    }
#endif
    pluginsDir.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_OBJECT

public:
    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));
    else
        return "";
}

//根据名称获得插件
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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

qt plugins 插件框架 的相关文章

  • Maven:无法解析的构建扩展

    我一直在谷歌上查看 没有任何内容真正指出这个问题 当我运行 mvn clean install 时 它返回以下错误 错误 无法解析的构建扩展 插件 org sonatype flexmojos flexmojos maven plugin
  • Qt GUI 编程设计

    我正在尝试创建一个 GUI 应用程序 主窗口 一个QMainWindow 包含 9 个固定大小的标签以及主窗口的大小 我尝试在没有 Qt GUI Designer 的情况下以编程方式制作它 该项目构建时没有错误 但我看不到主窗口上显示的任何
  • 如何获取 QIcon 的文件/资源​​路径

    假设我做了这样的事情 QIcon myIcon resources icon ico 我稍后如何确定该图标的路径 例如 QString path myIcon getPath 问题是 没有getPath 会员 我找不到类似的东西 但肯定有办
  • QT 中只获取文件而不获取目录?

    当我这样做时 QDir myDir home some location QStringList filesList myDir entryList 它返回该位置内的文件和目录 但我只想要文件 并且这些文件可以具有任意扩展名 有任何想法吗
  • Qt 支持在 QIcon 中为 SVG 着色

    看来 Qt 不支持 SVG 中路径标签上的描边 填充选项
  • MAC 上的 QT/C++ - 未设置应用程序图标

    我正在努力解决的奇怪问题 在与我的 pro QT 项目文件相同的文件夹中 我有一个 Resources myIcon png 我试图将其设置为我构建的应用程序的图标 在 OSX 上运行 我阅读了文档 它建议在 pro 文件中添加 ICON
  • 程序意外完成 - QT Creator

    我正在尝试使用 QT Creator 使用 QT 框架开发 GUI 控制台应用程序 我使用的是Windows XP 我安装了QT 4 8 3和mingw 两者均已安装 没有任何错误 然后我安装了QT Creator QT 版本 路径中的 Q
  • Qt Creator:如何区分 win32 和 win64

    我必须在 pro 文件中执行类似的操作 win32 LIBS L 3rdparty libusb win32 lib msvc llibusb else win64 LIBS L 3rdparty libusb win32 lib msvc
  • XcodeColors 在 XCode 5 中不起作用

    我尝试安装XcodeColors在 XCode 5 中 但不幸的是 它不能与我从 XCode 4 6 获得的旧插件一起使用 下一步 我检查了 github 网站 在那里我看到了以下拉取请求 提供了 XCode 5 的工作版本 https g
  • Qt 创建者 + MITK (Linux)

    我正在尝试使用MITK 与 Qt Creator 我已经通过 ccmake 成功编译并使用了 VTK 和 ITK 我已经编译了 MITK超级建造模式 它下载 CTK VTK ITK 等 然后我就配置好了 我已经用 make 编译了 大约两个
  • 如何在带有预编译头的项目中使用google protobuf

    我有一个包含多个项目的解决方案 我的项目 但不是全部 使用预编译头 我决定使用 protobuf 但遇到了一个问题 在 protoc exe 从 proto 生成 pb h 后 我尝试包含标头并收到错误 预编译标头未包含在 pb h 中 我
  • 加权 Voronoi 的 CGAL 2D APOLLONIUS 图 - 如何生成和获取面和顶点?

    我正在尝试根据阿波罗尼乌斯图生成加权沃罗诺伊 我正在使用 CGAL 库 我找不到如何从 apollonius 获取面和顶点的好例子 我有以下类型定义 typedef double NT typedef CGAL Cartesian lt N
  • 有没有人有 Ruby 和 Rake 的 Notepad++ 函数列表插件的解析规则

    我使用 Notepad 编辑 rake 文件 并且希望能够使用函数列表插件 我无法在线找到任何解析规则 并且 语言解析规则 对话框没有非常清晰的记录 我正在将方法解析到以下列表中 但还想显示任务 Function Begin t def t
  • 为 Windows 98 编译 Qt

    我需要支持 Windows 98 Qt 文档声称这是可能的 但没有说明 Qt 4 6 的分布式二进制文件不能在 Win98 上运行 而且我采样的大多数 Qt 应用程序也不能在 Win98 上运行 对于几个确实在 98 上运行的应用程序 我询
  • QToolButton:更改菜单位置

    使用菜单时QToolButton菜单显示在按钮的正下方 有没有办法在按钮的左侧 右侧显示菜单 我知道这个问题不久前已得到回答 但我想为此问题添加新答案 因为接受的答案不再有效 实际上 更改 QToolButton 上的菜单位置非常容易 您需
  • 如何在 QTabWidget Qt 中展开选项卡

    我有一个QTabWidget像这个 但我想展开选项卡以 填充 整个小部件宽度 如下所示 我怎样才能做到这一点 我在用Qt 5 3 2 and Qt 创建者 3 2 1 Update 我尝试使用setExpanding功能 ui gt myT
  • Qt 布局,在小部件大小更改后调整到最小大小

    基本上我有一个QGridLayout里面有一些小部件 最重要的是 2 个标签 我用它们将图像绘制到屏幕上 好吧 如果用户愿意 他可以更改传入图像的分辨率 从而强制标签调整大小 我们假设标签的初始大小是320x240 用户将 VideoMod
  • Qt:测量事件处理时间

    我想测量我的应用程序中的哪些事件在主线程中需要很长时间才能执行 阻塞 GUI 或者至少是否有任何事件花费的时间超过 比如说 10 毫秒 显然 我对需要很长时间的任务使用线程和并发 但有时很难在其他线程中放入的内容和可以保留在 GUI 中的内
  • QTimer 一点也不准确?

    运行在 Windows7 64 位机器上 具有非常强大的 CPU 8 核 16 线程 我使用 QTimer 以 50Hz 触发函数调用 但我最终得到了 30Hz 函数调用本身肯定需要不到 10 毫秒才能完成 整个过程发生在一个单独的线程中
  • 在 MacOS 终端上运行 ffmpeg [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我对 MacOS 相当陌生 我发现使用终端来获取信息并不容易ffmpeg和我在 Window 上一样正常运行 我有 ffmpeg 二进制文件ffmpe

随机推荐