300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > Qt开发技术:Qt的动态静态插件框架介绍和Demo

Qt开发技术:Qt的动态静态插件框架介绍和Demo

时间:2022-01-26 22:07:31

相关推荐

Qt开发技术:Qt的动态静态插件框架介绍和Demo

若该文为原创文章,转载请注明原文出处

本文章博客地址:/qq21497936/article/details/105481285

长期持续带来更多项目与技术分享,咨询请加QQ:21497936、微信:yangsir198808

红胖子(红模仿)的博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中...(点击传送门)

Qt开发专栏:开发技术(点击传送门)

前言

Qt的插件化开发框架类似于前后端的微服务的场景,授权哪个微服务则前端可以使用哪个微服务,作为进一步的货站,软件中也可以先出现图标,点击后进行动态加载,减少软件的部署大小同时更重要的是增加了程序的灵活性。

创建Qt插件

Qt提供了两个用于创建插件的API:

编写Qt扩展的高级API:自定义数据库驱动程序、图像格式、文本编解码器、自定义样式等;用于扩展Qt应用程序的低级API;

例如,如果您想编写一个定制的QStyle子类并让Qt应用程序动态加载它,那么您将使用更高级别的API。

由于较高级别的API构建在较低级别的API之上,因此两个API都存在一些共同的问题。

如果您想提供与Qt设计器一起使用的插件,请参阅Qt设计器模块文档

(注意:此处我们的插件主要有研究动态下载加载)

高级API:编写Qt扩展(这不是我们当前所关注的,可以略过)

编写扩展Qt本身的插件是通过子类化适当的插件基类、实现一些函数和添加宏来实现的。

有几个插件基类。默认情况下,派生插件存储在标准插件目录的子目录中。如果插件没有存储在适当的目录中,Qt将找不到它们。

下表总结了插件基类。有些类是私有的,因此没有文档记录。您可以使用它们,但不能保证与更高版本的Qt兼容。

如果有一个名为MyStyle的新样式类要作为插件提供,则该类需要定义如下(MyStyle plugin.h):

class MyStylePlugin : public QStylePlugin{Q_OBJECTQ_PLUGIN_METADATA(IID \"org.qt-project.Qt.QStyleFactoryInterface" \FILE"mystyleplugin.json")public:QStyle *create(const QString &key);};

确保类实现位于.cpp文件中:

#include "mystyleplugin.h"QStyle *MyStylePlugin::create(const QString &key){if (key.toLower() == "mystyle")return new MyStyle;return 0;}

(请注意,QStylePlugin不区分大小写,create()实现中使用了该键的小写版本;大多数其他插件区分大小写。)

此外,大多数插件都需要一个包含描述插件的元数据的json文件(mystyleplugin.json)。对于样式插件,它只包含插件可以创建的样式列表:

json文件中需要提供的信息类型依赖于插件,有关需要包含在文件中的信息的详细信息,请查看具体的类描述。

对于数据库驱动程序、图像格式、文本编解码器和大多数其他插件类型,不需要显式创建对象。Qt将根据需要找到并创建它们。样式是一个例外,因为您可能希望在代码中显式设置样式。要应用样式,请使用以下代码:

QApplication::setStyle(QStyleFactory::create("MyStyle"));

一些插件类需要实现额外的函数。有关必须为每种插件重新实现的虚拟函数的详细信息,请参阅类文档。

样式插件示例演示如何实现扩展QStylePlugin基类的插件。

低级API:扩展Qt应用程序(这是我们需要的)

不仅Qt本身,Qt应用程序也可以通过插件进行扩展。这需要应用程序使用QPluginLoader检测和加载插件。在这种情况下,插件可以提供任意的功能,并且不限于数据库驱动程序、图像格式、文本编码、样式和扩展Qt功能的其他类型的插件。

通过插件使应用程序可扩展包括以下步骤:

步骤一:定义一组用于与插件对话的接口(只有纯虚拟函数的类,在调用插件时就是使用给该接口类实例调用接口);步骤二:使用Q_DECLARE_INTERFACE()宏告诉Qt的元对象系统有关该接口的信息;步骤三:在应用程序中,使用QPluginLoader加载插件;步骤四:使用qobject_cast()测试插件是否实现给定接口;

例如,下面是接口类的定义:

class FilterInterface{public:virtual ~FilterInterface() {}virtual QStringList filters() const = 0;virtual QImage filterImage(const QString &filter,const QImage &image,QWidget *parent) = 0;};

下面是实现该接口的插件类的定义:

#include <QObject>#include <QtPlugin>#include <QStringList>#include <QImage>#include <plugandpaint/interfaces.h>class ExtraFiltersPlugin : public QObject, public FilterInterface{Q_OBJECTQ_PLUGIN_METADATA(IID \"org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface" \FILE \"extrafilters.json")Q_INTERFACES(FilterInterface)public:QStringList filters() const; QImage filterImage(const QString &filter,const QImage &image,QWidget *parent);};

必须先初始化QCoreApplication,然后才能加载插件。

部署插件位置

Qt应用程序自动知道哪些插件可用,因为插件存储在标准插件子目录中。因为这个应用程序不需要任何代码来查找和加载插件,因为Qt会自动处理它们。

在开发过程中,插件的目录是QTDIR/plugins(其中QTDIR是Qt的安装目录),每种类型的插件都位于该类型的子目录中,例如styles。

如果您希望应用程序使用插件,但不希望使用标准插件路径,请让安装过程确定要用于插件的路径,并保存路径,例如,使用QSettings,以便应用程序在运行时读取。然后,应用程序可以使用此路径调用QCoreApplication::addLibraryPath(),应用程序将可以使用您的插件。请注意,路径的最后部分(例如,样式)不能更改。

如果您希望插件可加载,那么一种方法是在应用程序下创建一个子目录,并将插件放在该目录中。如果分发Qt附带的任何插件(位于plugins目录中的插件),则必须将插件所在的插件下的子目录复制到应用程序根文件夹(即,不包括插件目录)。

静态插件

将插件包含在应用程序中的最常规和最灵活的方法是将其编译到单独提供、在运行时检测和加载的动态库中。

插件可以静态地链接到应用程序中。如果您构建Qt的静态版本,这是包含Qt的预定义插件的唯一选项。使用静态插件使部署不易出错,但有一个缺点,即如果不完全重建和重新分发应用程序,就无法添加插件的任何功能。

要静态链接插件,需要使用QTPLUGIN将所需的插件添加到构建中。

在应用程序的.pro文件中,需要以下条目:

QTPLUGIN + = qjpeg \qgif \qkrcodecs

make会自动将QTPLUGIN中使用的Qt模块通常需要的插件添加到QTPLUGIN中,而更多专用插件需要手动添加。每个类型都可以覆盖自动添加的插件的默认列表。例如,要链接最小插件而不是默认的Qt平台适配插件,使用:

QTPLUGIN.platforms = qminimal

如果既不希望默认的,也不希望最小的QPA插件自动链接,使用:

QTPLUGIN.platforms = -

默认设置被调整为最佳的开箱即用体验,但可能会不必要地使应用程序膨胀。建议检查qmake构建的链接器命令行,并消除不必要的插件。

链接静态插件的详细信息

要使静态插件真正被链接和实例化,应用程序代码中还需要Q_IMPORT_PLUGIN()宏,但这些宏是由qmake自动生成并添加到应用程序项目中的。

如果不希望自动链接添加到QTPLUGIN的所有插件,请从配置变量中删除import_plugins:

CONFIG -= import_plugins

创建静态插件

可以通过以下步骤创建自己的静态插件:

步骤一:将CONFIG+=static添加到插件的.pro文件中。步骤二:在应用程序中使用Q_IMPORT_PLUGIN()宏。步骤三:如果插件附带qrc文件,请在应用程序中使用Q_INIT_RESOURCE()宏。步骤四:使用.pro文件中的LIBS将应用程序与插件库链接。

注意:如果不使用qmake构建插件,则需要确保已定义QT_STATICPLUGIN预处理器宏。

部署和调试插件

部署插件

部署Qt或应用程序在运行时应加载的插件库。如果使用静态插件,那么插件代码已经是应用程序可执行文件的一部分,不需要单独的部署步骤。

插件目录

当应用程序运行时,Qt首先将应用程序的可执行目录作为搜索插件的基本目录。例如,如果应用程序位于C:\ Program Files\MyApp中并且有一个样式插件,Qt将在C:\ Program Files\MyApp\styles中查找。(具体可查看QCoreApplication::applicationDirPath(),找到应用程序的可执行文件)。Qt还将查找QLibraryInfo::location(QLibraryInfo::PluginsPath)指定的目录,该目录通常位于QTDIR/plugins(其中QTDIR是Qt的安装目录)。

如果希望Qt在其他地方查找,可以通过调用QCoreApplication::addLibraryPath()添加所需的路径。如果要设置自己的路径,可以使用QCoreApplication::setLibraryPaths()。还可以使用qt.conf文件覆盖编译到qt库中的硬编码路径。还有一种可能是在运行应用程序之前设置QT_PLUGIN_PATH环境变量。如果设置了,Qt将在变量中指定的路径(由系统路径分隔符分隔)中查找插件。

注意:QT_PLUGIN_PATH不应作为系统范围的环境变量导出,因为它可能会干扰其他QT安装。

动态加载和验证插件

加载插件时,Qt库会进行一些健全性检查,以确定插件是否可以加载和使用。这提供了并行安装Qt库的多个版本和配置的能力。

与版本号较高的Qt库链接的插件不会被版本号较低的库加载;

例如:Qt 5.0.0 不会加载 Qt 5.0.1编译的插件;

与主版本号较低的Qt库链接的插件不会由主版本号较高的库加载;

例如:Qt 5.0.1 不会加载 Qt 4.8.2编译的插件;

例如:Qt 5.1.1 不会加载 Qt 5.1.0和Qt 5.0.3编译的插件;

在构建插件以扩展应用程序时,确保插件的配置方式与应用程序相同非常重要。这意味着如果应用程序是以发布模式构建的,那么插件也应该以发布模式构建。除了Unix操作系统,插件系统不会加载以不同模式构建的插件。

如果您将Qt配置为在调试和发布模式下构建,但只在发布模式下构建应用程序,则需要确保插件也在发布模式下构建。默认情况下,如果Qt的调试版本可用,则插件将仅在调试模式下生成。要强制插件以发布模式构建,请将以下行添加到插件的项目文件中:

CONFIG += release

这将确保插件与应用程序中使用的库版本兼容。

调试插件

有许多问题可能会阻止正确编写的插件与设计用来使用它们的应用程序一起工作。其中许多都与插件和应用程序构建方式的差异有关,这些差异通常是由独立的构建系统和过程引起的。

下表描述了开发人员在创建插件时遇到的常见问题的原因:

您还可以使用QT_DEBUG_PLUGINS环境变量从QT获取有关试图加载的每个插件的诊断信息。在启动应用程序的环境中将此变量设置为非零值。

补充:QT_DEBUG_PLUGINS在调试xcb错误时,非常重要,请查看

Qt实用技巧:ubuntu发布程序打包流程(解决插件xcb加载失败)》博文中的“步骤四:运行调试插件(点击传送门)”。

使用qt.conf

可以使用qt.conf文件覆盖路径或指定要传递给平台插件的参数。

格式和位置

qt.conf文件是一个INI文本文件,如QSettings文档中所述。

QLibraryInfo将从以下位置之一加载qt.conf:

路径一::/qt/etc/qt.conf使用资源系统路径二:在macOS上,在应用程序包内的资源目录中,例如assistant.app/Contents/Resources/qt.conf路径三:在包含应用程序可执行文件的目录中,即QCoreApplication::applicationDirPath()+QDir::separator()+"qt.conf"

重写路径

conf文件可用于重写编译到qt库中的硬编码路径。可以使用QLibraryInfo类访问这些路径。如果没有qt.conf,QLibraryInfo中的函数将返回这些硬编码的路径;否则,它们将返回qt.conf中指定的路径。

如果没有qt.conf,qt库将使用硬编码路径来查找插件、翻译等。目标系统上可能不存在这些路径,或者它们不可访问。因此,您可能需要qt.conf来使qt库在其他地方查找。

文件应该有一个路径组,其中包含与QLibraryInfo::LibraryLocation枚举的每个值对应的条目。有关各个位置含义的详细信息,请参阅QLibraryInfo文档。

绝对路径按qt.conf文件中的指定使用。所有路径都与前缀相关。在Windows和X11上,前缀是相对于包含应用程序可执行文件QCoreApplication::applicationDirPath()的目录的。在macOS上,前缀与应用程序包中的内容相关。例如,application.app/Contents/plugins/是加载Qt插件的默认位置。

注意,插件需要放在plugins目录下的特定子目录中(详细信息请参见“创建Qt插件”->“部署插件位置””)。

例如,qt.conf文件可以包含以下内容:

注意:在INI文件中,反斜杠字符被视为特殊字符(请参见QSettings)。因此,建议对Windows上的路径也使用正斜杠。否则,需要转义字符:

[Paths]Prefix = /some/pathTranslations = i18n

注意:在INI文件中,反斜杠字符被视为特殊字符(请参见QSettings)。因此,建议对Windows上的路径也使用正斜杠。否则,需要转义字符:

为平台插件配置参数

conf可以包含一个平台组,其键是要传递给平台插件的以逗号分隔的参数列表。密钥名是平台插件的名称,第一个字母大写,后跟参数。

例如:

[Platforms]WindowsArguments = fontengine=freetype

Demo:扩展Qt应用程序动态插件

Demo运行效果

动态插件的pro文件:sharePlugin.pro

TEMPLATE= lib# 动态库的CONFIG += plugin# 静态库的#CONFIG += plugin staticQT += widgetsTARGET = $$qtLibraryTarget(sharePlugin)DESTDIR = ../../pluginsHEADERS += \Interface.h \SharePlugin.hSOURCES += \SharePlugin.cpp

动态插件的接口头文件(只有头文件):Interface.h

#ifndef INTERFACE_H#define INTERFACE_H#include <QString>#include <QtPlugin>class Interface{public:virtual ~Interface(){}virtual QString getSerailData() = 0;// 序列化virtual void setSerailData(const QString data) = 0; // 序列化设置virtual void show() = 0; // 显示窗口};#define Interface_iid "MyPlugin.Interface"Q_DECLARE_INTERFACE(Interface, Interface_iid)#endif // INTERFACE_H

动态插件的具体实现头文件:SharePlugin.h

#ifndef SHAREPLUGIN_H#define SHAREPLUGIN_H/************************************************************\* 控件名称:SharePlugin* 控件描述:动态插件模板,生成插件部分* 作者:红模仿 联系方式:QQ21497936* 博客地址:/qq21497936* 日期 版本描述* 04月10日v1.0.0 基础模板\************************************************************/#include <QObject>#include <QtPlugin>#include "Interface.h"class SharePlugin : public QObject, public Interface{Q_OBJECTQ_INTERFACES(Interface)// Q_INTERFACES宏用于告诉Qt该类实现的接口。Q_PLUGIN_METADATA(IID Interface_iid) // Q_PLUGIN_METADATA宏用于描述插件元数据public:explicit SharePlugin(QObject *parent = 0);virtual QString getSerailData();// 序列化virtual void setSerailData(const QString data); // 序列化设置virtual void show(); // 显示窗口private:QString _data; // 序列化};#endif // SHAREPLUGIN_H

动态插件的具体实现源文件:SharePlugin.cpp

#include "SharePlugin.h"#include <QDebug>#include <QWidget>SharePlugin::SharePlugin(QObject *parent): QObject(parent),_data("null"){}void SharePlugin::setSerailData(const QString data){_data = data;qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << _data;}QString SharePlugin::getSerailData(){qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << _data;return _data;}void SharePlugin::show(){QWidget *pWidget = new QWidget;pWidget->setWindowTitle("test SharePlugin");pWidget->show();}

测试Demo热加载关键代码

bool Widget::loadPlugin(){// 热加载插件// 获取当前应用程序所在的路径QDir pluginsDir(qApp->applicationDirPath());// 切换到插件路径(判断是否输出到debug和release,目前是在pro中直接输出到../../app目录)if(pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release"){// 返回上一级目录pluginsDir.cdUp();}// 切换到插件目录pluginsDir.cdUp();pluginsDir.cdUp();pluginsDir.cd("plugins");// 打开对应的插件// 引入静态插件需要将静态编译的库,编译的时候就要引入进去// 实测静态插件,debug调用release版本插件可行,反过来也可行// 实测动态插件,debug调用release版本插件不可行,反过来也不可行QString pluginDllName = "sharePlugin.dll"; // release版本插件(注意:release下只能加载release版本的插件)// QString pluginDllName = "sharePlugind.dll";// debug版本插件(注意:debug下只能加载debug版本的插件)QString dllPath = pluginsDir.absolutePath() + QDir::separator() + pluginDllName;if(!QFile::exists(dllPath)){qDebug() << __FILE__ << __LINE__<< "Not exist file:" << dllPath;return false;}// 加载插件QPluginLoader pluginLoader(dllPath);pluginLoader.load();qDebug() << __FILE__ << __LINE__ << pluginLoader.isLoaded();QObject *pPlugin = pluginLoader.instance();if(pPlugin){QString pluginName = pPlugin->metaObject()->className();qDebug() << __FILE__ << __LINE__ << "Succeed to get plugin class Name:" << pluginName;//对插件初始化if(pluginName == "SharePlugin"){_pInterface = qobject_cast<Interface *>(pPlugin);if (_pInterface){qDebug() << __FILE__ << __LINE__ << "Succeed to load SharePlugin";}else{qDebug() << __FILE__ << __LINE__ << "Failed to load SharePlugin";}}}else{qDebug() << __FILE__ << __LINE__ << "Failed to load plugin:" << dllPath;return false;}return true;}

测试Demo测试代码

void Widget::on_pushButton_testSharePlugin_clicked(){if(_pInterface){qDebug() << __FILE__ << __LINE__ << _pInterface->getSerailData();_pInterface->setSerailData("hello world!!!");qDebug() << __FILE__ << __LINE__ << _pInterface->getSerailData();_pInterface->show();}}

Demo:扩展Qt应用程序静态插件

Demo运行效果

静态插件的pro文件:staticPlugin.pro

TEMPLATE= lib# 动态库的#CONFIG += plugin# 静态库的CONFIG += plugin staticQT += widgetsTARGET = $$qtLibraryTarget(staticPlugin)DESTDIR = ../../pluginsHEADERS += \StaticInterface.h \StaticPlugin.hSOURCES += \StaticPlugin.cpp

静态插件的接口头文件(只有头文件):StaticInterface.h

#ifndef STATICINTERFACE_H#define STATICINTERFACE_H#include <QString>#include <QtPlugin>class StaticInterface{public:virtual ~StaticInterface(){}virtual QString getSerailData() = 0;// 序列化virtual void setSerailData(const QString data) = 0; // 序列化设置virtual void show() = 0; // 显示窗口};#define StaticInterface_idd "MyPlugin.StaticInterface"Q_DECLARE_INTERFACE(StaticInterface, StaticInterface_idd)#endif // STATICINTERFACE_H

静态插件的具体实现头文件:StaticPlugin.h

#ifndef STATICPLUGIN_H#define STATICPLUGIN_H/************************************************************\* 控件名称:StaticPlugin* 控件描述:静态插件模板,生成插件部分* 作者:红模仿 联系方式:QQ21497936* 博客地址:/qq21497936* 日期 版本描述* 04月10日v1.0.0 基础模板\************************************************************/#include <QObject>#include <QtPlugin>#include <StaticInterface.h>class StaticPlugin : public QObject, public StaticInterface{Q_OBJECTQ_INTERFACES(StaticInterface) // Q_INTERFACES 宏用于告诉Qt该类实现的接口。Q_PLUGIN_METADATA(IID StaticInterface_idd)// Q_PLUGIN_METADATA宏用于描述插件元数据public:explicit StaticPlugin(QObject *parent = 0);virtual QString getSerailData();// 序列化virtual void setSerailData(const QString data); // 序列化设置virtual void show(); // 显示窗口private:QString _data; // 序列化};#endif // STATICPLUGIN_H

静态插件的具体实现源文件:StaticPlugin.cpp

#include "StaticPlugin.h"#include <QDebug>#include <QWidget>StaticPlugin::StaticPlugin(QObject *parent): QObject(parent),_data("null"){}void StaticPlugin::setSerailData(const QString data){_data = data;qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << _data;}QString StaticPlugin::getSerailData(){qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << _data;return _data;}void StaticPlugin::show(){QWidget *pWidget = new QWidget;pWidget->setWindowTitle("test StaticPlugin");pWidget->show();}测试Demo的pro工程文件:testStaticDemo.pro#-------------------------------------------------## Project created by QtCreator -04-09T15:36:39##-------------------------------------------------QT += core guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsTARGET = testDemoTEMPLATE = app#DESTDIR = ../../app# The following define makes your compiler emit warnings if you use# any feature of Qt which has been marked as deprecated (the exact warnings# depend on your compiler). Please consult the documentation of the# deprecated API in order to know how to port your code away from it.DEFINES += QT_DEPRECATED_WARNINGS# You can also make your code fail to compile if you use deprecated APIs.# In order to do so, uncomment the following line.# You can also select to disable deprecated APIs only up to a certain version of Qt.#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0SOURCES += \main.cpp \StaticWidget.cppHEADERS += \StaticWidget.hFORMS += \StaticWidget.ui# 引入静态插件需要将静态编译的库,编译的时候就要引入进去# 实测静态插件,debug调用release版本插件可行,反过来也可行# 实测动态插件,debug调用release版本插件不可行,反过来也不可行CONFIG(debug, debug|release) {LIBS += -L../../pluginsLIBS += -lstaticPlugind} else {LIBS += -L../../pluginsLIBS += -lstaticPlugin}

测试Demo加载关键代码

bool StaticWidget::loadStaticPlugin(){bool ret = false;for(int index = 0; index < QPluginLoader::staticInstances().size(); index++){QObject *pStaticPlugin = QPluginLoader::staticInstances().at(index);_pStaticInterface = qobject_cast<StaticInterface *>(pStaticPlugin);if (_pStaticInterface){qDebug() << __FILE__ << __LINE__ << "Succeed to load StaticInterface:" << pStaticPlugin->metaObject()->className();ret = true;}}if(!ret){qDebug() << __FILE__ << __LINE__ << "Failed to load StaticInterface";return false;}return true;}

测试Demo测试代码

void StaticWidget::on_pushButton_testStaticPlugin_clicked(){if(_pStaticInterface){qDebug() << __FILE__ << __LINE__ << _pStaticInterface->getSerailData();_pStaticInterface->setSerailData("hello world!!!");qDebug() << __FILE__ << __LINE__ << _pStaticInterface->getSerailData();_pStaticInterface->show();}}

工程模板v1.0.0

插件的工程模板v1.0.0,包含动态q插件模板和静态插件模板以及各自的测试Demo代码

下载工程模板

CSDN:/download/qq21497936/12324131

QQ群:1047134658(点击“文件”搜索“qtPluginDemo”,群内与博文同步更新所有可开源的源码模板)

若该文为原创文章,转载请注明原文出处

本文章博客地址:/qq21497936/article/details/105481285

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。