如何创建 Qt 插件

Qt 为创建插件提供了 2 个 API:

  • A high-level API for writing extensions to Qt itself, such as custom database drivers, image formats, text codecs, and custom styles.
  • 用于扩展 Qt 应用程序的低级 API。

例如:若想要编写自定义 QStyle 子类并让 Qt 应用程序动态加载它,将使用更高级 API。

由于更高级 API 建立在更低级 API 之上,因此有一些问题是两者公共的。

If you want to provide plugins for use with Qt Designer ,见 Creating Custom Widget Plugins .

高级 API:编写 Qt 扩展

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

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

下表汇总了插件基类。某些类是私有的,因此未文档化。可以使用它们,但不承诺兼容更高 Qt 版本。

基类 目录名 Qt 模块 键区分大小写
QAccessibleBridgePlugin accessiblebridge Qt GUI 区分大小写
QImageIOPlugin imageformats Qt GUI 区分大小写
QPictureFormatPlugin (obsolete) pictureformats Qt GUI 区分大小写
QBearerEnginePlugin bearer Qt Network 区分大小写
QPlatformInputContextPlugin platforminputcontexts Qt Platform Abstraction 不区分大小写
QPlatformIntegrationPlugin platforms Qt Platform Abstraction 不区分大小写
QPlatformThemePlugin platformthemes Qt Platform Abstraction 不区分大小写
QPlatformPrinterSupportPlugin printsupport Qt Print Support 不区分大小写
QSGContextPlugin scenegraph Qt Quick 区分大小写
QSqlDriverPlugin sqldrivers Qt SQL 区分大小写
QIconEnginePlugin iconengines Qt SVG 不区分大小写
QAccessiblePlugin accessible Qt Widgets 区分大小写
QStylePlugin styles Qt Widgets 不区分大小写

If you have a new document viewer class called JsonViewer 想要将其用作插件,则需要按以下方式定义类 ( jsonviewer.h ):

class JsonViewer : public ViewerInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface/1.0" FILE "jsonviewer.json")
    Q_INTERFACES(ViewerInterface)
public:
    JsonViewer();
    ~JsonViewer() override;
private:
    bool openJsonFile();
    QTreeView *m_tree;
    QListWidget *m_toplevel = nullptr;
    QJsonDocument m_root;
    QPointer<QLineEdit> m_searchKey;
};
					

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

JsonViewer::JsonViewer()
{
    connect(this, &AbstractViewer::uiInitialized, this, &JsonViewer::setupJsonUi);
}
void JsonViewer::init(QFile *file, QWidget *parent, QMainWindow *mainWindow)
{
    AbstractViewer::init(file, new QTreeView(parent), mainWindow);
    m_tree = qobject_cast<QTreeView *>(widget());
}
					

此外,JSON 文件 ( jsonviewer.json ) containing meta data describing the plugin is required for most plugins. For document viewer plugins it simply contains the name of the viewer plugin.

{ "Keys": [ "jsonviewer" ] }
					

The type of information that needs to be provided in the json file is plugin dependent. See the class documentation for details on the information that needs to be contained in the file.

For database drivers, image formats, text codecs, and most other plugin types, no explicit object creation is required. Qt will find and create them as required.

Plugin classes may require additional functions to be implemented. See the class documentation for details of the virtual functions that must be reimplemented for each type of plugin.

The Document Viewer Demo shows how to implement a plugin that displayes structured contents of a file. Each plugin therefore reimplements virtual functions, which

  • identify the plugin
  • return the MIME types it supports
  • inform whether there is content to display and
  • how contents are presented
    QString viewerName() const override { return QLatin1StringView(staticMetaObject.className()); };
    QStringList supportedMimeTypes() const override;
    bool hasContent() const override;
    bool supportsOverview() const override { return true; }
					

低级 API:扩展 Qt 应用程序

In addition to Qt itself, Qt applications can be extended through plugins. This requires the application to detect and load plugins using QPluginLoader . In that context, plugins may provide arbitrary functionality and are not limited to database drivers, image formats, text codecs, styles, and other types of plugins that extend Qt's functionality.

透过插件使应用程序可扩展,涉及以下步骤:

  1. 定义一组用于对话插件的接口 (仅具有纯虚函数的类)。
  2. 使用 Q_DECLARE_INTERFACE () 宏告诉 Qt 的 元对象系统 关于接口。
  3. 使用 QPluginLoader 在应用程序中加载插件。
  4. 使用 qobject_cast () 测试插件是否有实现给定接口。

编写插件涉及这些步骤:

  1. 声明插件类继承 QObject 及插件想要提供的接口。
  2. 使用 Q_INTERFACES () 宏告诉 Qt 的 元对象系统 关于接口。
  3. 导出插件使用 Q_PLUGIN_METADATA () 宏。

例如,这里是接口类的定义:

class ViewerInterface : public AbstractViewer
{
public:
    virtual ~ViewerInterface() = default;
};
					

Here's the interface declaration:

#define ViewerInterface_iid "org.qt-project.Qt.Examples.DocumentViewer.ViewerInterface/1.0"
Q_DECLARE_INTERFACE(ViewerInterface, ViewerInterface_iid)
					

另请参阅 创建自定义 Widget 为 Qt Designer for information about issues that are specific to Qt Designer .

定位插件

Qt applications automatically know which plugins are available, because plugins are stored in the standard plugin subdirectories. Because of this, applications don't require any code to find and load plugins, since Qt handles them automatically.

在开发期间,插件目录为 QTDIR/plugins (在哪里 QTDIR 是 Qt 的安装目录),各种类型的插件在该类型的子目录下,例如, styles 。若想要应用程序使用插件但又不想使用标准插件路径,让安装进程确定想要使用的插件路径并保存路径,例如,通过使用 QSettings ,供应用程序运行时读取。然后,应用程序可以调用 QCoreApplication::addLibraryPath () 采用此路径,您的插件将可用于应用程序。注意,最后部分的路径 (例如, styles ) 无法更改。

If you want the plugin to be loadable, one approach is to create a subdirectory under the application, and place the plugin in that directory. If you distribute any of the plugins that come with Qt (the ones located in the plugins 目录),必须拷贝其子目录在 plugins 插件,位于应用程序根文件夹下 (即:不包括 plugins 目录)。

有关部署的更多信息,见 部署 Qt 应用程序 and 部署插件 文档编制。

静态插件

将插件包括在应用程序中的正常且最灵活方式,是将其编译成单独随附的动态库,并在运行时检测并加载。

可以将插件静态链接到应用程序。若构建静态版本的 Qt,这是包括 Qt 预定义插件的唯一选项。使用静态插件可使部署不易出错,但有缺点:无法添加插件功能,当不完整重新构建和重新分发应用程序时。

CMake and qmake automatically add the plugins that are typically needed by the Qt modules that are used, while more specialized plugins need to be added manually. The default list of automatically added plugins can be overridden per type.

The defaults are tuned towards an optimal out-of-the-box experience, but may unnecessarily bloat the application. It is recommended to inspect the linker command line and eliminate unnecessary plugins.

为促使实际链接并实例化静态插件, Q_IMPORT_PLUGIN () macros are also needed in application code, but those are automatically generated by the build system and added to your application project.

在 CMake 中导入静态插件

To statically link plugins in a CMake project, you need to call the qt_import_plugins 命令。

For example, the Linux libinput plugin is not imported by default. The following command imports it:

qt_import_plugins(myapp INCLUDE Qt::QLibInputPlugin)
					

To link the minimal platform integration plugin instead of the default Qt platform adaptation plugin, use:

qt_import_plugins(myapp
    INCLUDE_BY_TYPE platforms Qt::MinimalIntegrationPlugin
)
					

Another typical use case is to link only a certain set of imageformats plugins:

qt_import_plugins(myapp
    INCLUDE_BY_TYPE imageformats Qt::QJpegPlugin Qt::QGifPlugin
)
					

If you want to prevent the linking of any imageformats plugin, use:

qt_import_plugins(myapp
    EXCLUDE_BY_TYPE imageformats
)
					

If you want to turn off the addition of any default plugin, use the NO_DEFAULT option of qt_import_plugins .

在 qmake 中导入静态插件

In a qmake project, you need to add the required plugins to your build using QTPLUGIN :

QTPLUGIN += qlibinputplugin
					

For example, to link the minimal plugin instead of the default Qt platform adaptation plugin, use:

QTPLUGIN.platforms = qminimal
					

若不想链接默认,也不想自动链接 minimal QPA 插件,使用:

QTPLUGIN.platforms = -
					

若不想要添加到 QTPLUGIN 的所有插件被自动链接,移除 import_plugins CONFIG 变量:

CONFIG -= import_plugins
					

创建静态插件

It is also possible to create your own static plugins by following these steps:

  1. 传递 STATIC 选项到 qt_add_plugin command in your CMakeLists.txt . For a qmake project, add CONFIG += static 到插件的 .pro 文件。
  2. 使用 Q_IMPORT_PLUGIN () 宏在应用程序中。
  3. 使用 Q_INIT_RESOURCE () 宏在应用程序中,若插件随附 qrc 文件。
  4. 链接应用程序与插件库,使用 target_link_libraries 在您的 CMakeLists.txt or LIBS 在您的 .pro 文件。

插件和描绘 范例和关联的 基本工具 插件,了解如何做到这的有关细节。

注意: If you are not using CMake or qmake to build your plugin, you need to make sure that the QT_STATICPLUGIN 预处理器宏有定义。

Loading plugins

Plugin types (static or shared) and operating systems require specific approaches to locate and load plugins. It's useful to implement an abstraction for loading plugins.

void ViewerFactory::loadViewerPlugins()
{
    if (!m_viewers.isEmpty())
        return;
					

QPluginLoader::staticInstances () 返回 QObjectList with a pointer to each statically linked plugin

    // Load static plugins
    const QObjectList &staticPlugins = QPluginLoader::staticInstances();
    for (auto *plugin : staticPlugins)
        addViewer(plugin);
					

Shared plugins reside in their deployment directories, which may require OS-specific handling.

    // Load shared plugins
    QDir pluginsDir = QDir(QApplication::applicationDirPath());
#if defined(Q_OS_WINDOWS)
    pluginsDir.cd("app"_L1);
#elif defined(Q_OS_DARWIN)
    if (pluginsDir.dirName() == "MacOS"_L1) {
        pluginsDir.cdUp();
        pluginsDir.cdUp();
        pluginsDir.cdUp();
    }
#endif
    const auto entryList = pluginsDir.entryList(QDir::Files);
    for (const QString &fileName : entryList) {
        QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
        QObject *plugin = loader.instance();
        if (plugin)
            addViewer(plugin);
#if 0
        else
            qDebug() << loader.errorString();
#endif
    }
}
					

部署和调试插件

The 部署插件 文档涵盖采用应用程序部署插件和调试它们 (当出现问题时) 的过程。

另请参阅 QPluginLoader and QLibrary .