Qt插件化(Plugins)开发扩展应用程序

前言

在前一篇文章中介绍了插件的定义及Qt中插件的基本概念,那么这里通过一个最简单的demo来对Qt插件开发有一定的了解。

Qt扩展应用程序

要想使用插件来扩展应用程序,那么首先在主程序中的步骤如下:

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

编写扩展 Qt 应用程序的插件,步骤如下:

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

示例

我使用的环境:MacOS + Qt 5.12.3

这里只是为了演示插件的创建和使用,所以做了一个功能非常简单的代码。在插件中计算两个数据的和并返回结果到主工程中进行显示。

创建工程

首先,新建一个子目录项目,如下:
Qt插件化(Plugins)开发扩展应用程序_第1张图片

项目名称为PluginDemo

创建好后会提示创建一个子项目,这里直接创建一个最简单的widget主工程项目,并命名为MainWindow

Qt插件化(Plugins)开发扩展应用程序_第2张图片 Qt插件化(Plugins)开发扩展应用程序_第3张图片

接着,我们再创建一个插件的工程,右击主工程,添加子项目:

Qt插件化(Plugins)开发扩展应用程序_第4张图片

选择一个空项目添加:

Qt插件化(Plugins)开发扩展应用程序_第5张图片

OK,主工程和插件工程已创建完成。

定义通信接口

接下来在主工程中新建一个头文件,用于定义与插件通信的接口(只有纯虚函数的类)

#ifndef CALINTERFACE_H
#define CALINTERFACE_H

//定义接口
class CalInterface
{
public:
    virtual ~CalInterface() {}
    virtual int add(int a,int b) = 0;
};

#define CalInterface_iid "Examples.Plugin.CalInterface"

QT_BEGIN_NAMESPACE
Q_DECLARE_INTERFACE(CalInterface,CalInterface_iid)
QT_END_NAMESPACE

#endif // CALINTERFACE_H

为方面演示,这里只做了非常简单的加法运算,所以创建了一个纯虚函数接口add,这里的CalInterface_iid 宏定义字符串一定要是唯一的,然后使用宏Q_DECLARE_INTERFACE来声明该接口。

编写插件

插件通信接口写完了,然后开始来编写插件,在插件工程下创建一个新的类,同时继承于QObject和通信接口类CalInterface,如下:

#include 
#include 
#include "calinterface.h"

class CalPlugin : public QObject,public CalInterface
{
    Q_OBJECT
    Q_INTERFACES(CalInterface)
    Q_PLUGIN_METADATA(IID CalInterface_iid FILE "calplugin.json")
public:
    explicit CalPlugin(QObject *parent = nullptr);
    int add(int a,int b);
};
#include "calplugin.h"

CalPlugin::CalPlugin(QObject *parent) : QObject(parent)
{

}

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

注意,在头文件中一定要用Q_INTERFACES宏来声明接口类。
这里的calplugin.json是为插件提供插件信息的,并使用Q_PLUGIN_METADATA声明(实例化该对象的)插件的元数据,元数据是插件的一部分。

Q_PLUGIN_METADATA这个宏所在的类必须是默认可构造的。

FILE 是可选的,并指向一个 Json 文件。

Json 文件必须位于构建系统指定的包含目录之一中。当无法找到指定的文件时,moc 会出现错误。

如果不想为插件提供信息,当然不会有任何问题,只需保证 Json 文件为空就行。

修改插件类pro文件

由于插件编译出来也是dll的形式,所以我们需要在工程文件中将类型改成lib,以及其他的一些配置,如下:

TEMPLATE        = lib
CONFIG         += plugin
QT             += widgets
INCLUDEPATH    += ../MainWindow

TARGET          = $$qtLibraryTarget(calplugin)
DESTDIR         = ../plugins

EXAMPLE_FILES = calplugin.json

HEADERS += \
    calplugin.h

SOURCES += \
    calplugin.cpp

CONFIG += install_ok

主工程中调用插件

#include 
#include "calinterface.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

private:
    bool loadPlugin();
private:
    Ui::Widget *ui;
    CalInterface * m_pInterface = nullptr;
};

cpp文件


#include 

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;
}

void Widget::on_pushButton_clicked()
{
    int a = ui->lineEdit_a->text().toInt();
    int b = ui->lineEdit_b->text().toInt();
    int r = -1;
    if(m_pInterface)
      r = m_pInterface->add(a,b);
    ui->lineEdit_r->setText(QString::number(r));
}

bool Widget::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() << __FUNCTION__ << pluginLoader.errorString();
        if (plugin) {
            m_pInterface = qobject_cast(plugin);
            if (m_pInterface)
                return true;
        }
    }

    return false;
}

其中loadPlugin()函数就是用于加载本地的插件文件,以上是动态加载的方式实现。

编译运行

Qt插件化(Plugins)开发扩展应用程序_第6张图片

示例Demo

你可能感兴趣的:(Qt)