QT学习笔记(18)——第一个Qt插件程序

前言

刚开始接触QT插件,急需一个小程序练练手,看一下,无奈找了许多,都发现最后的Demo都是要积分的,首先,声明,下面我新建的这个程序时不用积分的,并且,我会尽我所能,把这个程序讲明白。
同时感谢这个博主的这篇文章让我进行参考,整个程序也是基本参考于他。

Qt插件

Qt插件——先讲一点

对于QT插件,说到底,就是为了降低整个程序的冗余性,因为随着系统的日益庞大,各种模块之间耦合在一起,当修改其中一个模块时,其他模块也跟着一起受到影响。而且,有时候,我并不需要这些功能,而你也给我集成了,这不是浪费我内存吗?我们可以从Qt Create上面先看一下,你之间安装的时候所集成的一些插件。
首先,点击帮助->关于插件,你就可以看到这个了。
QT学习笔记(18)——第一个Qt插件程序_第1张图片
这个就是你当时安装的插件了。

Qt插件——再讲一点

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

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

本文主要是通过低阶API来创建Qt插件,并通过静态、动态两种方式来调用插件,即通过应用程序预留四则预算接口,通过插件来定义四则运算的简单例子。

正式开始创建程序

  1. 首先,先新建一个主程序,让我们来调用插件。我们这里的主程序完成的功能就是四则运算。
    首先,点击新建,先创建一个子目录项。
    QT学习笔记(18)——第一个Qt插件程序_第2张图片
    就会生成这样的一个东西:
    在这里插入图片描述
    然后,继续添加新子项目:
    QT学习笔记(18)——第一个Qt插件程序_第3张图片
    选择Application:
    QT学习笔记(18)——第一个Qt插件程序_第4张图片

命名:
QT学习笔记(18)——第一个Qt插件程序_第5张图片

接下来,我们就要开始创建插件项了。
同样,新建子项目,不过,这次选择的是C++库了。
QT学习笔记(18)——第一个Qt插件程序_第6张图片
注意,这里选择的是Qt Plugin,然后命名QT学习笔记(18)——第一个Qt插件程序_第7张图片
这里,路径记得更换一下,别给默认放在~App里面了,要放在最外面。直接放在Test那个的下面就好了,记住App和Lib这两个是独立的,可以剥离的,只是我们这里为了方便测试,放在了同一个项目的下面,让它一起运行。
QT学习笔记(18)——第一个Qt插件程序_第8张图片
类名及类信息的命名看下面:
QT学习笔记(18)——第一个Qt插件程序_第9张图片
然后,直接下一步就可以了,就可以看到这样了。
QT学习笔记(18)——第一个Qt插件程序_第10张图片
这样,我们就在一个工程下面弄了两个项目了,到时就可以直接运行了。
另一种插件是为了动态调用插件,整个创建方法也跟上面第一个插件的创建方法是一样的,只是不再需要。不过,你在这个插件的pro文件的设置就要稍微注意一下了,跟第一个是不一样的,具体不一样的地方,你看下下文就可以明白了。

调用插件的应用程序(App)

首先,在子项目EchoPluginApp中添加一个echointerface.h文件,在该头文件中定义插件接口,应用程序正是通过这些接口来实现额外的功能。
接下来,我们在Headers中新建一个头文件,用来放上面的接口。
QT学习笔记(18)——第一个Qt插件程序_第11张图片
首先,书写好这个接口程序:

//echoInterface.h
#ifndef ECHOINTERFACE_H
#define ECHOINTERFACE_H

#endif // ECHOINTERFACE_H
#include 
#include 
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界面就直接绘制成这样就可以了:
QT学习笔记(18)——第一个Qt插件程序_第12张图片
也可以用代码绘制界面,当然都可以。
首先,在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。

而这个时候,你如果想配置的是静态插件,那么你只需做三个修改:

  1. 修改plugin工程的pro文件,在config后面添加static配置,即:CONFIG += plugin static
  2. 修改EchopluginApp工程的pro文件,添加 LIBS += ./libplugin.a,即为EchopluginApp工程引入静态插件所对应的.a文件(gcc)或.lib文件(vs)。若文件不在当前目录下,则需指定具体路径。
  3. 在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 
#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 
#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
时间原因,可能有些许错误,欢迎指正~

参考文献

  1. Qt Plugin创建及调用

你可能感兴趣的:(QT,Qt,插件)