Qt Plugin插件机制与实例

2023-11-03

1、Qt 插件机制

1.1、Qt 插件简介

插件是一种遵循一定规范的应用程序接口编写出来的程序,定位于开发实现应用软件平台不具备的功能的程序。插件与宿主程序之间通过接口联系,就像硬件插卡一样,可以被随时删除,插入和修改,所以结构很灵活,容易修改,方便软件的升级和维护。Qt 提供了两种API用于创建插件:一种是高阶 API,用于扩展 Qt 本身的功能,如自定义数据库驱动,图像格式,文本编码,自定义样式等;一种是低阶 API,用于扩展 Qt 应用程序。本文主要是通过低阶 API 来创建 Qt 插件,并通过静态、动态两种方式来调用插件。(以下都是 Qt5 的插件开发方式)

1.2、Qt 插件开发的流程

  1. 定义一个接口集(只有纯虚函数的类)。
  2. 用宏Q_DECLARE_INTERFACE()将该接口告诉 Qt 元对象系统
  3. 声明插件类,插件类继承自 QObject 和插件实现的接口。
  4. 用宏Q_INTERFACES()将插件接口告诉 Qt 元对象系统(在头文件中)。
  5. 用适当的 .pro 文件构建插件。

1.3、Qt 插件调用的流程

  1. 包含接口头文件(只有纯虚函数的类)。
  2. 应用程序中用QPluginLoader来加载插件。
  3. 用宏qobject_cast()来判断一个插件是否实现了接口。

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

2、插件开发实例

2.1、创建目录工程

创建目录工程,以放置 GUI 应用工程和插件工程,选择 “Other Project”->“Subdirs Project”,填写工程名称为 PluginApp,选择保存目录。

2.2、创建GUI应用工程

在 PluginApp 工程上右键选择 “New Subproject” 菜单项,选择创建一个 GUI 应用:

填写工程应用名称为 MainWindow:

填写主界面类的名称:

 

2.3、创建插件子工程

在 PluginApp 工程上右键选择 “New Subproject” 菜单项,选择创建一个空的 Qt 工程,名称为 EchoPlugin。

 

2.4、插件的实现

1. 定义一个接口集(只有纯虚函数的类)

在 MainWindow 应用增加一个接口 Echonterface.h。

#ifndef ECHOINTERFACE_H
#define ECHOINTERFACE_H

#include <QString>

// 1.定义一个接口集(只有纯虚函数的类)
class EchoInterface
{
public:
    virtual ~EchoInterface() {}
    virtual QString echo(const QString &message) = 0;
};

// 2.用宏Q_DECLARE_INTERFACE()将该接口告诉Qt元对象系统
QT_BEGIN_NAMESPACE
#define EchoInterface_iid "org.qt-project.Qt.Examples.EchoInterface"
Q_DECLARE_INTERFACE(EchoInterface, EchoInterface_iid)
QT_END_NAMESPACE
#endif

2. 声明插件类,插件类继承自 QObject 和插件实现的接口

EchoPlugin.pro工程文件内容如下:

TEMPLATE        = lib
CONFIG         += plugin
QT             += widgets
INCLUDEPATH    += ../MainWindow
HEADERS         = EchoPlugin.h
SOURCES         = EchoPlugin.cpp
TARGET          = $$qtLibraryTarget(echoplugin)
DESTDIR         = ../EchoPlugin

# install
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/tools/echoplugin/plugins
INSTALLS += target

CONFIG += install_ok  # Do not cargo-cult this!

在插件子工程中添加一个插件类 EchoPlugin,实现如下:

EchoPlugin.h 文件:

#ifndef ECHOPLUGIN_H
#define ECHOPLUGIN_H

#include <QObject>
#include <QtPlugin>
#include "EchoInterface.h"

// 3.声明插件类,插件类继承自QObject和插件实现的接口
class EchoPlugin : public QObject, EchoInterface
{
    // 3.用宏Q_INTERFACES()将插件接口告诉Qt元对象系统(在头文件中)
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.EchoInterface") // 宏需要声明通过对象实现的接口的IID,并引用一个包含插件元数据的文件
    Q_INTERFACES(EchoInterface)

public:
    QString echo(const QString &message) override; // 实现的接口:返回字符串消息
};

#endif

EchoPlugin.cpp 文件:

#include "EchoPlugin.h"

// 实现的接口:返回字符串消息
QString EchoPlugin::echo(const QString &message)
{
    return message;
}

2.5、GUI应用的实现

实现 MainWindow 主界面。

MainWindow.pro 文件:

QT += widgets

HEADERS    = Widget.h \
             EchoInterface.h
SOURCES    = Widget.cpp \
             main.cpp

TARGET     = echoplugin
QMAKE_PROJECT_NAME = MainWindow
win32 {
    CONFIG(debug, release|debug):DESTDIR = ../debug/
    CONFIG(release, release|debug):DESTDIR = ../release/
} else {
    DESTDIR    = ../
}

# install
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/tools/echoplugin
INSTALLS += target

CONFIG += install_ok  # Do not cargo-cult this!

Widget.h 文件:

#ifndef ECHODIALOG_H
#define ECHODIALOG_H

#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
#include <QMessageBox>
#include <QDir>
#include <QPluginLoader>
#include "EchoInterface.h"

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget();

private slots:
    void sendEcho();

private:
    void initUI(); // 初始化UI
    bool loadPlugin(); // 加载插件

    EchoInterface *m_pEchoInterface;
    QLineEdit *m_pLineEdit;
    QLabel *m_pLabel;
    QPushButton *m_pBtn;
};

#endif

Widget.cpp 文件:

#include "Widget.h"

Widget::Widget()
{
    // 初始化UI
    initUI();

    // 加载插件
    if (!loadPlugin()) {
        QMessageBox::information(this, "Error", "Could not load the plugin");
        m_pLineEdit->setEnabled(false);
        m_pBtn->setEnabled(false);
    }
}

void Widget::sendEcho()
{
    // 调用插件接口 - EchoPlugin::echo
    QString text = m_pEchoInterface->echo(m_pLineEdit->text());
    m_pLabel->setText(text);
}

// 初始化UI
void Widget::initUI()
{
    m_pLineEdit = new QLineEdit;
    m_pLabel = new QLabel;
    m_pLabel->setFrameStyle(QFrame::Box | QFrame::Plain);
    m_pBtn = new QPushButton(tr("Send Message"));

    connect(m_pLineEdit, &QLineEdit::editingFinished,
            this, &Widget::sendEcho);
    connect(m_pBtn, &QPushButton::clicked,
            this, &Widget::sendEcho);

    QGridLayout *m_pLayoutMain = new QGridLayout(this);
    m_pLayoutMain->addWidget(new QLabel(tr("Message:")), 0, 0);
    m_pLayoutMain->addWidget(m_pLineEdit, 0, 1);
    m_pLayoutMain->addWidget(new QLabel(tr("Answer:")), 1, 0);
    m_pLayoutMain->addWidget(m_pLabel, 1, 1);
    m_pLayoutMain->addWidget(m_pBtn, 2, 1, Qt::AlignRight);
    m_pLayoutMain->setSizeConstraint(QLayout::SetFixedSize);
}

// 加载插件
bool Widget::loadPlugin()
{
    bool ret = true;

    // 获取当前应用程序所在路径
    QDir pluginsDir(qApp->applicationDirPath());
    if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
        pluginsDir.cdUp();

    // 切换到插件目录
    pluginsDir.cd("plugins");
    // 遍历plugins目录下所有文件
    foreach (QString fileName, pluginsDir.entryList(QDir::Files))
    {
        QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
        QObject *plugin = pluginLoader.instance();
        if (plugin)
        {
            // 获取插件名称
            QString pluginName = plugin->metaObject()->className();
            if(pluginName == "EchoPlugin")
            {
                // 对插件初始化
                m_pEchoInterface = qobject_cast<EchoInterface *>(plugin);
                if (m_pEchoInterface)
                    ret =  true;
                break;
            }
            else
            {
                ret = false;
            }
        }
    }
    return ret;
}

Main.cpp文件:

#include <QtWidgets>
#include "Widget.h"
#include "EchoInterface.h"

int main(int argv, char *args[])
{
    QApplication app(argv, args);

    Widget window;
    window.show();

    return app.exec();
}

2.6、程序运行结果

查看构建目录,生成的插件文件存放在 plugins 目录下:

 

3、定位插件

Qt 应用程序将会自动感知可用的插件,因为插件都被存储在标准的子目录当中。因此应用程序不需要任何查找或者加载插件的代码。

在开发过程中,插件的目录是 QTDIR/plugins(QTDIR 是 Qt 的安装目录),每个类型的插件放在相应类型的目录下面。如果想要应用程序使用插件,但不想用标准的插件存放路径,可以在应用程序的安装过程中指定要使用的插件的路径,可以使用 QSettings,保存插件路径,在应用程序运行时读取配置文件。应用程序可以通过QCoreApplication::addLibraryPath()函数将指定的插件路径加载到应用程序中。

使插件可加载的一种方法是在应用程序所在目录创建一个子目录,用于存放插件。如果要发布和 Qt 一起发布的插件(存放在 plugins 目录)中的任何插件,必须拷贝 plugins 目录下的插件子目录到应用程序的根目录下。

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击费领取↓↓

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

Qt Plugin插件机制与实例 的相关文章

随机推荐

  • 仿真引擎-文献总结笔记一

    文章题目 A Simulation Engine for Stochastic Timed Petri Nets and Application to Emergency Healthcare Systems 文章结构 第二节介绍了随机时间
  • python基础语法

    一个将某进制数转换为十进制数字的方法 该某进制数需要是字符类型表示 Python 3 gt gt gt n input 12 gt gt gt int n 16 18 eval函数用法 gt gt gt a 7 gt gt gt b a g
  • IDEA中使用监听器

    一 被监听对象 二 监听器 三 将时间源和监听器绑定 package listener uilistener import javax swing import java awt event WindowEvent import java
  • python将输入的内容保存到一个新文件

    def file write file name f open file name w print 请输入内容 单独输入 w 保存退出 while True write some input if write some w f write
  • javapdf模板生成pdf_【第9期】springboot: pdf操作

    一 pdf模板制作 1 word编辑模板 2 转成pdf模板 这个比较简单 wps另存为pdf格式就行 3 编辑pdf模板字段 pdf的编辑一般的软件就不行了 需要下载一个专业软件Adobe Acrobat DC 网上有破解版的 也可以关注
  • 做为互联网人 你必须知道的一些IT类网站

    一 IT类网站 行业资讯 1 36氪 36kr com 让一部分人先看到未来 互联网创业服务提供商 2 雷锋网 www leiphone com 读懂智能 未来 科技信息与产品服务平台 3 i黑马 www iheima com 创业创新服务
  • bash 实现多线程执行多个命令

    正常代码 bin bash for i 0 i lt 5 i do sleep 3 echo i done done wait 等待前面的代码执行完成 多线程代码 bin bash for i 0 i lt 5 i do sleep 3 e
  • React基础(超级详细,有案例)

    源码地址 https github com BLKNjyty reactstudy 定义 用于构建用户界面的JavaScript库 1 发送请求获取数据 2 处理数据 3 操作DOM呈现页面 React只关注这个 简答的说 就是将数据渲染为
  • Learning from Imbalanced Data 非均衡数据学习问题

    本文分什么是非均衡数据 非均衡数据对算法的影响 怎样处理非均衡学习以及非均衡学习评估这四个方面进行叙述 在这里 正例或者星号代表多数类 负例或者圆圈代表少数类 1 非均衡问题 非均衡问题有多重形式 1 intrinsic 数据固有属性 数据
  • Oracle Redo log

    Redo Logs概述 The redo log records all changes made to data including both uncommitted and committed changes Oracle通过Redo来
  • 【千律】C++基础:List 链表循环删除元素,及其报错的解决方案

    报错原因 采用erase移除迭代器后 迭代器的值变为 572662307 无法作为迭代器继续运算 详细 当程序执行到 list int erase itor 时 满足条件的第一个元素被删除 从而导致 itor 指针被删除 使其不指向任何元素
  • 全新出品!阿里 P5 工程师~P8 架构师晋升路线揭秘

    阿里巴巴终于公开了从初级程序员到架构师的学习路线图 这里相对应的基本上就是从 P5 到 P8 的晋升体系 今天老师将会带着大家从初级程序员开始一点点分享整个晋升体系 职级 初级程序员 薪资 6 12K 开发年限 0 1 年 技术能力 能够理
  • 小程序开发:监听返回当前页面

    故事背景 小程序开发 需要判断进入当前页面是初次加载还是返回的操作 就分享一下卤煮的实现思路吧 实现原理 先上图 我们都知道 在创建page页面的时候 开发工具会默认帮我们把生命周期的钩子一起生成 卤煮就用到了下面这两个钩子函数 原理 页面
  • 改变Element-ui 主题颜色

    主题颜色的改变本质是改变 样式的color 文字 背景 描边 局部改变可以使用 样式穿透 普通css root gt gt gt el upload list item transition none important webkit tr
  • 产业区块链一周动态丨深圳龙华区与腾讯共建产业区块链联盟,新四板试水区块链...

    作者 邱祥宇 10 24 会议之后 产业区块链似乎成为了一种政治正确 全国26省将区块链写入政府工作报告 12省发布专项政策19件 一顿操作猛如虎 整个行业拿到融资的公司却只有17家 政策热火朝天 资本望而却步 是什么原因导致中间出现断层
  • mysql分布式实践 - keepalived 实现IP漂移

    前言 mysql 分布式尤其是主 主 复制架构中 也是实现了读写分离的 如果有一个主master 挂掉了 那么如何让用户无感知的将请求打到另外一个master上 keepalived 插件的IP漂移就可以实现 1 keepalived 原理
  • 利用ipv6,在windows和ipad上远程访问共享文件夹

    利用ipv6 在windows和ipad上远程访问共享文件夹 背景 之前说到用ipv6 解决了远程桌面连接问题 利用ipv6远程桌面 彻底解决校园网掉线带来的问题 那么问题又来了 我在实验室电脑下载了python学习视频 这个视频特别大 几
  • 帧间差分法、背景减法、光流场法简介

    概述 运动目标检测是指当监控场景中有活动目标时 采用图像分割的方法从背景图像中提取出目标的运动区域 运动目标检测技术是智能视频分析的基础 因为目标跟踪 行为理解等视频分析算法都是针对目标区域的像素点进行的 目标检测的结果直接决定着智能视觉监
  • 4个最实用最强大ChatGPT插件

    GPT的插件有很多 功能也很强大 这些插件是自定义模块 可以集成到为特定行业量身定制的 AI 聊天机器人中 包括电子商务 医疗保健 金融和教育 使用 ChatGPT 插件 您现在可以做的不仅仅是聊天 今天给大家分享4个经常使用的插件 希望能
  • Qt Plugin插件机制与实例

    1 Qt 插件机制 1 1 Qt 插件简介 插件是一种遵循一定规范的应用程序接口编写出来的程序 定位于开发实现应用软件平台不具备的功能的程序 插件与宿主程序之间通过接口联系 就像硬件插卡一样 可以被随时删除 插入和修改 所以结构很灵活 容易