Qt创建插件及使用

        本文使用“Qt Creator 6.0.1”和“Qt 6.2.2”完成插件创建及使用,主要有如下步骤:(1)创建子目录项目MyProject;(2)在子目录项目中创建应用程序项目MyApp;(3)在子目录项目中创建插件项目MyPlugin;(4)在项目MyApp中添加接口文件“MyInterfaces.h”;(5)在接口文件“MyInterfaces.h”中添加接口AnimalInterface;(6)在项目MyPlugin中实现接口;(7)在项目MyApp中使用插件。

1. 创建子目录项目MyProject

        在Qt Creator的主界面点击“文件”--“新建文件或项目”,在弹出的对话框中选择“其他项目”--“子目录项目”,如下图所示:

点击“Choose...”,指定项目名称(指定为MyProject)、项目创建路径(D:\QtTests),如下图所示:

点击“下一步”,在弹出的对话框中指定Kits(构建套件)(我选择的是“Desktop (x86_windows_msvc2019_pe_64bit) ”)

点击“下一步”

点击“完成& 添加子项目”按钮,完成子目录项目MyProject的创建。

紧接着,Qt Creator会自动弹出“新建子项目”对话框,这就进入了下一个步骤“在子目录项目中创建应用程序MyApp”

2. 在子目录项目中创建应用程序项目MyApp

在“新建子项目”对话框中选择“Application”--“Qt Widgets Application”

点击“Choose...”,指定项目名称(指定为MyApp)、项目创建路径(D:\QtTests),如下图所示:

点击“下一步”,指定构建系统(Build System)(我指定为qmake),

点击“下一步”,

无需修改,点击“下一步”

无需修改,点击“下一步”

我选择的是“Desktop (x86_windows_msvc2019_pe_64bit) ”,点击“下一步”

点击“完成”按钮,完成应用程序项目MyApp的创建。

3. 在子目录项目中创建插件项目MyPlugin

右键单击子目录项目MyProject,在弹出菜单中点击“新子项目...”

在弹出的对话框中选择“Library”--“C++ Library”

点击“Choose...”按钮,在弹出的对话框中指定名称(MyPlugin)和创建路径(D:\QtTests\MyProject),

点击“下一步”,指定构建系统(Build System)(我指定为qmake),

点击“下一步”,指定类型为“Qt Plugin”

点击“下一步”

点击“下一步”

点击“下一步”

点击“完成”按钮,完成插件项目MyPlugin的创建。

为了使得项目能够正常编译 ,可将函数GenericPlugin::create中的代码“static_assert(false, "You need to implement this function");”暂时修改为“return nullptr;”。

4. 在项目MyApp中添加接口文件“MyInterfaces.h”

右键单击项目MyProject,在弹出菜单中点击“Add New...”

在弹出的对话框中选择“C/C++”--“C/C++ Header File”

点击按钮“Choose...”,在弹出的对话框中指定文件名(“MyInterfaces.h”)、路径(“D:\QtTests\MyProject\MyApp”)

点击“下一步”

点击“完成”按钮,完成接口文件“MyInterfaces.h”的添加。Qt Creator会自动修改文件“MyApp.pro”,将HEADERS修改为:

HEADERS += \
    MyInterfaces.h \
    mainwindow.h

5. 在接口文件“MyInterfaces.h”中添加接口AnimalInterface

AnimalInterface即动物接口,该接口中就一个方法cry,这里cry意思是叫,大多数动物都会叫。

添加接口AnimalInterface的定义后,文件“MyInterfaces.h”的内容如下:

#ifndef MYINTERFACES_H
#define MYINTERFACES_H

#include <QtPlugin>

class AnimalInterface{
public:
    virtual ~AnimalInterface() = default;
    virtual int cry() = 0;
};

QT_BEGIN_NAMESPACE
#define AnimalInterface_iid "MyPlugin.AnimalInterface/1.0"
Q_DECLARE_INTERFACE(AnimalInterface, AnimalInterface_iid)

QT_END_NAMESPACE

#endif // MYINTERFACES_H

类AnimalInterface声明了一个纯虚函数cry(当然也可以声明更多的纯虚函数)。

该类还有一个虚拟析构函数。接口类通常不需要这样的析构函数(因为通过指向接口的指针删除实现接口的对象没有什么意义),但一些编译器会对声明虚拟函数但没有虚拟析构函数的类发出警告。我们提供析构函数来让这些编译器满意(即不发出警告)。(这段话来自参考资料[6],原文是: The class also has a virtual destructor. Interface classes usually don't need such a destructor (because it would make little sense to delete the object that implements the interface through a pointer to the interface), but some compilers emit a warning for classes that declare virtual functions but no virtual destructor. We provide the destructor to keep these compilers happy.)

‌QT_BEGIN_NAMESPACE的主要作用是定义一个命名空间,以避免不同库中的标识符冲突。通过使用QT_BEGIN_NAMESPACE和QT_END_NAMESPACE宏,Qt框架将所有的类和函数封装在一个特定的作用域内,从而防止它们与全局标识符或其他库中的标识符发生冲突。

为了在运行时可以查询某插件是否实现了给定的接口,我们必须使用Q_DECLARE_INTERFACE()宏。该宏的第一个参数是接口的名称;第二个参数是一个以唯一方式标识接口的字符串。按照惯例,我们使用“Java包名”语法来标识接口。若在后期更改接口,则必须使用不同的字符串来标识新接口;否则,应用程序可能会崩溃。因此,如上述代码所示,在字符串中加入版本号是一个好主意。(这段话来自参考资料[6],原文是:To make it possible to query at run-time whether a plugin implements a given interface, we must use the Q_DECLARE_INTERFACE() macro. The first argument is the name of the interface. The second argument is a string identifying the interface in a unique way. By convention, we use a "Java package name" syntax to identify interfaces. If we later change the interfaces, we must use a different string to identify the new interface; otherwise, the application might crash. It is therefore a good idea to include a version number in the string, as we did above.)

6. 在项目MyPlugin中实现接口

为了便于在项目MyPlugin的代码中找到接口定义,在文件“MyPlugin.pro”添加如下代码:

INCLUDEPATH += ../MyApp
DESTDIR = ../MyApp/Plugins

‌INCLUDEPATH指定项目要用到的头文件路径,从而避免在#include时需要提供完整的文件路径;DESTDIR宏用于指定生成的可执行文件(或库文件)的输出目录。

添加接口的实现类。这里,我们创建两个类:一个类是狗Dog,另一个类是猫Cat。这两个类都是接口AnimalInterface的实现类,在实现方法cry(叫)时,发出的声音不同,狗“汪汪”叫,猫“喵喵”叫。

(1)添加Dog类

右键单击项目“MyPlugin”,在弹出的菜单中点击“Add New...”

在弹出的对话框中,选择“C/C++”--“C++ Class”

点击按钮“Choose...”,在弹出的对话框中:指定类名(Class name)为Dog,基类(Base class)指定为“<custom>”--“AnimalInterface”,勾选“Include QObject”、“Add Q_OBJECT”。

点击“下一步”,

点击“完成”按钮,完成Dog类的添加。

“Dog.h”的初始代码如下:

#ifndef DOG_H
#define DOG_H

#include <QObject>

class Dog : public AnimalInterface
{
    Q_OBJECT
public:
    Dog();
};

#endif // DOG_H

向其中添加继承QObject及接口AnimalInterface的实现方法后,“Dog.h”的代码如下:

#ifndef DOG_H
#define DOG_H

#include <QObject>
#include "MyInterfaces.h"   //  添加

class Dog : public QObject,  //  添加
        public AnimalInterface
{
    Q_OBJECT
    Q_INTERFACES(AnimalInterface)   //  添加
public:
    Dog();
    virtual int cry();   //  添加
};

#endif // DOG_H
在“Dog.cpp”中,添加cry的实现代码,如下:
#include <QMessageBox>
int Dog::cry()
{
    QMessageBox::information(nullptr, "Dog", "WangWang!");
    return 1;
}

为了使用QMessageBox,需要将文件“MyPlugin.pro”中的“QT += gui”修改为“QT += gui widgets”。

(2)添加Cat类

添加Cat类的步骤与添加Dog类的步骤基本相同。

“Cat.h”的代码如下:

#ifndef CAT_H
#define CAT_H

#include <QObject>
#include "MyInterfaces.h"   //  添加

class Cat :  public QObject,  //  添加
        public AnimalInterface
{
    Q_OBJECT
    Q_INTERFACES(AnimalInterface)   //  添加
public:
    explicit Cat(QObject *parent = nullptr);
    virtual int cry();   //  添加

};

#endif // CAT_H

在“Cat.cpp”中,添加cry的实现代码,如下:

#include <QMessageBox>
int Cat::cry()
{
    QMessageBox::information(nullptr, "Cat", "MiaoMiao!");
    return 1;
}

(3)修改函数GenericPlugin::create的实现代码

代码修改为:

#include "dog.h"
#include "cat.h"
QObject *GenericPlugin::create(const QString &name, const QString &spec)
{
    //static_assert(false, "You need to implement this function");
    if(AnimalInterface_iid != name)
        return nullptr;
    if(spec == "Dog")
        return new Dog;
    else if(spec == "Cat")
        return new Cat;

    return nullptr;
}

7. 在项目MyApp中使用插件

为了简化处理,直接在main函数中使用插件。

对“main.cpp”进行修改,最终代码如下:

#include "mainwindow.h"

#include <QApplication>
#include "MyInterfaces.h"   //  添加
#include <QPluginLoader>   //  添加
#include <QDir>     //  添加
#include <QList>    //  添加
#include <QGenericPlugin>   //  添加
#include <QMessageBox>   //  添加

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ///  添加,开始
    QDir pluginDir; //  插件所在的目录
    pluginDir = QDir(QCoreApplication::applicationDirPath());
    QString strDir = pluginDir.absolutePath();
    pluginDir.cd("../Plugins");
    QList<QString> pluginFileNames;    //  插件文件名
    auto entryList = pluginDir.entryList(QDir::Files);
    for (const QString &fileName : entryList) {
        //使用QPluginLoader加载插件
        QPluginLoader loader(pluginDir.absoluteFilePath(fileName));
        QObject *plugin = loader.instance();
        if (plugin) {
            pluginFileNames.append(pluginDir.absoluteFilePath(fileName));
        }
    }
    //将插件实例pluginLoader.instance()转换为QGenericPlugin类型
    QGenericPlugin *genericPlugin = nullptr;
    QPluginLoader loader(pluginFileNames[0]);
    QObject *plugin = loader.instance();
    if (plugin) {
        genericPlugin = qobject_cast<QGenericPlugin *>(plugin);
    }
    if (genericPlugin) {    // 插件加载成功,可以使用插件
        // 使用 QGenericPlugin 的 create 函数来创建对象
        //  创建一个狗的实例,并调用函数cry
        AnimalInterface *pDog = qobject_cast<AnimalInterface *>
                (genericPlugin->create(AnimalInterface_iid, "Dog"));
        if(pDog)
            pDog->cry();
        //  创建一个猫的实例,并调用函数cry
        AnimalInterface *pCat = qobject_cast<AnimalInterface *>
                (genericPlugin->create(AnimalInterface_iid, "Cat"));
        if(pCat)
            pCat->cry();
    }else{
        //  插件加载失败
        QMessageBox::information(nullptr, "main", "插件加载失败!");
    }
    ///  添加,结束

    MainWindow w;
    w.show();
    return a.exec();
}

该程序首先使用QCoreApplication::applicationDirPath()获取可执行文件(“MyApp.exe”)所在的目录,然后进入该目录的上级目录的Plugins目录(因为文件“MyPlugin.pro”中有代码“DESTDIR = ../MyApp/Plugins”),然后获取Plugins目录中的所有文件名并保存在entryList中;然后使用QPluginLoader逐个文件加载,若成功,则将文件名的绝对路径保存到字符串列表pluginFileNames中;这里为了简化,只有一个插件,所以,随后再使用QPluginLoader加载第一个文件;将插件实例pluginLoader.instance()转换为QGenericPlugin类型genericPlugin;最后调用genericPlugin的create函数创建了一个狗的实例、一个猫的实例,并调用了每个实例的cry函数。

该程序运行的结果是先弹出一个狗叫的对话框,再弹出一个猫叫的对话框。

如下图所示:

   

8. 值得一提的参考资料

参考资料[6]是一个优秀的创建和使用Qt插件的例子,值得大家学习,参考资料[7]是这个例子的源代码网址。

9. 参考资料:

[1]使用QGenericPlugin和QPluginLoader创建和加载插件. 使用QGenericPlugin和QPluginLoader创建和加载插件-****博客 .

[2] QGenericPlugin Class. QGenericPlugin Class | Qt GUI 6.2.13 .

[3] QPluginLoader Class. QPluginLoader Class | Qt Core 6.2.13 .

[4] How to Create Qt Plugins. How to Create Qt Plugins | Qt 6.2 .

[5] Deploying Plugins (部署插件). Deploying Plugins | Qt 6.2 .

[6] Plug & Paint Example. Plug & Paint Example | Qt Widgets 6.2.13 .

[7] plugandpaint源代码. tools « widgets « examples - qt/qtbase.git - Qt Base (Core, Gui, Widgets, Network, ...) .   

上一篇:基于微信小程序的像素画创作与分享平台设计与实现


下一篇:【2024保研经验帖】东南大学计算机学院夏令营