Qt 插件开发 plugin 超详细 有源码

一、简介:

Qt 插件化开发框架类似于前后端的微服务的场景,授权哪个微服务则前端可以使用哪个微服务,

插件就行硬件插卡一样,可以被随时删除、插入和修改,所以结构很灵活,容易修改,方便软件的升级和维护;

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

二、插件和动态库的区别

两者都是用于封装部分功能的实现,并降低模块代码耦合度。
插件
插件主要面向接口编程,无需访问.lib文件,热插拔、利于团队开发。即使在程序运行时.dll不存在,也可以正常启动,只是相应插件功能无法正常使用而已;
动态库
动态库需要访问.lib文件,而且在程序运行时必须保证.lib存在,否则无法正常启动;

三、Qt插件开发–程序结构

Qt的插件开发至少分为两部分:主程序部分和插件程序部分
主程序部分:定义插件的接口并提供插件的管理器用于管理插件的加载与使用;
插件程序部分:用于按照主程序中定义的插件接口来定义插件,最终实现插件的功能,并生成供主程序部分调用的插件;

四、Qt扩展应用程序,插件创建和使用流程 

主程序中的步骤如下:

  • 定义一组用于与插件通信的接口(只有纯虚函数的类)
  • 使用 Q_DECLARE_INTERFACE() 宏来告诉 Qt 元对象系统有关接口的情况
  • 在应用程序中使用 QPluginLoader 加载插件
  • 使用 qobject_cast() 来测试插件是否实现了指定的接口

Qt 应用程序的插件,步骤如下: 

  • 声明一个继承自 QObject 和插件想要提供的接口的插件类
  • 使用 Q_INTERFACES() 宏来告诉 Qt 元对象系统有关接口的情况
  • 使用 Q_PLUGIN_METADATA() 宏导出插件
  • 使用合适的 .pro 文件构建插件

五、示例

我使用的环境:Windows10 + VS2019 + Qt 5.15.2

1.创建文件或项目-》其它项目-》Empty qmake Project ----choose

Qt 插件开发 plugin 超详细 有源码_第1张图片

 名称 : MyPlugin  项目名这个没有关系;填你想叫的名子;下一步;下一步,完成

Qt 插件开发 plugin 超详细 有源码_第2张图片

2.添加子项目, 添加一个正常 Qt 项目,做为主项目

Qt 插件开发 plugin 超详细 有源码_第3张图片

 主程序部分 Code

第一步 ,新建接口头文件,定义一组用于与插件通信的接口(只有纯虚函数的类)

declareinterface.h

#ifndef DECLAREINTERFACE_H
#define DECLAREINTERFACE_H

#include 

 
class DeclareInterface
{
public:
    virtual ~DeclareInterface(){}
    virtual int add(int a, int b) = 0;
};

//一定是唯一的标识符
#define DeclareInterface_iid "Examples.Plugin.DeclareInterface"

QT_BEGIN_NAMESPACE
Q_DECLARE_INTERFACE(DeclareInterface,DeclareInterface_iid)
QT_END_NAMESPACE

#endif // DECLAREINTERFACE_H

第二步,接口头 添加到 .pro文件

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    mainwindow.cpp

HEADERS += \
    mainwindow.h \
    declareinterface.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

 第二步: mainwindow.h 添加加载函数和成员变量,添加用到的头文件

private:
    bool loadPlugin();  //加载插件
    DeclareInterface* m_pInterface = nullptr;   //获取插件类型

第四步: Ui 文件,添加三个 lineEdit,一个 add 按钮;

完整理

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include "declareinterface.h"
#include 
#include 
#include 
#include 

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void on_pushButton_clicked();
    
private:
    bool loadPlugin();  //加载插件
    DeclareInterface* m_pInterface = nullptr;   //获取插件类型
private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

mainwindow.cpp

注意加载库路径,和plugin 生成的路径是对应的;

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    if(!loadPlugin()) {
         QMessageBox::warning(this,"Error","Could not load the plugin");
     }
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_pushButton_clicked()
{
    int a = ui->a->text().toInt();
    int b = ui->b->text().toUInt();
    int r = -1;

    if(m_pInterface) {
        r = m_pInterface->add(a,b);
    }
    ui->c->setText(QString::number(r));
}

bool MainWindow::loadPlugin()
{
    QDir pluginsDir(qApp->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");
    foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
        QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
        QObject *plugin = pluginLoader.instance();

        qDebug() << "--->>>Lynn<<<---" << __FUNCTION__ << pluginLoader.errorString();
        if (plugin) {
            m_pInterface = qobject_cast(plugin);
            if (nullptr!=m_pInterface)
                return true;
        }
    }

    return false;
}

3.添加子项目, 添加一个正常lib 项目,这个用来生成插件

Qt 插件开发 plugin 超详细 有源码_第4张图片

注意:type 选择 Qt Plugin

Qt 插件开发 plugin 超详细 有源码_第5张图片

Qt 插件开发 plugin 超详细 有源码_第6张图片

修改 plugin  .pro 文件,将 主项目作为头文件加进来;设置生成 dll 目录;

INCLUDEPATH    += ../TestProject
DESTDIR         = ../TestProject/plugins


completed code

QT += gui

TEMPLATE = lib
CONFIG += plugin

CONFIG += c++17
INCLUDEPATH    += ../TestProject
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    calculatorplugin.cpp

HEADERS += \
    calculatorplugin.h

DISTFILES += myPlugin.json
DESTDIR         = ../plugins
# Default rules for deployment.
unix {
    target.path = $$[QT_INSTALL_PLUGINS]/generic
}
!isEmpty(target.path): INSTALLS += target

CONFIG += install_ok

修改头文件 calculatorplugin.h:

#ifndef CALCULATORPLUGIN_H
#define CALCULATORPLUGIN_H

#include 
#include "declareinterface.h"
class CalculatorPlugin :public QObject, public DeclareInterface
{
    Q_OBJECT
    Q_INTERFACES(DeclareInterface)

    Q_PLUGIN_METADATA(IID DeclareInterface_iid FILE "myPlugin.json")
 
public:
    explicit CalculatorPlugin(QObject *parent = nullptr);

  int add(int a, int b)  ;
};

#endif // CALCULATORPLUGIN_H

 calculatorplugin.cpp

#include "calculatorplugin.h"

CalculatorPlugin::CalculatorPlugin(QObject *parent)
    : QObject(parent)
{
}

int CalculatorPlugin::add(int a, int b)
{
    return a+b;
}

到此完成代码部分,构建两个子项目;点击运行;

如出现找到插件,可以debug  函数 loadPlugin,看加载插件路径 是否正确;

Qt 插件开发 plugin 超详细 有源码_第7张图片

 最后结果界面:

Qt 插件开发 plugin 超详细 有源码_第8张图片

参考:Qt插件化(Plugins)开发扩展应用程序_luoyayun361的博客-CSDN博客

小结:

另外其它 感觉这个 plugin 并不好用;很多框架使用的是 CTK 插件框架,这个有时间再讲;

源码 demo

https://download.csdn.net/download/q610098308/87419270

你可能感兴趣的:(QT,Qt,Plugin)