前言
刚开始接触QT插件,急需一个小程序练练手,看一下,无奈找了许多,都发现最后的Demo都是要积分的,首先,声明,下面我新建的这个程序时不用积分的,并且,我会尽我所能,把这个程序讲明白。
同时感谢这个博主的这篇文章让我进行参考,整个程序也是基本参考于他。
Qt插件
Qt插件——先讲一点
对于QT插件,说到底,就是为了降低整个程序的冗余性,因为随着系统的日益庞大,各种模块之间耦合在一起,当修改其中一个模块时,其他模块也跟着一起受到影响。而且,有时候,我并不需要这些功能,而你也给我集成了,这不是浪费我内存吗?我们可以从Qt Create上面先看一下,你之间安装的时候所集成的一些插件。
首先,点击帮助->关于插件,你就可以看到这个了。
这个就是你当时安装的插件了。
Qt插件——再讲一点
Qt提供了两种API用于创建插件:
- 一种是高阶API用于扩展Qt本身的功能:如自定义数据库驱动,图像格式,文本编码,自定义样式等等;
- 一种低阶API用于扩展Qt应用程序。
本文主要是通过低阶API来创建Qt插件,并通过静态、动态两种方式来调用插件,即通过应用程序预留四则预算接口,通过插件来定义四则运算的简单例子。
正式开始创建程序
- 首先,先新建一个主程序,让我们来调用插件。我们这里的主程序完成的功能就是四则运算。
首先,点击新建,先创建一个子目录项。
就会生成这样的一个东西:
然后,继续添加新子项目:
选择Application:
命名:
接下来,我们就要开始创建插件项了。
同样,新建子项目,不过,这次选择的是C++库了。
注意,这里选择的是Qt Plugin,然后命名
这里,路径记得更换一下,别给默认放在~App里面了,要放在最外面。直接放在Test那个的下面就好了,记住App和Lib这两个是独立的,可以剥离的,只是我们这里为了方便测试,放在了同一个项目的下面,让它一起运行。
类名及类信息的命名看下面:
然后,直接下一步就可以了,就可以看到这样了。
这样,我们就在一个工程下面弄了两个项目了,到时就可以直接运行了。
另一种插件是为了动态调用插件,整个创建方法也跟上面第一个插件的创建方法是一样的,只是不再需要。不过,你在这个插件的pro文件的设置就要稍微注意一下了,跟第一个是不一样的,具体不一样的地方,你看下下文就可以明白了。
调用插件的应用程序(App)
首先,在子项目EchoPluginApp中添加一个echointerface.h文件,在该头文件中定义插件接口,应用程序正是通过这些接口来实现额外的功能。
接下来,我们在Headers中新建一个头文件,用来放上面的接口。
首先,书写好这个接口程序:
//echoInterface.h
#ifndef ECHOINTERFACE_H
#define ECHOINTERFACE_H
#endif // ECHOINTERFACE_H
#include <QString>
#include <QtWidgets>
class EchoInterface
{
public:
virtual ~EchoInterface();
virtual QStringList CalculateType(void) const =0;//这种后面有 = 0的是纯虚函数,一定需要实现的。
virtual double Calculate(QString &type,double xvar, double yvar) =0;
#define EchoInterface_iid "EchoPluginTest.EchoPluginLib.EchoInterface"//定义这个iid的这个字符串,后面这位验证手段
Q_DECLARE_INTERFACE(EchoInterface, EchoInterface_iid)
#endif // ECHOINTERFACE_H
};
这里实现了两个函数,并且,为了能够在运行时查询插件是否实现给定的接口,我们必须使用宏Q_DECLARE_INTERFACE(),该宏的第一参数为接口类的名称,第二个参数是一个字符串,用于唯一标记该接口类。
接下来,我们就先把mainwindow.ui给弄好。
ui界面就直接绘制成这样就可以了:
也可以用代码绘制界面,当然都可以。
首先,在mainwindow的构造函数里,先把一些东西给初始化好。
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
xvar(0.0),
yvar(0.0),
curType(tr("")),
m_EchoInterface(nullptr)
{
ui->setupUi(this);
ui->calType->clear();
m_pluginItemList.clear();
loadPlugins();
}
然后,我们就可以开始为那些控件编写一些使用的函数了。
比如,那个计算的函数:
void MainWindow::on_calculate_clicked()
{
if(m_EchoInterface !=0 )
{
double value = m_EchoInterface->Calculate(curType, xvar, yvar);//调用根据输入值和计算的类型计算输出结果的函数
ui->resultVal->setValue(value);
}
}
以及接下来这个是非常重要的加载插件的函数。
void MainWindow::loadPlugins()
{
//静态调用插件
//在QPluginLoader::staticInstances()搜寻静态插件
foreach (QObject *plugin, QPluginLoader::staticInstances())//注意这里的plugin应为QObject的类型
{
AddToCombo(plugin);
}
//动态调用插件
QDir pluginDir(qApp->applicationDirPath());//E:\QT_Code\work_content\Day37_730\build-EchoPluginTest-Desktop_Qt_5_12_3_MSVC2015_64bit-Debug\EchoPluginApp\debug
if(pluginDir.dirName().toLower() == tr("debug") || pluginDir.dirName().toLower() == tr("release") )
{
pluginDir.cdUp();
pluginDir.cdUp();
pluginDir.cd("plugins");
}
//遍历当前 文件夹下文件
foreach (QString filename, pluginDir.entryList(QDir::Files))
{
QPluginLoader pluginloader(pluginDir.absoluteFilePath(filename));
QObject *plugin = pluginloader.instance();//遍历文件夹中的dll文件,将其加载到plugin之中
if(plugin != 0)
AddToCombo(plugin);//将该插件功能集成到combox之中,用于解决元程序的问题。
}
if(ui->calType->count() > 0)
{
on_calType_currentIndexChanged(0);
}
qDebug()<<"count: "<<ui->calType->count();
}
void MainWindow::AddToCombo(QObject *pplugin)
{
EchoInterface *eInterface = qobject_cast<EchoInterface *>(pplugin);
if(eInterface != 0)
{
QStringList typelist = eInterface->CalculateType();
foreach (QString ctype, typelist)//遍历typeList,将里面所有的内容都加入calType的这个控件之中
{
ui->calType->addItem(ctype);
PluginItem item;//PluginItem是一个文本和插件集成在一起的一个结构体
item.text = ctype;
item.plugin =pplugin;
m_pluginItemList.append(item);
}
}
}
这里有一个非常重要的概念:静态插件,和动态插件。
在Q_IMPORT_PLUGIN(PluginName)
: 这个宏向应用程序中导入名字为PluginName的插件,这个名字对应于Q_PLUGIN_METADATA() 所在类的类名。这个宏主要用来导入静态插件。而相对于的也就有动态插件,并且我们使用最多的就是动态插件。
动态插件 本质上仍然是一个dll,只不过我们在编写时根据Qt的要求将其配置成了插件,这样我们在使用时就可以通过QPluginLoader 来直接加载该dll,并调用其中的函数;并且,在定义插件时不需要写一堆的函数导出声明。
上面我们开发动态插件时说过,动态插件其实也是一个dll文件,同理,静态插件其实也就是一个lib文件
所以还有一个配置动态插件非常重要的步骤:
最后也是最重要的一步,就是通过.pro文件,将该项目配置成动态插件,如下:
QT += widgets
TEMPLATE = lib
CONFIG += plugin
HEADERS +=
plugin.h
SOURCES +=
plugin.cpp
DISTFILES +=
plugin.json
其中,TEMPLATE指明这是一个dll工程,不是一个exe工程;config就是用类配置该工程为插件的。
构建该工程,即可在磁盘上生成该插件对应的dll。
而这个时候,你如果想配置的是静态插件,那么你只需做三个修改:
- 修改plugin工程的pro文件,在config后面添加static配置,即:CONFIG += plugin static
- 修改EchopluginApp工程的pro文件,添加 LIBS += ./libplugin.a,即为EchopluginApp工程引入静态插件所对应的.a文件(gcc)或.lib文件(vs)。若文件不在当前目录下,则需指定具体路径。
- 在main() 函数前添加 Q_IMPORT_PLUGIN(Plugin),即导入静态插件。
然后,使用方法就如上面的程序所写的一样, 通过QPluginLoader的静态方法staticInstances()使用加载到当前工程的所有静态插件。我们只需通过遍历,找到我们所需要的特定类型的插件即可。
插件一(静态插件开发)
首先,整个插件的创建在前面就已经讲了。现在就直接讲程序了。
你插件创建完后。
首先,你在.pro文件要做好设置。
先贴出.pro文件里面的内容:
QT += core gui
TARGET = EchoPluginLib
TEMPLATE = lib #设置其为静态链接库函数
CONFIG += plugin static #设置其为静态插件
DESTDIR =../plugins#设置这些静态文件放置的地方
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
echoplugin.cpp
HEADERS += \
echoplugin.h
DISTFILES += EchoPluginLib.json
unix {
target.path = /usr/lib
INSTALLS += target
}
你特别需要注意的点是:
QT += core gui
TARGET = EchoPluginLib
TEMPLATE = lib #设置其为静态链接库函数
CONFIG += plugin static #设置其为静态插件
DESTDIR =../plugins#设置这些静态文件放置的地方
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
就这几行要特别需要注意。
然后,现在你就可以正式开始书写插件类了。
先贴出.h文件,我们在详细讲一下这个东西。
#ifndef ECHOPLUGIN_H
#define ECHOPLUGIN_H
#include <QObject>
#include "../EchoPluginApp/echointerface.h"
class EchoPlugin : public QObject, public EchoInterface//这个插件需要集成QObject和需要在这边进行实现的接口
{
Q_OBJECT
#if QT_VERSION >= 0x050000
Q_PLUGIN_METADATA(IID "EchoPluginTest.EchoPluginLib.EchoInterface" FILE "EchoPluginLib.json")
#endif // QT_VERSION >= 0x050000//使用Q_PLUGIN_METEDATA() 宏导出这个插件。
Q_INTERFACES(EchoInterface)//告诉Qt元对象系统这个插件实现了哪些接口。
public:
EchoPlugin(QObject *parent = 0);
virtual QStringList CalculateType() const override;
virtual double Calculate(QString &type,double xvar, double yvar)override;
};
#endif // ECHOPLUGIN_H
首先,你在头文件中要记得引入#include "../EchoPluginApp/echointerface.h"
这个接口文件,下面才能进行实现。
然后,在继承类的时候,应继承QObject类和接口类。比如这样class EchoPlugin : public QObject, public EchoInterface
接下来,写下这个宏:Q_PLUGIN_METADATA(IID "EchoPluginTest.EchoPluginLib.EchoInterface" FILE "EchoPluginLib.json")
设置这个文件的IID以及json文件的名字。
\\EchoPluginLib.cpp
#include "echoplugin.h"
EchoPlugin::EchoPlugin(QObject *parent) :
QObject(parent)
{
}
QStringList EchoPlugin::CalculateType() const
{
return QStringList()<< tr("Add")<<tr("Sub");
}
double EchoPlugin::Calculate(QString &type, double xvar, double yvar)
{
if(type == tr("Add"))
return xvar + yvar;
else if(type == tr("Sub"))
return xvar - yvar;
else return 0.0;
}
#if QT_VERSION < 0x050000
Q_EXPORT_PLUGIN2(EchoPluginLib, EchoPlugin)
#endif // QT_VERSION < 0x050000
这个.cpp文件还是比较容易理解的,直接重写那两个文件就可以了。
接下来,就是进行动态调用的另一个插件。
它的.pro文件为:
#-------------------------------------------------
#
# Project created by QtCreator 2017-10-17T11:35:15
#
#-------------------------------------------------
QT += core gui widgets
TARGET = EchoPluginLib2
TEMPLATE = lib
CONFIG += plugin #这里是标识动态链接库,没有static的话。
DESTDIR = ../plugins #注意这里是lib文件的输出目录
# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
calculateplugin.cpp
HEADERS += \
calculateplugin.h
DISTFILES += EchoPluginLib2.json
unix {
target.path = /usr/lib
INSTALLS += target
}
这里要注意与第一个插件的不同点在哪里。
TARGET = EchoPluginLib2
TEMPLATE = lib
CONFIG += plugin #这里是标识动态链接库,没有static的话。
基本不同点是在这里,CONFIG那里少了一个static 。要注意。
然后,其他好像差别也不是很大。直接看代码就可以了。
//calculateplugin.h
#ifndef CALCULATEPLUGIN_H
#define CALCULATEPLUGIN_H
#include <QObject>
#include "../EchoPluginApp/echointerface.h"
class CalculatePlugin : public QObject, public EchoInterface
{
Q_OBJECT
#if QT_VERSION >= 0x050000
Q_PLUGIN_METADATA(IID "EchoPluginTest.EchoPluginLib.EchoInterface" FILE "EchoPluginLib2.json")
#endif // QT_VERSION >= 0x050000
Q_INTERFACES(EchoInterface)
public:
CalculatePlugin(QObject *parent = 0);
virtual QStringList CalculateType() const override;
virtual double Calculate(QString &type, double xvar, double yvar) override;
};
#endif // CALCULATEPLUGIN_H
//calculateplugin.cpp
#include "calculateplugin.h"
CalculatePlugin::CalculatePlugin(QObject *parent) :
QObject(parent)
{
}
QStringList CalculatePlugin::CalculateType() const
{
return QStringList()<<tr("Multi")<<tr("Division");
}
double CalculatePlugin::Calculate(QString &type, double xvar, double yvar)
{
if(type == tr("Multi"))
return xvar * yvar;
else if(type == tr("Division"))
{
if(yvar == 0)return 0;
else return xvar / yvar;
}
else {
return 0.0;
}
}
#if QT_VERSION < 0x050000
Q_EXPORT_PLUGIN2(EchoPluginLib2, CalculatePlugin)
#endif // QT_VERSION < 0x050000
代码地址
Github地址:https://github.com/zwzchome/QT
时间原因,可能有些许错误,欢迎指正~