Qt Plugin创建及调用

概述

插件是一种遵循一定规范的应用程序接口编写出来的程序,定位于开发实现应用软件平台不具备的功能的程序。插件与宿主程序之间通过接口联系,就像硬件插卡一样,可以被随时删除,插入和修改,所以结构很灵活,容易修改,方便软件的升级和维护。

Qt中的插件

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

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

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

框架自动搭建

我们利用QT Creator把应用程序和插件的运行框架搭建起来,在EchoPluginTest项目项目新建一个名为EchoPluginApp的应用程序,和一个名为EchoPluginLib的插件。

  1. 首先创建一个子目录项目,名为EchoPluginTest
    Qt Plugin创建及调用_第1张图片
    Qt Plugin创建及调用_第2张图片

  2. 创建应用程序EchoPluginApp
    Qt Plugin创建及调用_第3张图片

这里我们选择的是Qt Widgets Application,并命名为EchoPluginApp
Qt Plugin创建及调用_第4张图片

3.创建EchoPluginLib插件
Qt Plugin创建及调用_第5张图片
选择:Library –> C++库,并在“项目介绍和位置”选择类型为“Qt Plugin”,名称为“EchoPluginLib”
Qt Plugin创建及调用_第6张图片
在类信息中输入类名:“EchoPlugin”,基类默认为“QGenericPlugin”
Qt Plugin创建及调用_第7张图片

至此,基本框架自动生成完成,我们还需进一步对其进行修改。

应用程序中的插件接口创建

在子项目EchoPluginApp中添加一个echointerface.h文件,在该头文件中定义插件接口,应用程序正是通过这些接口来实现额外的功能。

class EchoInterface
{
public:
    virtual ~EchoInterface() {}

    virtual QStringList CalculateType(void) const =0;
    virtual double Calculate(QString &type,double xvar, double yvar) =0;
};
#define EchoInterface_iid "EchoPluginTest.EchoPluginLib.EchoInterface"
Q_DECLARE_INTERFACE(EchoInterface, EchoInterface_iid)

EchoInterface 类定义了两个纯虚函数,第一个函数CalculateType() 返回QStringList 用于标识插件中定义的运算类型,之所以采用 QStringList 是因为一个插件中可能存在多个运算类型。第二个函数Calculate(),用于根据传入的运算类型和运算参数xvar和yvar来返回计算值。
为了能够在运行时查询插件是否实现给定的接口,我们必须使用宏Q_DECLARE_INTERFACE(),该宏的第一参数为接口类的名称,第二个参数是一个字符串,用于唯一标记该接口类。

应用程序中插件接口的使用

在应用程序中,首先我们需要定义loadPlugins() 用于查找插件

void MainWindow::loadPlugins()
{
    //调用静态插件
    foreach (QObject *plugin, QPluginLoader::staticInstances())
        AddToCombo(plugin);

首先通过QPluginLoader::staticInstances() 函数寻找静态插件,并更新到界面中下拉控件中。

接下来,我们去加载动态插件:

    //动态调用插件
    QDir pluginDir(qApp->applicationDirPath());
    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();
        if(plugin != 0)
            AddToCombo(plugin);
    }

将变量pluginDir 设置到当前应用程序工作目录对应的Plugins目录下,并通过函数entryList 遍历该目录下所有的文件,对每个文件利用QPluginLoader 去尝试加载插件。通过QPluginLoader::instance() 函数去识别由插件返回的QObject对象,因为如果动态链接库不是qt插件或者编译版本不兼容则,函数将返回空指针。
当函数返回有效插件时,我们将其更新到下拉列表中去。

AddToCombo函数

void MainWindow::AddToCombo(QObject *pplugin)
{
    EchoInterface *eInterface = qobject_cast(pplugin);
    if(eInterface != 0)
    {
        QStringList typelist = eInterface->CalculateType();
        foreach (QString ctype, typelist)
        {
            ui->calType->addItem(ctype);
            PluginItem item;
            item.text = ctype;
            item.plugin =pplugin;
            m_pluginItemList.append(item);
        }
    }
}

对于每一个插件(不管是动态的,还是静态的),我们都利用qobject_cast() 函数去检验它的接口类,如果为EchoInterface接口则将插件的运算类型添加到下拉列表中去。

下拉列表选择变化响应槽函数

void MainWindow::on_calType_currentIndexChanged(int index)
{
    if(index >=0 && index < m_pluginItemList.size())
    {
        curType = m_pluginItemList.at(index).text;
        m_EchoInterface = qobject_cast(m_pluginItemList.at(index).plugin);
    }
    else
    {
        curType = tr("");
        m_EchoInterface = 0;
    }
}

计算按钮响应槽函数

void MainWindow::on_calculate_clicked()
{
    if(m_EchoInterface !=0 )
    {
        double value = m_EchoInterface->Calculate(curType, xvar, yvar);
        ui->resultVal->setValue(value);
    }
}

至此,简单的应用程序基本完成,下面可以通过预留的接口来开发具备各种运算功能的插件了。

插件开发

在开发插件之前,我们需要对Qt Creator自动生成的插件项目做一些修改,因为其是基于高阶应用生成的。
首先,在EchoPluginLib.pro 文件中的DESTDIR改为

DESTDIR =../plugins

其次,在EchoPluginLib.h 文件中将类EchoPlugin 从继承自public QGenericPlugin改为继承自public QObject, public EchoInterface,更改宏Q_PLUGIN_METADATA中IID参数为”EchoPluginTest.EchoPluginLib.EchoInterface”,并添加宏Q_INTERFACES,和重写接口类的虚函数。

class EchoPlugin : public QObject, public EchoInterface
{
    Q_OBJECT
#if QT_VERSION >= 0x050000
    Q_PLUGIN_METADATA(IID "EchoPluginTest.EchoPluginLib.EchoInterface" FILE "EchoPluginLib.json")
#endif // QT_VERSION >= 0x050000

    Q_INTERFACES(EchoInterface)

public:
    EchoPlugin(QObject *parent = 0);
    virtual QStringList CalculateType() const override;
    virtual double Calculate(QString &type,double xvar, double yvar)override;
};
  • Q_INTERFACES是必须的,用于告诉qt的meta-object compiler,插件接口的基础类,如果没有这个宏,那么在应用程序中,我们无法使用qobject_cast()去检测接口类。
  • Q_PLUGIN_METADATA用于导出该插件,必须包含插件的IID参数,和可选的参数(用于指向一个包含插件MetaData的Json文件)。

插件中重写函数的实现

QStringList EchoPlugin::CalculateType() const
{
    return QStringList()<< tr("Add")<"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;
}

至此,一个简单的插件就开发完成了。

编译运行

为了项目EchoPluginTest项目编译子项目的顺序(先EchoPluginLib后EchoPluginApp),我们打开EchoPluginTest.pro文件,将其中的SUBDIRS设置如下:

SUBDIRS += \
    EchoPluginLib \
    EchoPluginApp

编译并运行EchoPluginTest,应用程序EchoPluginApp中可使用插件EchoPluginLib中定义的加法、减法运算。
Qt Plugin创建及调用_第8张图片

插件的静态调用

如需静态调用插件,以上项目需做如下更改:
1. 在子项目EchoPluginLib中,打开EchoPluginLib.pro文件,在CONFIG后添加static:

CONFIG += plugin static

2.在子项目EchoPluginApp中,打开main.cpp文件,添加宏Q_IMPORT_PLUGIN,如

Q_IMPORT_PLUGIN(EchoPlugin)

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

3.在子项目EchopluginApp中,打开EchoPluginApp.pro文件中添加插件库。可在EchopluginApp通过右键,选择“添加库”–>“外部库”来自动添加。例如

win32: LIBS += -L$$PWD/../../***/plugins/ -lEchoPluginLib

INCLUDEPATH += $$PWD/../../***/plugins
DEPENDPATH += $$PWD/../../***/plugins

4.在子项目EchopluginApp项目中的loadplugins函数中使用QPluginLoader::staticInstances()函数来加载插件。

示例代码:EchoPluginTest
更多的资料可参考官网相关例子:Plug & Paint Example, Plug & Paint Basic Tools Example, Plug & Paint Extra Filters Example, Echo Plugin Example。

你可能感兴趣的:(Qt)