一、简介:
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
名称 : MyPlugin 项目名这个没有关系;填你想叫的名子;下一步;下一步,完成
2.添加子项目, 添加一个正常 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 项目,这个用来生成插件
注意:type 选择 Qt Plugin
修改 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,看加载插件路径 是否正确;
最后结果界面:
小结:
另外其它 感觉这个 plugin 并不好用;很多框架使用的是 CTK 插件框架,这个有时间再讲;