创建 Qt 插件
摘要:
- 高级 API:编写 Qt 自身扩展
- 低级 API:扩展 Qt 应用程序
- 部署插件
- 静态插件
- 链接静态插件的详细信息
- 创建静态插件
- 部署和调试插件
Qt 提供了两套 API 用于创建插件:
- 用于编写 Qt 自身扩展的高级 API:自定义数据库驱动程序、图像格式、文本编解码器、自定义样式等。
- 用于扩展 Qt 应用程序的低级 API。
例如,如果我们想编写一个自定义 QStyle 子类,并让 Qt 应用程序动态加载它,那么我们将使用到高级的 API。
由于高级 API 是在低级 API 的基础上构建的,所以两者都存在一些共通的地方。
高级 API:编写 Qt 自身扩展插件
编写编写 Qt 自身扩展插件实际上是通过子类化适当的插件基类、实现一些函数和添加宏来实现的。
Qt 提供了很多插件基类。派生插件默认存储在标准插件目录的子目录中。如果插件没有存储在适当的目录中,Qt 将无法找到并加载使用它们。
下表罗列了 Qt 目前(注:本文使用的版本是 Qt 5.12)提供的插件基类。其中有些类是私有的,因此没有文档记录。我们也可以使用它们,但是不能保证与以后的 Qt 版本兼容。
基类 | 目录名称 | Qt 模块 | Key 的大小写敏感性 |
---|---|---|---|
QAccessibleBridgePlugin | accessiblebridge | Qt GUI | 区分大小写 |
QImageIOPlugin | imageformats | Qt GUI | 区分大小写 |
QPictureFormatPlugin (废弃的) | pictureformats | Qt GUI | 区分大小写 |
QAudioSystemPlugin | audio | Qt Multimedia | 不区分大小写 |
QDeclarativeVideoBackendFactoryInterface | video/declarativevideobackend | Qt Multimedia | 不区分大小写 |
QGstBufferPoolPlugin | video/bufferpool | Qt Multimedia | 不区分大小写 |
QMediaPlaylistIOPlugin | playlistformats | Qt Multimedia | 不区分大小写 |
QMediaResourcePolicyPlugin | resourcepolicy | Qt Multimedia | 不区分大小写 |
QMediaServiceProviderPlugin | mediaservice | Qt Multimedia | 不区分大小写 |
QSGVideoNodeFactoryPlugin | video/videonode | Qt Multimedia | 不区分大小写 |
QBearerEnginePlugin | bearer | Qt Network | 区分大小写 |
QPlatformInputContextPlugin | platforminputcontexts | Qt Platform Abstraction | 不区分大小写 |
QPlatformIntegrationPlugin | platforms | Qt Platform Abstraction | 不区分大小写 |
QPlatformThemePlugin | platformthemes | Qt Platform Abstraction | 不区分大小写 |
QGeoPositionInfoSourceFactory | position | Qt Positioning | 区分大小写 |
QPlatformPrinterSupportPlugin | printsupport | Qt Print Support | 不区分大小写 |
QSGContextPlugin | scenegraph | Qt Quick | 区分大小写 |
QScriptExtensionPlugin | script | Qt Script | 区分大小写 |
QSensorGesturePluginInterface | sensorgestures | Qt Sensors | 区分大小写 |
QSensorPluginInterface | sensors | Qt Sensors | 区分大小写 |
QSqlDriverPlugin | sqldrivers | Qt SQL | 区分大小写 |
QIconEnginePlugin | iconengines | Qt SVG | 不区分大小写 |
QAccessiblePlugin | accessible | Qt Widgets | 区分大小写 |
QStylePlugin | styles | Qt Widgets | 不区分大小写 |
现在,假设我们有一个叫做 MyStyle 的新样式类,我们想把它当做一个插件来使用,那么这个类需要定义成如下(mystyleplugin.h)形式:
class MyStylePlugin : public QStylePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "mystyleplugin.json")
public:
QStyle *create(const QString &key);
};
类位于 mystyleplugin.cpp 文件中的实现:
#include "mystyleplugin.h"
QStyle *MyStylePlugin::create(const QString &key)
{
if (key.toLower() == "mystyle")
return new MyStyle;
return 0;
}
(注意,QStylePlugin 是大小写不敏感的,在我们的 create() 实现中使用了 key 的小写版本;大多数其他插件都是区分大小写的。)
此外,大多数插件都需要一个包含描述插件的元数据的 json 文件(mystyleplugin.json)。对于本例中的样式插件,它只是包含了一个可以由该插件创建的样式列表:
{ "Keys": [ "mystyleplugin" ] }
json 文件中需要提供的信息类型依赖于插件,有关文件中需要包含的信息的详细信息,请参阅插件基类的类文档。
对于数据库驱动程序、图像格式、文本编解码器和大多数其他插件类型,不需要显式的对象创建。Qt 将根据需要查找并创建它们。样式是一个例外,因为我们可能希望在代码中显式地设置样式。要应用样式,可以这样使用代码:
QApplication::setStyle(QStyleFactory::create("MyStyle"));
一些插件类需要额外的函数来实现。有关每种插件类型必须重新实现的虚拟函数的详细信息,请参阅插件基类的类文档。
链接 Style 插件实例 展示了如何实现扩展 QStylePlugin 基类的插件。
低级 API:扩展 Qt 应用程序
不仅 Qt 本身,Qt 应用程序也可以通过插件进行扩展。这需要应用程序使用 QPluginLoader 检测和加载插件。在这种情况下,插件可以提供任意的功能,而不局限于数据库驱动程序、图像格式、文本编解码器、样式以及扩展 Qt 功能的其他类型的插件。
通过插件使应用程序可扩展涉及以下步骤:
- 定义一组用于与插件交互的接口(只有纯虚函数的类)。
- 使用 Q_DECLARE_INTERFACE() 宏告诉 Qt 的元对象系统我们定义的这个接口。
- 在应用程序中使用 QPluginLoader 来加载插件。
- 使用 qobject_cast() 测试插件是否实现了给定的接口。
编写插件包括以下步骤:
- 声明一个插件类,该类继承自 QObject 和插件想要提供的接口。
- 使用Q_INTERFACES()宏告诉 Qt 的元对象系统这些接口。
- 使用Q_PLUGIN_METADATA()宏导出插件。
- 使用合适的 .pro 文件构建插件。
例如,下面是接口类的定义:
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_OBJECT
Q_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);
};
Plug & Paint 实例 文档详细说明了这一过程。有关 Qt Designer 特有问题的信息,请参见为Qt Designer创建自定义小部件。我们还可以查看 Echo 插件实例,这是一个关于如何实现扩展 Qt 应用程序的插件的更简单的示例。请注意,在加载插件之前,QCoreApplication 必须已经初始化。
部署插件
Qt 应用程序自动知道哪些插件可用,因为插件存储在标准插件子目录中。因此,应用程序不需要任何代码来查找和加载插件,因为 Qt 会自动处理它们。
在开发过程中,插件的目录是 QTDIR/plugins (其中 QTDIR 是 Qt 安装的目录),每种类型的插件都位于该类型的子目录中,例如,styles。如果我们希望我们的应用程序使用插件,而我们又不希望使用标准的插件路径,请在安装过程确定我们希望为插件使用的路径,并保存路径,例如,通过使用 QSettings,以便应用程序在运行时读取。然后,应用程序可以通过 QCoreApplication::addLibraryPath() 使用此路径调用,这样我们的应用程序将使插件可用。注意,路径的最后一部分(例如 styles)不能更改。
如果我们希望插件是可加载的,那么一种方法是在应用程序下创建一个子目录,并将插件放在该目录中。如果我们发布任何与 Qt 一起发布的插件(位于 plugins 目录下的插件),我们必须将插件所在的 plugins 目录下的子目录复制到我们的应用程序根目录(注意,不包括 plugins 目录)。
有关部署的更多信息,请参见《部署 Qt 应用程序》和《部署插件》文档。
静态插件
在应用程序中包含插件的最常见和最灵活的方法是将其编译成一个动态库,这个动态库是单独发布的,并在运行时进行检测和加载。
插件可以静态地链接到我们的应用程序中。如果我们构建 Qt 的静态版本,这是包含 Qt 预定义插件的唯一选择。使用静态插件使部署不那么容易出错,但是有一个缺点,如果不完全重新构建和重新分发应用程序,就不能添加来自插件的功能。
要静态地链接插件,我们需要使用 QTPLUGIN 将所需的插件添加到构建中。
在我们的应用的 .pro 档案中,我们需要类似下列项目:
QTPLUGIN += qjpeg \
qgif \
qkrcodecs
qmake 会自动向 QTPLUGIN 中添加 Qt 模块通常需要的插件(参见QT),而更专业的插件则需要手动添加。自动添加插件的默认列表可以按类型覆盖。例如,链接最小的插件而不是默认的 Qt 平台适应插件,使用:
我的 安全中心 安全工具
QTPLUGIN.platforms = qminimal
如果你既不想要默认的,也不想要最小的QPA插件自动链接,使用:
QTPLUGIN.platforms = -
默认设置一般可以提供最佳的开箱即用体验,但是可能会不必要地使应用程序膨胀。建议检查由 qmake 构建的 linker 命令行,并消除不必要的插件。
链接静态插件的详细信息
为了使静态插件被实际链接和实例化,应用程序代码中也需要Q_IMPORT_PLUGIN() 宏,但这些宏是由 qmake 自动生成并添加到我们的应用程序项目中的。
如果我们不希望所有添加到 QTPLUGIN 的插件都被自动链接,请从配置变量中删除 import_plugins:
CONFIG -= import_plugins
创建静态插件
我们也可以创建自己的静态插件,通过以下步骤:
- 将 CONFIG += static 添加到插件的 .pro 文件中。
- 在应用程序中使用 Q_IMPORT_PLUGIN() 宏。
- 如果插件附带了 qrc 文件,那么在应用程序中使用 Q_INIT_RESOURCE()宏。
- 使用 .pro 文件中的 LIBS 将应用程序与插件库链接起来。
有关如何做到这一点,请参阅 Plug & Paint 实例 和相关的 基本工具 插件。
注意:如果我们没有使用 qmake 来构建我们的插件,那么我们需要确保定义了 QT_STATICPLUGIN 预处理器宏。
部署和调试插件
“部署插件”文档涵盖了使用应用程序部署插件并在出现问题时调试插件的过程。
参考 QPluginLoader、QLibrary 和 Plug & Paint 实例 。
网友评论