如何創建 Qt 插件

Qt 為創建插件提供瞭 2 個 API:

  • 用於編寫 Qt 自身擴展的高級 API,譬如:自定義數據庫驅動程序、圖像格式、文本編解碼器、及自定義樣式。
  • 用於擴展 Qt 應用程序的低級 API。

例如:若想要編寫自定義 QStyle 子類並讓 Qt 應用程序動態加載它,將使用更高級 API。

由於更高級 API 建立在更低級 API 之上,因此有一些問題是兩者公共的。

若想要為用於 Qt Widgets Designer 提供插件,見 創建自定義 Widget 插件 .

高級 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;
    QMenu *m_jsonMenu = nullptr;
    QToolBar *m_jsonToolBar = nullptr;
    QAction *m_expandAllAction = nullptr;
    QAction *m_collapseAllAction = nullptr;
};
					

確保類實現位於 .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);
    setTranslationBaseName("jsonviewer"_L1);
    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)
					

另請參閱 為 Qt Widgets Designer 創建自定義 Widgets for information about issues that are specific to Qt Widgets 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 命令。

例如,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_pluginsCONFIG 變量:

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 預處理器宏有定義。

加載插件

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_DARWIN)
    if (pluginsDir.exists("../PlugIns"_L1)) { // installed build
        pluginsDir.cd("../PlugIns"_L1);
    } else {
        pluginsDir.cd("../../../../plugins"_L1); // non-installed build
    }
#elif defined(Q_OS_WIN)
    if (pluginsDir.exists("plugins"_L1)) { // non-installed build
        pluginsDir.cd("plugins"_L1);
    } else {
        pluginsDir.cd("../plugins"_L1); // installed build
    }
#else
    pluginsDir.cd("../plugins"_L1); // installed and non-installed build
#endif
    // qDebug("Loading plugins from %s...", qUtf8Printable(pluginsDir.path()));
    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 .