QT插件学习系列(一) 初识QtPlugin
1、 概述
为什么我们要学习插件化,其和 windows 导出 dll 有什么区别呢?
- 导出的动态库如果缺失,程序不能运行。但插件可以。
- 同一套代码,即可分别在 windows 下和 linux 下生成插件。
QT 本身提供两种插件支持,一种称为高级 API,一种称为低级 API。
- 高级API的作用是扩展 QT 程序本身,需要子类化 QT 提供的插件基类,例如现有的 QTSqlDriver,因此你可也以编写自己的 QTStyle 扩展 QT。
- 低级 API 的作用是扩展自己的程序,也就是动态库的形式,在windows下就是个dll。同时因为高级 API 基于低级 API 创建,因此掌握低级 API 用法,高级 API 的用法也不在话下。
QT 的插件加载需要用到类 QPluginloader,你可以编写支持任意功能的插件。如何创建一个插件和加载这个插件 QT Assist 中是这样描述的:
创建一个扩展应用程序的插件:
- 定义一组用于与插件对话的接口(仅具有纯虚拟函数的类)。(预留好接口,方便建立通信)。
- 接口内使用 Q_DECLARE_INTERFACE() 宏告诉qt的元对象系统有关接口的信息。
- 使用 QPluginLoader 加载插件。
- 使用 qobject_cast 检验插件是否实现了既定的接口(以防外部插件注入),转换成功即可得到插件实例。
插件编写具体步骤(代码编写):
- 声明插件类,并继承 QObject 和 实现上面提到的既定接口。
- 使用 Q_INTERFACES 告诉 QT 元对象系统有关这个接口的信息(虚方法)。
- 使用 Q_PLUGIN_METADATA 导出插件。(Q_PLUGIN_METADATA 是 QT5的宏,QT4 使用的是 Q_EXPORT_PLUGIN2)
- 编写合适的 .pro 文件。
2、 实例
一、 新建子目录项目 PluginApp
二、 在 PluginApp 下 新建 QWidget 项目,名为 Main
三、 右键 PluginApp 新建子项目 pluginA
四、 项目目录结构
五、 编写接口,在 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;
}
八、 运行
注:
1、Main 项目选择 QWidget GUI项目是有原因的,下篇再说是为什么。
2、windows 下生成的插件为 dll 后缀,linux 下生成的即为 .so后缀(下篇出 linux 测试结果)。