目录
概述
Qt提供了两个用于创建插件的api:
- 一个高级API,用于编写Qt本身的扩展:自定义数据库驱动程序,图像格式,文本编解码器,自定义样式等。
- 用于扩展Qt应用程序的低级API。
例如,如果您想编写一个自定义的QStyle子类并让Qt应用程序动态加载它,那么您将使用高级的API。
由于高级API构建在低级API之上,因此有些问题对两者都是通用的。
如果你想为Qt Designer提供插件,请参阅Qt Designer模块文档。
高级API:编写Qt扩展
通过继承适当的插件基类、实现一些函数和添加宏,可以编写扩展Qt本身的插件。
有几个插件基类。派生插件默认存储在标准插件目录的子目录中。如果插件没有存储在适当的目录中,Qt将无法找到它们。
下表总结了插件基类。有些类是私有的,因此没有在文档中记录。你可以使用它们,但不能保证与以后的Qt版本兼容。
Base Class | Directory Name | Qt Module | Key Case Sensitivity |
---|---|---|---|
QAccessibleBridgePlugin | accessiblebridge |
Qt GUI | Case Sensitive |
QImageIOPlugin | imageformats |
Qt GUI | Case Sensitive |
QPictureFormatPlugin (obsolete) | pictureformats |
Qt GUI | Case Sensitive |
QAudioSystemPlugin | audio |
Qt Multimedia | Case Insensitive |
QDeclarativeVideoBackendFactoryInterface | video/declarativevideobackend |
Qt Multimedia | Case Insensitive |
QGstBufferPoolPlugin | video/bufferpool |
Qt Multimedia | Case Insensitive |
QMediaPlaylistIOPlugin | playlistformats |
Qt Multimedia | Case Insensitive |
QMediaResourcePolicyPlugin | resourcepolicy |
Qt Multimedia | Case Insensitive |
QMediaServiceProviderPlugin | mediaservice |
Qt Multimedia | Case Insensitive |
QSGVideoNodeFactoryPlugin | video/videonode |
Qt Multimedia | Case Insensitive |
QBearerEnginePlugin | bearer |
Qt Network | Case Sensitive |
QPlatformInputContextPlugin | platforminputcontexts |
Qt Platform Abstraction | Case Insensitive |
QPlatformIntegrationPlugin | platforms |
Qt Platform Abstraction | Case Insensitive |
QPlatformThemePlugin | platformthemes |
Qt Platform Abstraction | Case Insensitive |
QGeoPositionInfoSourceFactory | position |
Qt Positioning | Case Sensitive |
QPlatformPrinterSupportPlugin | printsupport |
Qt Print Support | Case Insensitive |
QSGContextPlugin | scenegraph |
Qt Quick | Case Sensitive |
QScriptExtensionPlugin | script |
Qt Script | Case Sensitive |
QSensorGesturePluginInterface | sensorgestures |
Qt Sensors | Case Sensitive |
QSensorPluginInterface | sensors |
Qt Sensors | Case Sensitive |
QSqlDriverPlugin | sqldrivers |
Qt SQL | Case Sensitive |
QIconEnginePlugin | iconengines |
Qt SVG | Case Insensitive |
QAccessiblePlugin | accessible |
Qt Widgets | Case Sensitive |
QStylePlugin | styles |
Qt Widgets | Case Insensitive |
如果你有一个新的样式类MyStyle,你想让它作为一个插件可用,类需要定义如下(mystyleplugin.h):
cpp
class MyStylePlugin : public QStylePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "mystyleplugin.json")
public:
QStringList keys() const;
QStyle *create(const QString &key);
};
确保类实现位于.cpp文件中:
cpp
#include "mystyleplugin.h"
QStringList MyStylePlugin::keys() const
{
return QStringList() << "mystyle";
}
QStyle *MyStylePlugin::create(const QString &key)
{
if (key.toLower() == "mystyle")
return new MyStyle;
return 0;
}
(注意,QStylePlugin是不区分大小写的,在我们的create()实现中使用小写版本的键;大多数其他插件是区分大小写的。)
此外,大多数插件都需要一个json文件(mystyleplugin.json),其中包含描述插件的元数据。对于样式插件,它只是包含一个可以由插件创建的样式列表:
cpp
{ "Keys": [ "mystyle" ] }
json文件中需要提供的信息类型依赖于插件,请参阅类文档了解文件中需要包含的信息的详细信息。
对于数据库驱动程序、图像格式、文本编解码器和大多数其他插件类型,不需要显式地创建对象。Qt会根据需要找到并创建它们。样式是个例外,因为你可能想在代码中显式地设置样式。要应用样式,请使用如下代码:
cpp
QApplication::setStyle(QStyleFactory::create("MyStyle"));
一些插件类需要实现额外的函数。请参阅类文档了解必须为每种类型的插件重新实现的虚函数的详细信息。
样式插件示例展示了如何实现扩展QStylePlugin基类的插件。
低级API:扩展Qt应用程序
不仅Qt本身可以通过插件进行扩展,Qt应用程序也可以通过插件进行扩展(插件是一个动态库)。为了确保插件不会丢失这要求应用程序使用QPluginLoader检测和加载插件。在这种情况下,插件可以提供任意功能,而且不限于数据库驱动、图像格式、文本编解码器、样式和其他扩展Qt功能的插件。
通过插件使应用程序具有可扩展性涉及以下步骤。
- 定义一组用于与插件通信的接口(只有纯虚函数的类)。
- 使用Q_DECLARE_INTERFACE()宏告诉Qt的元对象系统有关接口的信息。
- 在应用程序中使用QPluginLoader加载插件。
- 使用qobject_cast()测试插件是否实现了给定的接口。
编写插件需要以下步骤。
- 声明一个插件类,它继承QObject和插件想要提供的接口。
- 使用Q_INTERFACES()宏告诉Qt的元对象系统有关接口的信息。
- 使用Q_PLUGIN_METADATA()宏导出插件。
- 使用合适的。pro文件构建插件。
例如,下面是一个接口类的定义:interfaces.h
cpp
class FilterInterface
{
public:
virtual ~FilterInterface() {}
virtual QStringList filters() const = 0;
virtual QImage filterImage(const QString &filter, const QImage &image,
QWidget *parent) = 0;
};
QT_BEGIN_NAMESPACE
#define FilterInterface_iid "org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface/1.0"
//使用Q_DECLARE_INTERFACE宏让Qt的元对象系统意识到这个接口。我们这样做是为了在运行时识别实现接口的插件。第二个参数是一个字符串,必须以唯一的方式标识接口。
Q_DECLARE_INTERFACE(FilterInterface, FilterInterface_iid)
QT_END_NAMESPACE
下面是实现该接口的插件类的定义:
cpp
#include <QObject>
#include <QtPlugin>
#include <QStringList>
#include <QImage>
#include <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)
//Q_INTERFACES宏告诉Qt类实现了哪些接口。在我们的例子中,我们只实现了FilterInterface。如果一个类实现了多个接口,它们将以空格分隔的列表形式给出。Q_PLUGIN_METADATA宏包含在Q_OBJECT宏的旁边。它包含插件的IID和一个指向包含插件元数据的json文件的文件名。json文件被编译到插件中,不需要安装。
public:
QStringList filters() const;
QImage filterImage(const QString &filter, const QImage &image,
QWidget *parent);
};
extrafilters.json为空:
cpp
{}
.pro文件:生成动态插件到../../plugins
cpp
TEMPLATE = lib
CONFIG += plugin
QT += widgets
INCLUDEPATH += ../../app
HEADERS += extrafiltersplugin.h
HEADERS += interfaces.h
SOURCES = extrafiltersplugin.cpp
TARGET = $$qtLibraryTarget(pnp_extrafilters)
DESTDIR = ../../plugins
Plug & Paint Extra Filters Example | Qt Widgets 5.15.17
mainwindow加载控件
.pro文件:
cpp
QT += widgets
HEADERS = MainWindow.h \
interfaces.h
SOURCES = MainWindow.cpp \
main.cpp
cpp
void MainWindow::loadPlugins()
{
const auto staticInstances = QPluginLoader::staticInstances();
for (QObject *plugin : staticInstances)
auto filter = qobject_cast<FilterInterface *>(plugin);
pluginsDir = QDir(QCoreApplication::applicationDirPath());
#if defined(Q_OS_WIN)
if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release")
pluginsDir.cdUp();
#elif defined(Q_OS_MAC)
if (pluginsDir.dirName() == "MacOS") {
pluginsDir.cdUp();
pluginsDir.cdUp();
pluginsDir.cdUp();
}
#endif
pluginsDir.cd("plugins");
const auto entryList = pluginsDir.entryList(QDir::Files);
for (const QString &fileName : entryList) {
QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = loader.instance();
if (plugin) {
auto = qobject_cast<FilterInterface *>(plugin);
pluginFileNames += fileName;
}
}
}
Plug & Paint示例文档详细解释了这个过程。关于Qt Designer特有的问题的信息,请参见为Qt Designer创建自定义小部件。你也可以看一看Echo插件示例,这是一个关于如何实现扩展Qt应用程序的插件的更简单的示例。请注意,在加载插件之前,必须初始化QCoreApplication。
导出插件
要最终导出你的插件,你只需要在插件的头文件中添加Q_PLUGIN_METADATA()宏,就在Q_OBJECT()宏旁边。它必须包含插件的IID和可选的指向包含插件元数据的json文件的文件名。
cpp
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.BrushInterface" FILE "basictools.json")
加载插件
Qt应用程序自动知道哪些插件可用,因为插件存储在标准的插件子目录中。因为这个应用程序不需要任何代码来查找和加载插件,因为Qt会自动处理它们。
在开发过程中,插件的目录是QTDIR/plugins (其中QTDIR是Qt安装的目录),每种类型的插件都有一个对应类型的子目录,例如styles。如果希望应用程序使用插件,而不想使用标准的插件路径,请让安装过程确定插件要使用的路径,并保存该路径,例如通过使用QSettings,以便应用程序在运行时读取。然后应用程序可以使用此路径调用QCoreApplication::addLibraryPath(),你的插件将对应用程序可用。注意,路径的最后一部分(例如styles)不能更改。
如果你希望插件是可加载的,那么一种方法是在应用程序下创建一个子目录,并将插件放在该目录中。如果发布任何Qt附带的插件(位于plugins目录下的那些),必须将插件所在的plugins子目录复制到应用程序根目录下(即不包含plugins目录)。
有关部署的更多信息,请参阅部署Qt应用程序和部署插件的文档。
静态插件
在应用程序中包含插件的常规和最灵活的方式是将其编译为单独发布的动态库,并在运行时检测和加载。
插件可以静态链接到应用程序中。如果构建的是静态版本的Qt,这是包含Qt预定义插件的唯一选择。使用静态插件使部署不那么容易出错,但有一个缺点,即在不完全重新构建和重新分发应用程序的情况下,无法添加来自插件的功能。
要静态链接插件,需要使用QTPLUGIN将所需的插件添加到构建中。
在你的应用程序的.pro文件中,你需要以下条目:
cpp
QTPLUGIN += qjpeg \
qgif \
qkrcodecs
qmake会自动向QTPLUGIN中添加Qt模块通常需要的插件(参见Qt),而更专业的插件需要手动添加。每个类型都可以覆盖自动添加插件的默认列表。例如,要链接最小插件而不是默认的Qt平台适配插件,请使用:
cpp
QTPLUGIN.platforms = qminimal
如果你既不想默认的,也不想最小的QPA插件被自动链接,使用:
cpp
QTPLUGIN.platforms = -
默认值被调优为最佳的开箱即用体验,但可能会不必要地增加应用程序的体积。建议检查由qmake构建的链接器命令行,并删除不必要的插件
链接静态插件的详细信息
为了使静态插件真正被链接和实例化,应用程序代码中还需要Q_IMPORT_PLUGIN()宏,但这些宏是由qmake自动生成并添加到应用程序项目中的。
如果你不希望所有添加到QTPLUGIN的插件都自动链接,从CONFIG变量中删除import_plugins:
cpp
CONFIG -= import_plugins
创建/导入静态插件
你也可以按照以下步骤创建自己的静态插件。
- 将CONFIG += static添加到插件的.pro文件中。
- 在应用程序中使用Q_IMPORT_PLUGIN()宏。
- 如果插件提供qrc文件,请在应用程序中使用Q_INIT_RESOURCE()宏。
- 在。pro文件中使用LIBS链接应用程序和插件库。
插件创建pro:
cpp
TEMPLATE = lib
CONFIG += plugin static
QT += widgets
INCLUDEPATH += ../../app
HEADERS = basictoolsplugin.h
SOURCES = basictoolsplugin.cpp
TARGET = $$qtLibraryTarget(pnp_basictools)
DESTDIR = ../../plugins
导入静态插件:
cpp
TARGET = plugandpaint
DESTDIR = ..
QT += widgets
HEADERS = interfaces.h \
mainwindow.h \
paintarea.h \
plugindialog.h
SOURCES = main.cpp \
mainwindow.cpp \
paintarea.cpp \
plugindialog.cpp
LIBS = -L../plugins
macx-xcode {
LIBS += -lpnp_basictools$($${QMAKE_XCODE_LIBRARY_SUFFIX_SETTING})
} else {
android {
LIBS += -lpnp_basictools_$${QT_ARCH}
} else {
LIBS += -lpnp_basictools
}
if(!debug_and_release|build_pass):CONFIG(debug, debug|release) {
mac:LIBS = $$member(LIBS, 0) $$member(LIBS, 1)_debug
win32:LIBS = $$member(LIBS, 0) $$member(LIBS, 1)d
}
}
cpp
#include "mainwindow.h"
#include <QApplication>
#include <QtPlugin>
Q_IMPORT_PLUGIN(BasicToolsPlugin)
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
有关如何做到这一点的详细信息,请参阅Plug & Paint示例和相关的基本工具插件。
注意:如果您不使用qmake来构建插件,则需要确保定义了QT_STATICPLUGIN预处理器宏。
部署和调试插件
部署插件文档涵盖了使用应用程序部署插件并在出现问题时调试插件的过程。
如何为Qt或应用程序部署插件库,以便在运行时加载。如果使用静态插件,那么插件代码已经是应用程序可执行文件的一部分,不需要单独的部署步骤。
插件目录
在Qt中,当应用程序启动时,应用程序的可执行目录是Qt搜索插件的基本目录。
例如,在Windows上,如果应用程序在C:\Program Files\MyApp中,并且它有一个样式插件,Qt将在C:\Program Files\MyApp\styles.中查找。
要查找应用程序的可执行文件所在的位置,请参见QCoreApplication::applicationDirPath()。
Qt还会在QLibraryInfo::location(QLibraryInfo::PluginsPath)指定的目录中查找,该目录通常位于QTDIR/plugins;其中,QTDIR为Qt的安装目录。如果你想让Qt在其他地方查找,你可以通过调用QCoreApplication:: addlibraryypath()来添加任意多的路径。如果你想设置自己的路径,可以使用QCoreApplication:: setlibrarypath()。
或者,你可以使用Qt .conf文件来覆盖编译到Qt库中的硬编码路径。有关更多信息,请参见使用qt.conf。
另一种可能是在运行应用程序之前设置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构建的插件。
在构建扩展应用程序的插件时,**一定要确保插件的配置方式与应用程序的配置方式相同。这意味着如果应用程序是在release模式下构建的,那么插件也应该在release模式下构建。**Unix操作系统除外,在Unix操作系统中,插件系统不会加载以与应用程序不同的模式构建的插件。
如果你配置Qt在debug模式和release模式下构建的,但只在release模式下构建应用程序,你需要确保你的插件也在release模式下构建。默认情况下,如果Qt的debug版本可用,那么插件只会在调试模式下构建。要强制插件以release模式构建,在插件的项目(.pro)文件中添加以下代码行:
cpp
CONFIG += release
#debug_and_release
这确保了插件与应用程序中使用的库的版本兼容。
调试插件
有许多问题可能会阻止正确编写的插件与设计使用它们的应用程序一起工作。其中许多问题与构建插件和应用程序的方式不同有关,通常是由单独的构建系统和过程引起的。
要从Qt获得关于它试图加载的每个插件的诊断信息,请使用QT_DEBUG_PLUGINS环境变量。在启动应用程序的环境中将此变量设置为非零值。
下表描述了开发人员在创建插件时遇到的常见问题以及可能的解决方案。
Problem | Cause | Solution |
---|---|---|
即使应用程序直接打开插件,插件也会静默地加载失败。Qt Designer在其 Help|About Plugins dialog中显示了插件库,但不是所有插件都会列出来。 | 应用程序及其插件以不同的模式(release、debug)构建。 | 要么共享相同的构建信息,要么通过将debug_and_release附加到每个项目文件中的CONFIG变量中,以调试和发布模式构建插件。 |
See also QPluginLoader, QLibrary, and Plug & Paint Example.