如何创建 Qt 插件

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

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

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

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

如果您想为 Qt Designer 提供插件,请参阅 创建自定义小部件插件

高级 API:编写 Qt 扩展

通过从合适的插件基类派生、实现一些函数和添加一个宏来编写扩展 Qt 自身的插件。

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

以下表格总结了插件基类。其中一些类是私有的,因此未进行文档说明。您可以使用它们,但不会保证与后续 Qt 版本的兼容性。

基类目录名称Qt 模块键_case_敏感
QAccessibleBridgePluginaccessiblebridgeQt GUICase Sensitive
QImageIOPluginimageformatsQt GUICase Sensitive
QPictureFormatPlugin (已弃用)pictureformatsQt GUICase Sensitive
QBearerEnginePluginbearerQt NetworkCase Sensitive
QPlatformInputContextPluginplatforminputcontextsQt 平台抽象Case Insensitive
QPlatformIntegrationPluginplatformsQt 平台抽象Case Insensitive
QPlatformThemePluginplatformthemesQt 平台抽象Case Insensitive
QPlatformPrinterSupportPluginprintsupportQt 打印支持Case Insensitive
QSGContextPluginscenegraphQt QuickCase Sensitive
QSqlDriverPluginsqldriversQt SQLCase Sensitive
QIconEnginePluginiconenginesQt SVGCase Insensitive
QAccessiblePluginaccessibleQt 小部件Case Sensitive
QStylePluginstylesQt 小部件Case Insensitive

如果您有一个名为 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),其中包含描述插件的元数据。对于文档查看器插件,它仅包含查看器插件的名称。

{ "Keys": [ "jsonviewer" ] }

json 文件需要提供的信息类型取决于插件。请参阅类文档,了解文件中需要包含的数据的详细信息。

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

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

文档查看器演示展示了如何实现一个显示文件结构化内容插件的实现方式。因此,每个插件都重新实现了虚拟函数,用于

  • 标识插件
  • 返回它支持的MIME类型
  • 通知是否有可显示的内容以及
  • 内容如何呈现
    QString viewerName() const override { return QLatin1StringView(staticMetaObject.className()); };
    QStringList supportedMimeTypes() const override;
    bool hasContent() const override;
    bool supportsOverview() const 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 : public AbstractViewer
{
public:
    virtual ~ViewerInterface() = default;
};

这里是接口声明

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

有关针对Qt Designer的特定问题,请参阅为Qt 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_pluginsNO_DEFAULT选项。

qmake中导入静态插件

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

QTPLUGIN += qlibinputplugin

例如,要在默认Qt平台适配插件而不是默认插件之间进行链接,使用以下命令:

QTPLUGIN.platforms = qminimal

如果既不希望默认,也不希望最小QPA插件自动链接,使用以下命令:

QTPLUGIN.platforms = -

如果您不希望将所有添加到QTPLUGIN的插件自动链接,请从CONFIG变量中删除import_plugins

CONFIG -= import_plugins

创建静态插件

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

  1. STATIC选项传递到您的CMakeLists.txt中的qt_add_plugin命令。对于qmake项目,将CONFIG += static添加到您的插件的.pro文件中。
  2. 在您的应用程序中使用Q_IMPORT_PLUGIN()宏。
  3. 如果您的插件包含qrc文件,在您的应用程序中使用Q_INIT_RESOURCE()宏。
  4. 使用CMakeLists.txt中的target_link_libraries或在您的.pro文件中的LIBS将您的应用程序与插件库链接。

查看Plug & Paint 示例及其相关的 基本工具 插件,以获取如何操作的详细信息。

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

加载插件

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

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

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

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

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

    // 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
    }
}

部署和调试插件

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

另见:QPluginLoaderQLibrary

© 2024 全权公司 Qt Ltd。本内含的贡献文档是各自所有者的版权。本提供的文档是根据自由软件基金会发布的 GNU 自由文档许可协议版本 1.3 许可的。Qt 和相关徽标是芬兰的全权公司 Ltd. 和/或世界其他国家的商标。所有其他商标均为各自所有者的财产。