警告

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

如何创建Qt插件

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

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

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

  • 用于扩展Qt应用程序的低级API。

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

由于高级API是建立在低级API之上的,因此一些问题对两者都是常见的。

如果你想提供用于Qt Widgets Designer的插件,请参阅创建自定义小部件插件。

高级API:编写Qt扩展

编写一个扩展Qt本身的插件是通过继承适当的插件基类,实现一些函数,并添加一个宏来完成的。

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

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

基类

目录名称

Qt 模块

键值大小写敏感

QAccessibleBridgePlugin

accessiblebridge

Qt GUI

区分大小写

QImageIOPlugin

imageformats

Qt GUI

区分大小写

QPictureFormatPlugin (已过时)

pictureformats

Qt GUI

区分大小写

QBearerEnginePlugin

bearer

Qt 网络

区分大小写

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 小部件

区分大小写

QStylePlugin

styles

Qt 小部件

不区分大小写

如果你有一个新的文档查看器类叫做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 Widgets Designer创建自定义小部件,了解特定于Qt Widgets Designer的问题信息。

定位插件

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的所有插件自动链接,请从CONFIG变量中删除import_plugins

CONFIG -= import_plugins

创建静态插件

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

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

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

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

  4. 使用target_link_libraries在你的CMakeLists.txt.pro文件中的LIBS将你的应用程序与你的插件库链接起来。

有关如何执行此操作的详细信息,请参阅Plug & Paint示例和相关的Basic Tools插件。

注意

如果您没有使用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