警告

本节包含从 C++ 自动翻译到 Python 的片段,可能包含错误。

如何创建 Qt 插件#

创建扩展 Qt 应用程序和功能的插件的指南。

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

  • 一个高级 API,用于编写扩展 Qt 自身,例如自定义数据库驱动程序、图像格式、文本编解码器和自定义样式。

  • 一个低级 API,用于扩展 Qt 应用程序。

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

由于高级 API 是建立在低级 API 之上的,因此一些问题在两者之间是通用的。

如需为 Qt Designer 提供插件,请参阅创建自定义小部件插件。

高级 API:编写 Qt 扩展#

通过子类化合适的插件基类,实现一些函数和添加一个宏来创建扩展 Qt 自身的插件。

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

下表总结了插件基类。其中一些类是私有的,因此没有文档。您可以使用它们,但无法保证与后续的 Qt 版本的兼容性。

基类

目录名

Qt 模块

键敏感度

QAccessibleBridgePlugin

accessiblebridge

Qt GUI

敏感

QImageIOPlugin

imageformats

Qt GUI

敏感

QPictureFormatPlugin (已废弃)

pictureformats

Qt GUI

敏感

QBearerEnginePlugin

bearer

Qt Network

敏感

QPlatformInputContextPlugin

platforminputcontexts

Qt 平台抽象

不敏感

QPlatformIntegrationPlugin

platforms

Qt 平台抽象

不敏感

QPlatformThemePlugin

platformthemes

Qt 平台抽象

不敏感

QPlatformPrinterSupportPlugin

printsupport

Qt 打印支持

不敏感

QSGContextPlugin

scenegraph

Qt Quick

敏感

QSqlDriverPlugin

sqldrivers

Qt SQL

敏感

QIconEnginePlugin

iconengines

Qt SVG

不敏感

QAccessiblePlugin

accessible

Qt Widgets

敏感

QStylePlugin

styles

Qt Widgets

不敏感

如果您有一个名为 JsonViewer 的新文档查看器类,您想要将其作为插件提供,则需要将该类定义为如下(jsonviewer.h

class JsonViewer(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
    openJsonFile = bool()
    m_tree = QTreeView()
    m_toplevel = None
    m_root = QJsonDocument()
m_searchKey = QPointer()

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

def __init__(self):

    self.uiInitialized.connect(self.setupJsonUi)

def init(self, file, parent, mainWindow):

    AbstractViewer::init(file, QTreeView(parent), mainWindow)
    m_tree = QTreeView(widget())

另外,还需要一个包含描述插件的元数据的 json 文件(jsonviewer.json),对于大多数插件,它只需包含查看器插件名称即可。

{ "Keys": [ "jsonviewer" ] }

在 json 文件中需要提供的信息类型取决于插件类型。有关文件中应包含的信息的详细信息,请参阅类文档。

对于数据库驱动程序、图像格式、文本编解码器和大多数其他插件类型,不需要显式创建对象。Qt 会根据需要找到并创建它们。

插件类可能需要实现额外的函数。有关必须重写的虚函数的详细信息,请参阅类文档。

文档查看器演示展示了如何实现一个显示器文件结构化内容的插件。因此,每个插件都必须重写虚函数,这些虚函数

  • 用于识别插件

  • 返回它支持的MIME类型

  • 通知是否有内容可以显示

  • 以及内容如何展示

QString viewerName() override { return QLatin1StringView(staticMetaObject.className()); }
QStringList supportedMimeTypes() override
bool hasContent() override
bool supportsOverview() override { return True; }

低级API:扩展Qt应用程序#

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

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

  1. 定义一组接口(只有纯虚函数的类)用于与插件通信。

  2. 使用Q_DECLARE_INTERFACE()宏告诉Qt的元对象系统关于接口的信息。

  3. 在应用程序中使用QPluginLoader加载插件。

  4. 使用qobject_cast()检查插件是否实现了给定的接口。

编写插件涉及以下步骤

  1. 声明一个插件类,它继承自QObject以及插件希望提供的接口。

  2. 使用Q_INTERFACES()宏告诉Qt的元对象系统关于接口的信息。

  3. 使用Q_PLUGIN_METADATA()宏导出插件。

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

class ViewerInterface(AbstractViewer):

# public
    virtual ~ViewerInterface() = default

下面是接口声明

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

有关特定于Qt设计师的问题的信息,请参阅创建自定义小部件。

定位插件#

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

在开发期间,插件目录为QTDIR/plugins(其中QTDIR是Qt安装的目录),每种类型的插件都有一个该类型的子目录,例如,styles。如果您想使应用程序使用插件并且不想使用标准插件路径,请让安装程序确定要用于插件的路径,并通过使用QSettings等方法将该路径保存下来,以便应用程序在运行时读取。然后,应用程序可以调用QCoreApplication::addLibraryPath()传入此路径,您的插件将可用。请注意,路径的最后一部分(例如,styles)不能更改。

如果您想使插件可加载,一种方法是创建应用程序下的一个子目录,并将插件放置在该目录中。如果您分发任何与Qt一起提供的插件(在plugins目录中定位到的插件),则必须将位于plugins下的子目录复制到应用程序的根目录中(即不包括plugins目录)。

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

静态插件#

将插件包含到应用程序中的正常和最灵活的方式是将它编译成一个动态库,该库作为单独的组件提供,并在运行时检测和加载。

可以将插件静态链接到您的应用程序中。如果您构建了 Qt 的静态版本,这是包含 Qt 预定义插件唯一的选项。使用静态插件可以使部署更不易出错,但缺点是不能在不重建和重新分发整个应用程序的情况下添加任何插件的功能。

CMake 和 qmake 会自动添加通常需要的 Qt 模块所需的插件,而更专业的插件则需要手动添加。可以按类型覆盖默认添加的插件列表。

默认设置旨在提供最佳开箱即用的体验,但也可能导致应用程序不必要地膨胀。建议检查链接器命令行并删除不必要的插件。

为了使静态插件真正被链接和实例化,还需要在应用程序代码中使用 Q_IMPORT_PLUGIN() 宏,但这些通常由构建系统自动生成并添加到您的应用程序项目中。

CMake 中的静态插件导入#

要在一个 CMake 项目中静态链接插件,需要调用 qt_import_plugins 命令。

例如,默认情况下,Linux 的 libinput 插件不会导入。以下命令可以导入它:

qt_import_plugins(myapp INCLUDE Qt::QLibInputPlugin)

要用最小平台集成插件而不是默认的 Qt 平台适配插件进行链接,请使用:

qt_import_plugins(myapp
    INCLUDE_BY_TYPE platforms Qt::MinimalIntegrationPlugin
)

另一个典型用例是仅链接一组特定的 imageformats 插件

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

如果要防止链接任何 imageformats 插件,请使用:

qt_import_plugins(myapp
    EXCLUDE_BY_TYPE imageformats
)

如果要关闭添加任何默认插件,请使用 qt_import_plugins 的 NO_DEFAULT 选项。

qmake 中的静态插件导入#

在 qmake 项目中,需要使用 QTPLUGIN 将所需的插件添加到构建中。

QTPLUGIN += qlibinputplugin

例如,要链接最小插件而不是默认的 Qt 平台适配插件,请使用:

QTPLUGIN.platforms = qminimal

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

QTPLUGIN.platforms = -

如果您不希望将所有插件自动添加到 QTPLUGIN,请将 import_pluginsCONFIG 变量中删除。

CONFIG -= import_plugins

创建静态插件#

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

  1. STATIC 选项传递到您的 CMakeLists.txt 中的 qt_add_plugin 命令。对于 qmake 项目,请在您的插件的 .pro 文件中添加 CONFIG += static

  2. 在您的应用程序中使用 Q_IMPORT_PLUGIN() 宏。

  3. 如果插件提供 qrc 文件,请在您的应用程序中使用 Q_INIT_RESOURCE() 宏。

  4. 使用您的 CMakeLists.txt 中的 target_link_libraries 或您的 .pro 文件中的 LIBS 将您的应用程序与插件库链接。

查看Plug & Paint 示例和相关基本工具插件,以了解如何进行此操作。

注意

如果您不是使用 CMake 或 qmake 构建插件,请确保预处理器宏 QT_STATICPLUGIN 已定义。

加载插件#

插件类型(静态或动态)和操作系统需要特定的方法来定位和加载插件。实现一个加载插件的抽象很有用。

def loadViewerPlugins(self):

    if not m_viewers.isEmpty():
        return

QPluginLoader::staticInstances() 返回一个 QObjectList,其中包含指向每个静态链接插件的指针

# Load static plugins
QObjectList staticPlugins = QPluginLoader.staticInstances()
for plugin in staticPlugins:
    addViewer(plugin)

共享插件位于其部署目录中,可能需要操作系统特定的处理。

    # Load shared plugins
    pluginsDir = QDir(QApplication.applicationDirPath())
#if defined(Q_OS_WINDOWS)
    pluginsDir.cd("app")
#elif defined(Q_OS_DARWIN)
    if pluginsDir.dirName() == "MacOS":
        pluginsDir.cdUp()
        pluginsDir.cdUp()
        pluginsDir.cdUp()

#endif
    entryList = pluginsDir.entryList(QDir.Files)
    for fileName in entryList:
        loader = QPluginLoader(pluginsDir.absoluteFilePath(fileName))
        plugin = loader.instance()
        if plugin:
            addViewer(plugin)
#if 0
else:
            print(loader.errorString())
#endif

部署和调试插件#

部署插件 文档涵盖了将插件与应用程序一起部署以及出现问题时进行调试的过程。

另请参阅

QPluginLoaderQLibrary