QT插件化系列(一) 初识QtPlugin

2,358 阅读3分钟

这是我参与8月更文挑战的第29天,活动详情查看:8月更文挑战

1、 概述

为什么我们要学习插件化,其和 windows 导出 dll 有什么区别呢?

  1. 导出的动态库如果缺失,程序不能运行。但插件可以。
  2. 同一套代码,即可分别在 windows 下和 linux 下生成插件。

QT 本身提供两种插件支持,一种称为高级 API,一种称为低级 API。

  1. 高级API的作用是扩展 QT 程序本身,需要子类化 QT 提供的插件基类,例如现有的 QTSqlDriver,因此你可也以编写自己的 QTStyle 扩展 QT。
  2. 低级 API 的作用是扩展自己的程序,也就是动态库的形式,在windows下就是个dll。同时因为高级 API 基于低级 API 创建,因此掌握低级 API 用法,高级 API 的用法也不在话下。

QT 的插件加载需要用到类 QPluginloader,你可以编写支持任意功能的插件。如何创建一个插件和加载这个插件 QT Assist 中是这样描述的:

创建一个扩展应用程序的插件:

  1. 定义一组用于与插件对话的接口(仅具有纯虚拟函数的类)。(预留好接口,方便建立通信)。
  2. 接口内使用 Q_DECLARE_INTERFACE() 宏告诉qt的元对象系统有关接口的信息。
  3. 使用 QPluginLoader 加载插件。
  4. 使用 qobject_cast 检验插件是否实现了既定的接口(以防外部插件注入),转换成功即可得到插件实例。

插件编写具体步骤(代码编写):

  1. 声明插件类,并继承 QObject 和 实现上面提到的既定接口。
  2. 使用 Q_INTERFACES 告诉 QT 元对象系统有关这个接口的信息(虚方法)。
  3. 使用 Q_PLUGIN_METADATA 导出插件。(Q_PLUGIN_METADATA 是 QT5的宏,QT4 使用的是 Q_EXPORT_PLUGIN2)
  4. 编写合适的 .pro 文件。

2、 实例

一、 新建子目录项目 PluginApp

图1

二、 在 PluginApp 下 新建 QWidget 项目,名为 Main

图2

三、 右键 PluginApp 新建子项目 pluginA

图3 图3.5

四、 项目目录结构

图4

五、 编写接口,在 Main 下新建头文件 interfaceplugin.h

#interfaceplugin.h

#ifndef INTERFACEPLUGIN_H
#define INTERFACEPLUGIN_H

#include <QString>
#include <QtPlugin>

//定义接口
class InterfacePlugin
{
public:
    virtual ~InterfacePlugin() {}
    virtual QString output(const QString &message) = 0;
};

#define InterfacePlugin_iid "Test.Plugin.InterfacePlugin"   // 唯一标识符

Q_DECLARE_INTERFACE(InterfacePlugin, InterfacePlugin_iid)

#endif

六、 加载插件

#main.cpp

#include "widget.h"
#include <QApplication>
#include <QDir>
#include <QPluginLoader>
#include "interfaceplugin.h"
#include <QObject>
#include <QDebug>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
//    Widget w;
//    w.show();

    //加载exe所在目录下  plugin文件夹的所有插件
        QDir path = QDir(qApp->applicationDirPath());
        path.cd("../plugins");
        foreach (QFileInfo info, path.entryInfoList(QDir::Files | QDir::NoDotAndDotDot))
        {
                QPluginLoader pluginLoader(info.absoluteFilePath());
                QObject *plugin = pluginLoader.instance();
                if (plugin)
                {
                    InterfacePlugin *app = qobject_cast<InterfacePlugin*>(plugin);
                    if (app)
                    {
                        app->output("i am a plugin");
                    }
                }
        }
    return a.exec();
}

七、 编写插件 .pro 文件、头文件、 cpp 文件

#pluginA.pro

TEMPLATE        = lib           #表示这个makefile是一个lib的makefile
CONFIG         += plugin        #应用程序是一个插件
TARGET          = pluginA       #生成插件的名称
DESTDIR         = ../plugins    #生成插件的目录

HEADERS += \
    pluginA.h

SOURCES += \
    pluginA.cpp

DISTFILES += \
    programmer.json             #插件描述文件

#this is pluginA.h

#ifndef PLUGINA_H
#define PLUGINA_H

#include <QObject>
#include <QtPlugin>
#include "../Main/interfaceplugin.h"

class PluginA : public QObject, public InterfacePlugin
{
    // programmer.json 插件的信息描述类
    Q_OBJECT
    Q_PLUGIN_METADATA(IID InterfacePlugin_iid FILE "programmer.json") // QT5.0 引入
    Q_INTERFACES(InterfacePlugin)
public:
    explicit PluginA(QObject *parent = 0);
    virtual  QString output(const QString &message) Q_DECL_OVERRIDE;
};
#endif // PLUGINA_H

"programmer.json" 为插件描述文件,会作为元数据被加载到插件中,可在需要的时候手动读取。其中包含了插件的基本信息,更重要的是包含了插件的依赖项,这些依赖项决定了插件的加载顺序,关于插件依赖解决下篇再讲。

{
    "author" : "qht",
    "date" : "2019/05/27",
    "name" : "pluginA",
    "version" : "1.0.0",
    "des" : "这是一个插件A,按此方法加载插件B、C等",
    "dependencies" : []
}

#this is pluginA.cpp

#include "pluginA.h"
#include <QtDebug>

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

QString PluginA::output(const QString &message)
{
    qDebug() << message + "插件A加载成功";
    return message;
}

八、 运行

VZGNtg.png

注:
1、Main 项目选择 QWidget GUI项目是有原因的,下篇再说是为什么。
2、windows 下生成的插件为 dll 后缀,linux 下生成的即为 .so后缀(下篇出 linux 测试结果)。

Demo 地址:
download.csdn.net/download/u0…
github.com/qht10030778…