Qt 插件开发 plugin 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程

时间:2021-02-22 00:36:45


一、简介:

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 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程

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

Qt 插件开发 plugin 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程

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

Qt 插件开发 plugin 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程

 主程序部分 Code

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

declareinterface.h

#ifndef DECLAREINTERFACE_H
#define DECLAREINTERFACE_H

#include <QObject>


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 <QMainWindow>
#include "declareinterface.h"
#include <QMessageBox>
#include <QDir>
#include <QPluginLoader>
#include <QDebug>

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<DeclareInterface*>(plugin);
if (nullptr!=m_pInterface)
return true;
}
}

return false;
}

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

Qt 插件开发 plugin 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程

注意:type 选择 Qt Plugin

Qt 插件开发 plugin 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程

Qt 插件开发 plugin 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程

修改 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 <QGenericPlugin>
#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 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程

 最后结果界面:

Qt 插件开发 plugin 超详细 有源码四、Qt扩展应用程序,插件创建和使用流程


小结:

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