资料:Qt Creator快速入门

创建应用程序插件

创建一个插件时,要先创建一个接口,接口就是一个类,它只包含纯虚函数。插件类要继承自该接口。插件类存储在一个共享库中,因此可以在应用程序运行时进行加载。

创建一个插件包括以下几步:

  1. 定义一个插件类,它需要同时继承自 QObject 类和该插件所提供的功能对应的接口类;
  2. 使用 Q_INTERFACES() 宏在 Qt 的元对象系统中注册该接口;
  3. 使用 Q_PLUGIN_METADATA() 宏导出该插件;
  4. 使用合适的 .pro 文件构建该插件;

使用一个应用程序可以通过插件进行扩展要进行以下几步:

  1. 定义一组接口(只有纯虚函数的抽象类);
  2. 使用 Q_DECLARE_INTERFACE() 宏在 Qt 的元对象系统中注册该接口;
  3. 在应用程序中使用 QPluginLoader 来加载插件;
  4. 使用 qobject_cast() 来测试插件是否实现了给定的接口;

下面通过创建一个过滤字符串中出现的第一个数字的插件来学习应用程序插件的创建过程。这里需要创建两个项目,一个项目用来生成插件,即 dll 文件;另一个项目是测试程序,用来使用插件。

1.创建插件

  1. 创建插件类。新建空项目 Empty qmake Project,项目名称为 plugin,选择路径为 myplugin。建立好项目后向其中添加一个 C++ 类,类名为 RegExpPlugin,基类保持为空。

  2. 定义插件类。将 regexpplugin.h 文件中的内容更改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #ifndef REGEXPPLUGIN_H
    #define REGEXPPLUGIN_H
    #include <QObject>
    #include "regexpinterface.h"

    class RegExpPlugin : public QObject, RegExpInterface
    {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qter.Examples.myplugin.RegExpInterface"
    FILE "myplugin.json")
    Q_INTERFACES(RegExpInterface)
    public:
    QString regexp(const QString &message);
    };
    #endif // REGEXPPLUGIN_H

    为了使这个类作为一个插件,它需要同时继承自 QObject 和 RegExpInterface。RegExpInterface 是接口类,用来指明插件要时间的功能,其在 regexpinterface.h 文件中定义,这个文件在后面的测试程序项目中(该文件可以直接在插件项目中定义,后面测试程序拷贝一份即可)。Q_PLUGIN_METEDATA() 宏用于声明插件的元数据,其中必须知名 IID 标识符,标识符是一个字符串,必须保证它的唯一性;FILE 指定一个 JSON 格式的插件元数据文件,该参数是可选的,其命名一般使用项目名称即可,内容一般只包含一组大括号(那么插件元数据文件的作用是什么?)。这里还需要使用 Q_INTERFACES() 宏将这个接口注册到 Qt 的元对象系统中,告知 Qt 这个类实现了哪个接口。这里通过重写它来实现该插件具体的功能,就是将字符串中的第一个数字提取出来并返回。

    在目录 plugin 中新建一个 myplugin.json 文档,内容是一组大括号{}。

  3. 导出插件。将 regexpplugin.cpp 文件中的内容更改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include "regexpplugin.h"
    #include <QRegExp>
    #include <QtPlugin>
    QString RegExpPlugin::regexp(const QString &message)
    {
    QRegExp rx("\\d+");
    rx.indexIn(message);
    QString str = rx.cap(0);
    return str;
    }
  4. 更改项目文件。打开 plugin.pro 文件,将其内容更改如下:

    1
    2
    3
    4
    5
    6
    7
    TEMPLATE = lib
    CONFIG += plugin
    INCLUDEPATH += ../regexpwindow #如果在本项目中定义了 regexpinterface.h(建议),则注释掉
    HEADERS = regexpplugin.h
    SOURCES = regexpplugin.cpp
    TARGET = regexpplugin
    DESTDIR = ../plugins

    这里使用 “TEMPLATE=lib” 表明该项目要构建库文件;使用 “CONFIG += plugin” 告知 qmake 要创建一个插件;因为项目中使用了 regexpwindow 目录中的 regexpinterface.h 文件,所以将该目录的路径添加到 INCLUDEPATH 中(regexpwindow 是测试项目目录,如果直接在本项目中定义了 regexpinterface.h,则不需要包含此目录了);TARGET 指定了产生的 dll 文件的名字;最后,使用 DESTDIR 指定了生成的 dll 文件所在的目录。

    因为这个项目中使用了 regexpinterface.h 文件,而这个文件在另一个项目中,所以现在还无法构建该项目(如果项目已经定义了 regexpinterface.h 文件,则可以构建)。

2.使用插件扩展应用程序

  1. 新建 Qt Widgets 应用。项目名称为 regexpwindow,选择路径时仍选择前面建立的 myplugin 目录。基类选择 QWidget,类名保持 Widget 不变。建立完成后,向该项目中添加新文件,模板选择 C++ 头文件,名称为 regexpinterface.h。

  2. 定义接口。将 regexpinterface.h 文件的内容更改如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #ifndef REGEXPINTERFACE_H
    #define REGEXPINTERFACE_H
    #include <QString>

    class RegExpInterface
    {
    public:
    virtual ~RegExpInterface(){}
    virtual QString regexp(const QString &message) = 0;
    };

    Q_DECLARE_INTERFACE(RegExpInterface, "org.qter.Examples.myplugin.RegExpInterface")

    #endif // REGEXPINTERFACE_H

    在接口类中定义了插件要实现的函数,比如这里定义了 regexp() 函数,可以看到在前面的 RegExpPlugin 类中已经实现了该函数。这个类中只能包含纯虚函数。最后使用 Q_DECLARE_INTERFACE() 宏在 Qt 元对象系统中注册了该接口,其中第二个参数就是前面指定的 IID。

  3. 加载插件。先单机 widget.ui 文件进入设计模式,设计的界面如图所示。将其中显示“无”字的 Label 的 objectName 属性更改为 labelNum。然后进入 wiget.h 文件,先添加头文件 #inlcude “regexpinterface.h”,然后再 private 部分定义一个对象指针,再声明一个加载插件函数:

    1
    2
    RegExpInterface * regexpIntergace;
    bool loadPlugin();

    image-20211112115518432

    现在到 widget.cpp 文件中先添加头文件,包含:

    1
    2
    3
    #include <QPluginLoader>
    #include <QMessageBox>
    #include <QDir>

    然后再构造函数中调用加载插件函数,如果加载失败,则进行告警:

    1
    2
    3
    4
    5
    if(!loadPlugin()){
    QMessageBox::information(this, "Error", "Could not load the plugin");
    ui->lineEdit->setEnabled(false);
    ui->pushButton->setEnabled(false);
    }

    下面添加加载插件函数的定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    bool Widget::loadPlugin()
    {
    QDir pluginsDir("../plugins");
    foreach(QString fileName, pluginsDir.entryList(QDir::Files)){
    QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
    QObject *plugin = pluginLoader.instance();
    if(plugin){
    regexpIntergace = qobject_cast<RegExpInterface*>(plugin);
    if(regexpIntergace)
    return true;
    }
    }
    return false;
    }

    这里使用 QDir 类指定到存放 dll 文件的 plugins 目录,然后便利该目录,使用 QPluginLoader 类来加载插件,并使用 qobject_cast() 来测试插件是否时间了 RegExpInterface 接口。

    最后到设计模式,转到“过滤”按钮的 clicked() 信号对应的槽,更改如下:

    1
    2
    3
    4
    5
    void Widget::on_pushButton_clicked()
    {
    QString str = regexpIntergace->regexp(ui->lineEdit->text());
    ui->labelNum->setText(str);
    }

    这里就是使用了 regexpInterface 接口的 regexp() 函数来获取 lineEdit 不见输入字符串中第一个出现的数字,然后在 labelNum 中显示出来。

  4. 运行程序。先构建plugin项目,再运行 regexpwindow 项目。

    image-20211112120230955

到此为止,创建插件并在应用程序中使用插件的整个过程就介绍完了。这个例子是基于 Qt 的 Echo Plugin Example 示例程序的,可以在帮助中查看这个例子。Qt 中还有一个 Plug & Paint Example 演示程序,这是一个比较综合的使用插件扩展应用程序的例子。