使用QGenericPlugin和QPluginLoader创建和加载插件

为什么使用Qt插件

根据官方文档介绍,Qt插件开发允许你通过动态加载和使用插件来扩展应用程序的功能。在应用程序中,我们可以事先定义好接口,把接口的具体实现交给插件去完成。这样做的好处是应用程序和插件可以分别开发,降低代码耦合度,实现代码模块化设计。当接口的具体实现需要修改时,我们不必重写编译应用程序,只要把修改编译后的插件dll文件拷贝到应用程序指定目录,即可完成功能升级。

如何创建Qt插件

QGenericPlugin是Qt提供的用于创建插件的基类。它具有一个create函数,用于根据插件名称创建接口实例。这个类允许你创建插件框架,并在其中实现插件用户逻辑。QGenericPlugin官方文档详见https://doc.qt.io/qt-6.2/qgenericplugin.html

如何加载Qt插件

加载Qt插件的方法之一,是使用QPluginLoader,它提供了插件加载机制,允许应用程序在运行时动态加载插件。QPluginLoader官方文档详见https://doc.qt.io/qt-6.2/qpluginloader.html

使用示例

接下来,我们Step by step演示如何使用QGenericPlugin和QPluginLoader创建和加载插件。示例代码测试环境为Windows 10,Qt Creator 11.0.2, Qt 6.2.4, MinGw 11.2.0 64-bit。

1. 创建工作目录

使用Windows资源管理器在“文档”下,创建一个MyApp文件夹,我们所有的代码测试均在该目录下编辑、调试和运行。

2. 打开Qt Creator 创建子目录项目

这里我使用了子目录项目,把主应用程序和插件两个分项目包含到一个.pro文件里,这样方便打开整个工程项目。

  • 选择“其它项目”,模板为子目录项目。
    在这里插入图片描述
  • 创建路径选择MyApp文件夹,项目名称Application。
    在这里插入图片描述
  • 选择构建套件,我使用的是Desktop Qt 6.2.4 MinGW 64-bit。
  • 完成&添加子项目
    在这里插入图片描述
3. 新建主应用程序项目App。

完成子目录创建后,自动弹出菜单,让你添加第一个子项目。这里我们开始创建主应用程序项目,我选择Qt Widgets应用模板。
在这里插入图片描述

  • 设置主应用程序的名称和路径。
    在这里插入图片描述
  • 这里我使用的是qmake。
    在这里插入图片描述
  • 选择QWidget基类,类名AppWidget,其它默认。
    在这里插入图片描述
  • 一路“下一步”,直到“汇总”界面,注意看一下是否作为子项目添加到Application.pro,然后“完成”。
    在这里插入图片描述
    到此为止,我们创建了Application子目录项目和App主应用程序项目,Qt Creator给出了默认模板的代码。
    在这里插入图片描述
4. 创建插件项目Plugin。

右键Application,点击新子目录…
在这里插入图片描述

  • 插件项目模板选择C++库。

在这里插入图片描述

  • 插件项目命名Plugin。
    在这里插入图片描述
  • 同样使用qmake
    在这里插入图片描述
  • 库的类型选择Qt Plugin,其它默认。

在这里插入图片描述
在这里插入图片描述

  • 一路“下一步”,直到汇总界面,注意看一下是否作为子项目添加到Application.pro项目中。
    在这里插入图片描述
  • 同样,完成后Qt Creator给出了默认的模板代码。
    在这里插入图片描述
    到此为止,我们已经创建了主程序和插件的框架,并把它们放到一个目录下统一管理。
5. 理解GenericPlugin类

一顿操作后,让我们稍微休息一下,喝点什么。
在开始编写代码之前,有必要先熟悉一下插件模板代码。查阅网上资料,我给出关键部分的注释。

#ifndef GENERICPLUGIN_H
#define GENERICPLUGIN_H

/* 这是包含 Qt 的 QGenericPlugin 类
 * 的头文件,它是 Qt 插件机制的一部分,
 * 提供了一些必要的基础设施来创建插件。
 * */
#include <QGenericPlugin>
/* 这个插件类继承自 QGenericPlugin,
 * 它是Qt插件机制的一部分。
 * */
class GenericPlugin : public QGenericPlugin
{
	/* 这个宏用于启用 Qt 的元对象系统,
	 * 它通常出现在所有继承自QObject
	 * 的类中,以支持 Qt 的信号和槽机制
	 * 以及其他元对象相关功能。
	 * */
    Q_OBJECT
    /* 这个宏用于定义插件的元数据,
     * 包括插件的 IID(Interface ID)
     * 和其他信息。在这里,IID 是 
     * QGenericPluginFactoryInterface_iid,
     * 这是 Qt 插件工厂接口的 IID。
     * FILE "Plugin.json" 部分指定了
     * 包含插件元数据的 JSON 文件的名称。
     * */
    Q_PLUGIN_METADATA(IID QGenericPluginFactoryInterface_iid FILE "Plugin.json")

public:
    explicit GenericPlugin(QObject *parent = nullptr);

private:
	/* 这是一个纯虚函数,必须在子类中实现。
	 * 在插件加载时,Qt插件系统会调用这个
	 * 函数来创建插件的实例。name 参数通常
	 * 是插件的名称,spec 参数是规格说明,
	 * 用于进一步区分不同的插件。
	 * */
    QObject *create(const QString &name, const QString &spec) override;	
};

#endif // GENERICPLUGIN_H

整个代码的目的是定义一个插件类,它可以通过插件工厂接口来创建。当你加载这个插件后,Qt 插件系统允许你在主应用程序中调用 create 函数,创建一个具体的插件实例,并返回给调用者。这允许你动态加载和使用插件,而无需在应用程序代码中硬编码每个插件的创建。

6. 创建接口

接下来我们要创建一个接口文件。接口在应用程序和插件之间的通信和集成中确实起着重要作用。通过定义清晰的接口,应用程序可以与插件进行交互,而不需要了解插件的内部实现细节。这有助于实现模块化、可扩展和可维护的应用程序设计。

  • 右键App,添加新文件,选择C++头文件模板,文件名Interface,添加到App.pro项目。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    以下是Interface.h代码,关键部分给出注释。
#ifndef INTERFACE_H
#define INTERFACE_H

#include <QGenericPlugin>

class Interface
{
public:
    virtual ~Interface() = default;
    virtual void doSomething(const QString &param) = 0;
};

QT_BEGIN_NAMESPACE
Q_DECLARE_INTERFACE(Interface, QGenericPluginFactoryInterface_iid)
QT_END_NAMESPACE

#endif // INTERFACE_H

这段代码定义了一个纯虚接口(Interface),它是一个纯虚类,没有实际的实现,只有一组虚函数声明。接口文件的目的是为了定义一个插件必须遵循的接口规范,以便插件和应用程序之间能够互操作。

#include <QGenericPlugin> 

这里包含了 Qt 的 QGenericPlugin 类的头文件,因为我们将使用该类的接口来创建插件。

virtual void doSomething(const QString &param) = 0;

接口声明了一个纯虚函数 doSomething,该函数需要一个QString参数,并且没有实际的实现。

Q_DECLARE_INTERFACE(Interface, QGenericPluginFactoryInterface_iid)

这个宏告诉 Qt 插件系统 Interface 类是一个插件接口,并将其关联到 QGenericPluginFactoryInterface_iid,这是插件工厂接口的 IID。这样,Qt 插件系统就知道 Interface 是一个插件接口,可以被用来创建插件。

7. 接口的实现

接下来,我们在插件项目中实现class Interface。既然GenericPlugin类是通过create函数给应用程序返回一个接口实例对象,那么我们就顺势而为,在插件项目里头创建一个MyPlugin类,并且让它继承QObject和Interface。
在这里插入图片描述
MyPlugin类的头文件myplugin.h里,记得#include “Interface.h”,并且使用宏Q_INTERFACES(Interface),向Qt插件系统声明MyPlugin类实现了Interface接口,以便主应用程序App可以在运行时识别和使用这个接口。在这个示例中,我们仅用qDebug()输出一行文本,表示插件已被正确加载并且运行正常。

Plugin.pro文件这里补充两行代码,以便MyPlugin类include到App项目里的"Interface.h",同时将编译后生成的插件dll文件输出到一个独立的目录Plugins下。

INCLUDEPATH += ../App
DESTDIR = ../App/Plugins

myplugin.h

#ifndef MYPLUGIN_H
#define MYPLUGIN_H

#include <QObject>
#include "Interface.h"

class MyPlugin : public QObject, public Interface
{
    Q_OBJECT
    Q_INTERFACES(Interface)
public:
    explicit MyPlugin(QObject *parent = nullptr);
    void doSomething(const QString &param) override;

signals:

};

#endif // MYPLUGIN_H

myplugin.cpp

#include "myplugin.h"

MyPlugin::MyPlugin(QObject *parent)
    : QObject{parent}
{

}

void MyPlugin::doSomething(const QString &param)
{
    qDebug() << "传入参数:" << param << ",插件功能执行完毕!";
}

接下来修改genericplugin.cpp,实现create函数。这里我们从简,忽略了name和spec参数,直接返回一个MyPlugin对象。

#include "genericplugin.h"
#include "myplugin.h"

GenericPlugin::GenericPlugin(QObject *parent)
    : QGenericPlugin(parent)
{
}

QObject *GenericPlugin::create(const QString &name, const QString &spec)
{
    //static_assert(false, "You need to implement this function");
    Q_UNUSED(name)
    Q_UNUSED(spec)
    return new MyPlugin();
}

至此,我们基本完成插件项目代码编写。

8. 加载和运行插件

在AppWidget类里添加一个私有成员函数pluginLoader,用来演示加载和验证插件功能是否正确实现。

pluginLoader()

void AppWidget::pluginLoader()
{
	//使用QPluginLoader加载插件
	//pluginLoader的参数要使用绝对路径文件名,否则会找不到Plugin.dll
    QPluginLoader pluginLoader("C:/Users/Simon/Documents/MyApp/build-Application-Desktop_Qt_6_2_4_MinGW_64_bit-Debug/App/Plugins/Plugin.dll");
    
    //将插件实例pluginLoader.instance()转换为QGenericPlugin类型
    QGenericPlugin *genericPlugin = qobject_cast<QGenericPlugin *>(pluginLoader.instance());
    
    if (genericPlugin) {
        // 插件加载成功,可以使用插件
        
        // 使用 QGenericPlugin 的 create 函数来创建插件对象
        QObject *pluginInstance = genericPlugin->create("", "");
        
        if (pluginInstance) {
            // 创建成功,可以使用插件实例
            
            // 尝试转换插件对象为MyPlugin类型
            Interface *myPlugin = qobject_cast<Interface *>(pluginInstance);
            
            if (myPlugin) {
                // 使用插件的方法
                myPlugin->doSomething("COM3");
            } else {
                // 无法将插件实例转换为你的插件类
            }
        } else {
            // 插件创建失败
        }
    } else {
        // 插件加载失败
    }
}

下面给出class AppWidget完整代码

appwidget.h

#ifndef APPWIDGET_H
#define APPWIDGET_H

#include <QWidget>
#include <QGenericPlugin>
#include <QPluginLoader>
#include <QDir>

QT_BEGIN_NAMESPACE
namespace Ui { class AppWidget; }
QT_END_NAMESPACE

class AppWidget : public QWidget
{
    Q_OBJECT

public:
    AppWidget(QWidget *parent = nullptr);
    ~AppWidget();

private:
    Ui::AppWidget *ui;
    void pluginLoader();
};
#endif // APPWIDGET_H

appwidget.cpp

#include "appwidget.h"
#include "ui_appwidget.h"
#include "Interface.h"

AppWidget::AppWidget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::AppWidget)
{
    ui->setupUi(this);
    pluginLoader();
}

AppWidget::~AppWidget()
{
    delete ui;
}

void AppWidget::pluginLoader()
{
    //使用QPluginLoader加载插件
    //pluginLoader的参数要使用绝对路径文件名,否则会找不到Plugin.dll
    //实际调试时,记得换成你自己的路径
    QPluginLoader pluginLoader("C:/Users/Simon/Documents/MyApp/build-Application-Desktop_Qt_6_2_4_MinGW_64_bit-Debug/App/Plugins/Plugin.dll");

    //将插件实例pluginLoader.instance()转换为QGenericPlugin类型
    QGenericPlugin *genericPlugin = qobject_cast<QGenericPlugin *>(pluginLoader.instance());

    if (genericPlugin) {
        // 插件加载成功,可以使用插件

        // 使用 QGenericPlugin 的 create 函数来创建插件对象
        QObject *pluginInstance = genericPlugin->create("", "");

        if (pluginInstance) {
            // 创建成功,可以使用插件实例

            // 尝试转换插件对象为MyPlugin类型
            Interface *myPlugin = qobject_cast<Interface *>(pluginInstance);

            if (myPlugin) {
                // 使用插件的方法
                myPlugin->doSomething("COM3");
            } else {
                // 无法将插件实例转换为你的插件类
            }
        } else {
            // 插件创建失败
        }
    } else {
        // 插件加载失败
    }
}
9. 测试结果

在Qt Creator中运行Application项目,结果弹出一个空白窗口,并在“应用程序输出窗口”输出“传入参数: “COM3” ,插件功能执行完毕!”

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
是的,你可以使用Qt提供的QPluginLoader类来加载.so文件。QPluginLoader是一个可以加载插件的类,插件可以是动态链接库、静态链接库或可执行文件,只要它们实现了QPlugin接口。 下面是一个使用QPluginLoader加载.so文件的示例代码: ``` c++ #include <QApplication> #include <QPluginLoader> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); // 加载.so文件 QPluginLoader loader("/path/to/plugin.so"); // 判断.so文件是否加载成功 if (!loader.load()) { qDebug() << "Failed to load plugin:" << loader.errorString(); return -1; } // 获取.so文件中的插件实例 QObject *plugin = loader.instance(); // 判断插件实例是否获取成功 if (!plugin) { qDebug() << "Failed to get plugin instance:" << loader.errorString(); return -1; } // 使用插件 plugin->someFunction(); return a.exec(); } ``` 在这个示例代码中,我们使用QPluginLoader加载了一个.so文件,并获取了其中的插件实例。如果加载或获取插件实例失败,我们会输出相应的错误信息。 需要注意的是,你需要在.so文件中实现QPlugin接口才能够被QPluginLoader加载。具体来说,你需要在.so文件中定义一个导出函数,这个函数返回一个Q_EXPORT_PLUGIN2宏,宏中包含了插件的元数据信息。例如: ``` c++ #include <QtPlugin> class MyPlugin : public QObject { // ... }; Q_DECLARE_METATYPE(MyPlugin*) Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.MyPlugin") Q_EXPORT_PLUGIN2(my_plugin, MyPlugin) ``` 在这个示例代码中,我们定义了一个MyPlugin类,并在.so文件中导出了这个类的实例。同时我们使用了Q_PLUGIN_METADATA宏来定义插件的元数据信息。需要注意的是,这个宏中的IID参数必须是唯一的,一般情况下我们使用插件类的全局静态变量作为IID。最后我们使用Q_EXPORT_PLUGIN2宏来导出插件,它的第一个参数是插件的名称,第二个参数是插件的类名。 总的来说,使用QPluginLoader类可以方便地加载.so文件,并获取其中的插件实例。你只需要在.so文件中实现QPlugin接口,就可以让它被QPluginLoader加载

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值