文章目录

  • 一、插件
  • 二、插件和动态库的区别
  • 三、Qt中的插件
  • 四、Qt插件开发--程序结构
  • 五、Qt插件开发--主程序部分开发流程
  • 六、Qt插件开发--插件程序部分开发流程
  • 七、实例编写
  • 7.1、主程序部分
  • 7.2、插件程序部分
  • 八、插件使用


一、插件

插件是一种(遵循一定规范的应用程序接口编写出来的)程序,定位于开发实现应用软件平台不具备的功能的程序。
插件必须依赖于应用程序才能发挥自身功能,仅靠插件是无法正常运行的;相反地,应用程序并不需要依赖插件就可以运行,这样一来,插件就可以加载到应用程序上并且动态更新而不会对应用诚信度造成任何改变(热更新)。

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


二、插件和动态库的区别

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


三、Qt中的插件

Qt提供了两种API用于创建插件:

  • 一种是高阶API,用于扩展Qt本身的功能:例如自定义数据库驱动、图像格式、文本编码、自定义样式等等;
  • 一种是低阶API,用于扩展Qt应用程序;

支持静态和动态两种方式来调用插件。


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

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


五、Qt插件开发–主程序部分开发流程

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

六、Qt插件开发–插件程序部分开发流程

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

七、实例编写

7.1、主程序部分

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "declareinterface.h"
#include <QMessageBox>
#include <QDir>
#include <QPluginLoader>
#include <QDebug>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();

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

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"

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

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

bool Widget::loadPlugin()
{
    QDir pluginsDir(qApp->applicationDirPath());    //pluginsDir: "../build-xxx-debug/debug"
#if defined (Q_OS_WIN)
    if(pluginsDir.dirName().toLower() == "debug" ||
            pluginsDir.dirName().toLower() == "release") {
        pluginsDir.cdUp();  //pluginsDir: "../build-xxx-debug"
        pluginsDir.cdUp();  //pluginsDir: "../"
    }
#elif defined(Q_OS_MAC)
    if(pluginsDir.dirName() == "MacIOS) {
        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()<<__FUNCTION__<<pluginLoader.errorString();
        if(plugin) {
            m_pInterface = qobject_cast<DeclareInterface*>(plugin);
            if(m_pInterface) {
                return true;
            }
        }
    }
    return false;
}

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

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

接口类

#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

工程结构

qt creator集成java插件 qt插件开发框架搭建_加载


7.2、插件程序部分

#ifndef QTPLUGIN_H
#define QTPLUGIN_H

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

class QTPlugin : public QObject,public DeclareInterface
{
    Q_OBJECT
    Q_INTERFACES(DeclareInterface)
    Q_PLUGIN_METADATA(IID DeclareInterface_iid FILE "qtplugin.json")
public:
    explicit QTPlugin(QObject *parent = nullptr);
    int add(int a, int b);

signals:

};

#endif // QTPLUGIN_H

FILE 是可选的,并指向一个 Json 文件。Json 文件必须位于构建系统指定的包含目录之一中(在本工程中和.pro在同级目录下)。当无法找到指定的文件时,moc 会出现错误。如果不想为插件提供信息,当然不会有任何问题,只需保证 Json 文件为空就行

#include "qtplugin.h"

QTPlugin::QTPlugin(QObject *parent) : QObject(parent)
{

}

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

接口类

#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

工程文件(xxx.pro)

TEMPLATE = lib
CONFIG += plugin
QT += widgets

TARGET = qtplugin   #插件名称
DESTDIR = ../plugins    # 输出目录

EXAMPLE_FILES = qtplugin.json

HEADERS += \
    declareinterface.h \
    qtplugin.h

SOURCES += \
    qtplugin.cpp

工程结构

qt creator集成java插件 qt插件开发框架搭建_加载_02


八、插件使用

1、编译运行插件程序部分,根据xxx.pro的配置,会生成文件夹plugins

qt creator集成java插件 qt插件开发框架搭建_qt creator集成java插件_03

这个qtplugin.dll就是我们需要的插件,在主程序中通过函数loadPlugin()来加载它,在实际使用中,可以在程序中新建一个文件夹专门用来存放插件,然后更新程序只需要替换插件文件就可以了;

2、直接运行主程序部分,如果插件加载成功,则功能可正常使用;

qt creator集成java插件 qt插件开发框架搭建_qt_04


如果插件加载不成功,功能虽然无法正常使用,但是程序可以正常运行(PS:动态库不行)

qt creator集成java插件 qt插件开发框架搭建_#include_05