Qt插件开发(1) - 开发实例

一、Qt插件机制

代码在Qt5.12.11和Qt6.1.1的linux版下测试ok。

1、简介

插件是一种遵循一定规范的应用程序接口编写出来的程序,定位于开发实现/扩展应用软件平台不具备的功能的程序。
具体的体现形式:
linux: .so文件(动态库),.a文件(静态库)
windows: *.dll文件(动态库), *.lib文件(静态库)

2、Qt插件API

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

3、插件调用

A、定义一个接口集(只有纯虚函数的类),用来与插件交流。
B、用宏Q_DECLARE_INTERFACE()将该接口告诉Qt元对象系统。
C、应用程序中用QPluginLoader来加载插件。
D、用宏qobject_cast()来判断一个插件是否实现了接口。
E、调用插件的功能。

4、创建插件

创建一个插件的步骤如下:
A、声明插件类,插件类继承自QObject和插件实现的接口。
B、用宏Q_INTERFACES()将插件接口告诉Qt元对象系统。
C、用宏Q_EXPORT_PLUGIN2()导出插件类。
D、用适当的.pro文件构建插件。
在加载插件前, QCoreApplication对象必须被初始化。

二、实操

1、创建工程

使用QtCreator创建子目录工程(Subdirs Project),命名为PluginDemoAndTest
在这里插入图片描述
其PluginDemoAndTest.pro文件如下:

TEMPLATE = subdirs

SUBDIRS += \
    PluginDemo \
    PluginTest

然后分别添加插件子工程,应用子工程。

2、插件子工程

PluginDemoAndTest上右键选择New Subproject,选择创建为空的Qt工程,取名PluginDemo,其PluginDemo.pro文件如下:

QT += core

TEMPLATE = lib

TARGET = $$qtLibraryTarget(PluginDemo)

CONFIG += c++11 plugin

TARGET = PluginDemo

DESTDIR = ../plugins

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
        plugindemo.cpp

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

HEADERS += \
    plugindemo.h \
    plugininterface.h

创建接口文件plugininterface.h如下:

#ifndef PLUGININTERFACE_H
#define PLUGININTERFACE_H

#include <QString>
#include <QtPlugin>

class PluginInterface
{
public:
    virtual ~PluginInterface() {}		//避免编译器抱怨

    virtual QStringList interfaceList() = 0;

    virtual QString action(QString& interface) = 0;
};

QT_BEGIN_NAMESPACE
#define PluginInterface_iid "org.csm.embed.plugin.pluginInterface.1.0"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
QT_END_NAMESPACE

#endif // PLUGININTERFACE_H

包含了提供接口查询的interfaceList()方法,以及根据调用接口实现功能的action()方法,使用纯虚方法,而析构函数的声明是为了满足一些编译器的要求。

然后创建PluginInterface的实现:

//plugindemo.h
#ifndef PLUGINDEMO_H
#define PLUGINDEMO_H

#include <QObject>

#include "plugininterface.h"

class PluginDemo : public QObject, PluginInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.csm.embed.plugin.pluginInterface.1.0" FILE "plugindemo.json")
    Q_INTERFACES(PluginInterface)

public:
    explicit PluginDemo();

    QStringList interfaceList();

    QString action(QString& interface);

private:
    QStringList m_ifList;
};

#endif // PLUGINDEMO_H

这里是一个PluginInterface的具体实现,其采用多继承方式,切第一个父类必须为QObject类,然后是接口。(如果其接口已经是QObject的子类,这段话作废)

//plugindemo.cpp
#include "plugindemo.h"

#include <QDebug>

PluginDemo::PluginDemo()
{
    m_ifList.append("interface1");
    m_ifList.append("interface2");
}

QStringList PluginDemo::interfaceList()
{
    return m_ifList;
}

QString PluginDemo::action(QString &interface)
{
    QString actionName;
    if(m_ifList.contains(interface)) {
        actionName =  interface + " done...";
    } else {
        actionName =  interface + " is not exists";
    }
    qDebug() << actionName;
    return actionName;
}

通过interfaceList()返回接口列表,而具体的action则根据interface来动作,即返回/打印字符串。
Plugin工程至此结束。

3、应用子工程

接下来创建使用/测试PluginDemo的应用工程, 其PluginDemo.pro文件如下,其关键点是需要引入plugininterface.h 头文件

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    main.cpp \
    widget.cpp

HEADERS += \
    ../PluginDemo/plugininterface.h \
    widget.h

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

main.cpp创建widget显示,并进入Qt事件循环机制:

//main.cpp
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

widget类实现,负责具体的调用实现了PluginInterface接口的PluginDemo类。

//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QStringList>

class QLineEdit;
class QLabel;
class PluginInterface;

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);

    ~Widget();

    void loadPlugin();

public slots:
    void queryInterface();

    void doAction();

private:
    QLabel* ifListInfo;				//显示接口列表
    QLineEdit* actionName ;			//输入接口名称
    PluginInterface* interfacePtr;  //保存接口指针
    QStringList interfaceList;		//保存运行结果	
};
#endif // WIDGET_H

具体的widget实现了,关键点queryInterface槽函数中的loadPlugin(),以及doAction槽函数中对接口的调用

//widget.cpp
#include "widget.h"

#include <QGridLayout>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
#include <QDebug>
#include <QDir>
#include <QtGlobal>
#include <QApplication>
#include <QPluginLoader>

#include "../PluginDemo/plugininterface.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    this->setMinimumSize(600, 400);
    QGridLayout* layout = new QGridLayout(this);

    QLabel* ifListName = new QLabel("list: ");
    ifListInfo = new QLabel("");
    QPushButton* queryButton = new QPushButton("inqury");
    connect(queryButton, &QPushButton::clicked,
            this, &Widget::queryInterface);

    layout->addWidget(ifListName, 0, 0);
    layout->addWidget(ifListInfo, 0, 1);
    layout->addWidget(queryButton, 0, 2);
    //QPushButton

    QLabel* actionLabel = new QLabel("interface:");
    actionName = new QLineEdit();
    actionName->setPlaceholderText("input one interface of list above");
    QPushButton* actionButton = new QPushButton("action");
    connect(actionButton, &QPushButton::clicked,
            this, &Widget::doAction);

    layout->addWidget(actionLabel, 1, 0);
    layout->addWidget(actionName, 1, 1);
    layout->addWidget(actionButton, 1, 2);

    doneResult  = new QLabel("result");
    layout->addWidget(doneResult, 2, 0, 1, 3, Qt::AlignCenter);

    interfacePtr = Q_NULLPTR;
}

Widget::~Widget()
{

}

void Widget::loadPlugin()
{
    bool bRet = true;
    //路径根据系统差异来具体实现
    QDir pluginDir(qApp->applicationDirPath());
#if defined (Q_OS_WIN)
    if (pluginDir.dirName().toLower() == "debug" || pluginDir.dirName().toLower() == "release") {
        pluginDir.cdUp();
        pluginDir.cdUp();
    }
#elif defined Q_OS_LINUX //Q_OS_WIN
    pluginDir.cdUp();
#endif //Q_OS_LINUX
    pluginDir.cd("plugins");

    foreach(QString fileName, pluginDir.entryList(QDir::Files)) {
    	//获取插件的具体路径
        QString file = pluginDir.absoluteFilePath(fileName);
		
		//实例化QPluginLoader类
        QPluginLoader pluginLoader(file);
		
		//返回插件实例
        QObject* plugin = pluginLoader.instance();
        if (plugin) {
        	//注意此处是插件类名,而不是接口类名称哦
            QString plugName = plugin->metaObject()->className();
            if (plugName == "PluginDemo") {
                interfacePtr = qobject_cast<PluginInterface*>(plugin);
                interfaceList = interfacePtr->interfaceList();
                break;
            }
        } else {
            qDebug() << pluginLoader.errorString();
        }
    }
}

void Widget::queryInterface()
{
    //qDebug() << "query";
    loadPlugin();
    QString interfaceList;
    if (interfacePtr != Q_NULLPTR) {
        foreach(auto interfaceName, interfacePtr->interfaceList()) {
            interfaceList.append(interfaceName);
            interfaceList.append(", ");
        }

        if (!interfaceList.isEmpty())
            interfaceList.remove(interfaceList.length() - 2, 2);
    }
    ifListInfo->setText(interfaceList);
}

void Widget::doAction()
{
    //qDebug() << "doAction";
    QString action = actionName->text();
    QString doneAction = interfacePtr->action(action);
    doneResult->setText(doneAction);
}

实际运行结果:
在这里插入图片描述

在这里插入图片描述

三、定位插件(此段照搬《Qt高级——Qt插件开发》)

Qt应用程序将会自动感知可用的插件,因为插件都被存储在标准的子目录当中。因此应用程序不需要任何查找或者加载插件的代码。
在开发过程中,插件的目录是QTDIR/plugins(QTDIR是Qt的安装目录),每个类型的插件放在相应类型的目录下面。如果想要应用程序使用插件,但不想用标准的插件存放路径,可以在应用程序的安装过程中指定要使用的插件的路径,可以使用QSettings,保存插件路径,在应用程序运行时读取配置文件。应用程序可以通过QCoreApplication::addLibraryPath()函数将指定的插件路径加载到应用程序中。
使插件可加载的一种方法是在应用程序所在目录创建一个子目录,用于存放插件。如果要发布和Qt一起发布的插件(存放在plugins目录)中的任何插件,必须拷贝plugins目录下的插件子目录到应用程序的根目录下。

四、参考

Qt高级——Qt插件开发/天山老妖S
How to Create Qt Plugins

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的Qt插件开发实例: 1. 首先,创建一个Qt插件项目。在Qt Creator中选择"File -> New File or Project",然后选择"Qt Library"项目类型,再选择"Qt Designer Form Class"模板。 2. 在创建项目时,选择需要创建的插件类型。可以选择创建基于QWidget或QWindow的插件,也可以选择创建基于QML的插件。 3. 在项目中创建插件接口类,声明插件接口函数。例如,创建一个名为"PluginInterface"的接口类,在其中声明一个名为"doSomething"的虚函数。 ```c++ // PluginInterface.h #ifndef PLUGININTERFACE_H #define PLUGININTERFACE_H #include <QtPlugin> class PluginInterface { public: virtual ~PluginInterface() {} virtual void doSomething() = 0; }; Q_DECLARE_INTERFACE(PluginInterface, "com.example.PluginInterface") #endif // PLUGININTERFACE_H ``` 4. 在项目中创建插件实现类,继承自插件接口类并实现接口函数。例如,创建一个名为"ExamplePlugin"的实现类,在其中实现"doSomething"函数。 ```c++ // ExamplePlugin.h #ifndef EXAMPLEPLUGIN_H #define EXAMPLEPLUGIN_H #include "PluginInterface.h" class ExamplePlugin : public QObject, public PluginInterface { Q_OBJECT Q_PLUGIN_METADATA(IID "com.example.PluginInterface" FILE "exampleplugin.json") Q_INTERFACES(PluginInterface) public: void doSomething() override; }; #endif // EXAMPLEPLUGIN_H // ExamplePlugin.cpp #include "ExamplePlugin.h" #include <QDebug> void ExamplePlugin::doSomething() { qDebug() << "ExamplePlugin does something!"; } ``` 5. 在项目中创建一个元数据文件,描述插件信息。例如,创建一个名为"exampleplugin.json"的元数据文件,指定插件名称、版本号、作者等信息。 ```json { "Keys": [ "PluginName", "Version", "Vendor" ], "Values": [ "ExamplePlugin", "1.0", "com.example" ] } ``` 6. 在项目中的.pro文件中添加必要的配置,例如指定插件类型、添加插件源文件、指定插件元数据文件等。例如: ```pro TEMPLATE = lib CONFIG += plugin QT += core SOURCES += \ ExamplePlugin.cpp HEADERS += \ PluginInterface.h \ ExamplePlugin.h TARGET = ExamplePlugin QMAKE_PLUGIN_NAME = ExamplePlugin QMAKE_TARGET_DESCRIPTION = Example Plugin QMAKE_TARGET_PRODUCT = ExamplePlugin QMAKE_PLUGIN_CLASS_NAME = ExamplePlugin QMAKE_PLUGIN_METADATA = exampleplugin.json ``` 7. 编译插件项目,生成插件库文件。在项目构建完成后,会在输出目录中生成一个名为"libExamplePlugin.so"(在Linux下)或"ExamplePlugin.dll"(在Windows下)的库文件。 8. 创建一个应用程序项目,加载并使用插件。例如,在应用程序中加载并使用ExamplePlugin插件: ```c++ // main.cpp #include <QCoreApplication> #include <QPluginLoader> #include "PluginInterface.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QPluginLoader loader("libExamplePlugin.so"); QObject* plugin = loader.instance(); if (plugin) { PluginInterface* interface = qobject_cast<PluginInterface*>(plugin); if (interface) { interface->doSomething(); } } return a.exec(); } ``` 在上面的示例中,首先创建一个QPluginLoader对象,并使用其load()函数加载插件库文件。然后,使用loader.instance()函数获取插件实例,再将其转换为插件接口类的指针,最后调用插件接口函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值