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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值