Введение
Помимо самого IID интерфейса, плагин может нести произвольные дополнительные метаданные — версию, автора, список поддерживаемых форматов файлов, требования к окружению — через JSON-блок, встроенный непосредственно в Q_PLUGIN_METADATA. Эти метаданные доступны через QPluginLoader::metaData() ещё до фактической загрузки и инстанцирования плагина — это позволяет приложению предварительно отфильтровать доступные плагины (например, по совместимости или категории) без накладных расходов на загрузку каждого из них в память. В статье разберём практическое использование метаданных для системы плагинов с категоризацией.
Концепция
JSON-метаданные указываются через дополнительный параметр FILE макроса Q_PLUGIN_METADATA, ссылающийся на отдельный .json-файл, содержимое которого встраивается в скомпилированную библиотеку как часть метаданных плагина. QPluginLoader::metaData() возвращает QJsonObject, где пользовательские данные из этого файла доступны внутри вложенного объекта "MetaData" — это разделение позволяет Qt хранить и собственные системные поля (IID, версия Qt, архитектура) на одном уровне, а произвольные пользовательские данные — на другом, не конфликтующем уровне.
Пример кода
// csvexportplugin.json — метаданные плагина, встраиваемые при сборке
{
"version": "1.2.0",
"author": "ООО МойКомпани",
"category": "export",
"supportedExtensions": ["csv", "tsv"],
"minimumAppVersion": "3.0.0"
}
// csvexportplugin.cpp — подключение JSON-метаданных к плагину
#include "iexportplugin.h"
class CsvExportPlugin : public QObject, public IExportPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID IExportPlugin_iid FILE "csvexportplugin.json")
Q_INTERFACES(IExportPlugin)
public:
QString formatName() const override { return "CSV таблица"; }
QString fileExtension() const override { return "csv"; }
bool canExport(const QVariantMap &) const override { return true; }
bool exportToFile(const QString &, const QByteArray &) override { return true; }
};
// pluginscanner.h — сканирование директории плагинов с предварительной фильтрацией
#pragma once
#include <QDir>
#include <QPluginLoader>
#include <QJsonObject>
#include <QVersionNumber>
#include <QDebug>
class PluginScanner
{
public:
struct PluginInfo
{
QString filePath;
QString category;
QVersionNumber version;
QStringList supportedExtensions;
};
QList<PluginInfo> scanDirectory(const QString &pluginsDir, const QString &requiredCategory)
{
QList<PluginInfo> result;
QDir dir(pluginsDir);
for (const QString &fileName : dir.entryList(QDir::Files)) {
const QString filePath = dir.absoluteFilePath(fileName);
QPluginLoader loader(filePath);
// Метаданные читаются БЕЗ фактической загрузки кода плагина в память —
// это быстрая, "дешёвая" операция по сравнению с loader.instance()
const QJsonObject metaData = loader.metaData();
if (metaData.isEmpty()) {
continue; // не Qt-плагин вовсе
}
const QJsonObject userData = metaData.value("MetaData").toObject();
const QString category = userData.value("category").toString();
if (category != requiredCategory) {
continue; // фильтрация по категории без загрузки кода
}
PluginInfo info;
info.filePath = filePath;
info.category = category;
info.version = QVersionNumber::fromString(userData.value("version").toString());
for (const auto &ext : userData.value("supportedExtensions").toArray()) {
info.supportedExtensions.append(ext.toString());
}
result.append(info);
}
return result;
}
};
Пояснения к коду
JSON-файл csvexportplugin.json лежит рядом с исходным кодом плагина и подключается через FILE "csvexportplugin.json" внутри Q_PLUGIN_METADATA — на этапе компиляции содержимое файла встраивается в бинарный плагин, и далее доступно через рефлексию метаданных без необходимости в исходном .json-файле на диске после сборки (он нужен только во время компиляции).
PluginScanner::scanDirectory() демонстрирует ключевое практическое преимущество JSON-метаданных: фильтрация по category происходит на основе loader.metaData(), вызов которого не требует фактической загрузки кода библиотеки в память процесса (в отличие от loader.instance(), который выполняет полную загрузку и инициализацию) — это позволяет эффективно просканировать директорию с большим числом плагинов разных категорий и инициализировать только те, что реально нужны в текущем контексте, не тратя время и память на загрузку нерелевантных.
Подводные камни
- Различие между системными полями метаданных Qt и пользовательскими данными внутри
"MetaData". Начинающие разработчики иногда пытаются прочитать своё пользовательское поле напрямую из корняQJsonObject, возвращённогоmetaData(), без обращения к вложенному объекту"MetaData"— и получают пустое значение, поскольку Qt явно разделяет собственные системные поля (IID,className,versionсамого Qt) от пользовательских данных файла, указанного черезFILE. - Несинхронизация версии в JSON-метаданных (
"version": "1.2.0") с реальной версией кода плагина. Поскольку это просто текстовое поле в отдельном файле, ничто не гарантирует автоматическую синхронизацию с фактической версией собранного бинарника — если процесс релиза плагинов не автоматизирован (например, через генерацию JSON-файла на основе единого источника версии в системе сборки), эти два значения легко рассинхронизировать вручную. - Отсутствие проверки
minimumAppVersionв показанном коде сканера. Метаданные содержат поле, заявляющее минимальную версию приложения, необходимую для совместимости плагина, ноPluginScannerв примере его не проверяет — для полноценной защиты от загрузки плагина, разработанного под более новую версию API приложения, чем фактически установлена, нужна явная проверка этого поля перед попыткой инстанцирования плагина черезloader.instance(). - JSON-файл метаданных, ссылающийся на несуществующий путь или содержащий синтаксически некорректный JSON, приводит к ошибке на этапе компиляции плагина (поскольку Qt разбирает и встраивает этот файл при сборке) — но если ошибка в самом JSON-синтаксисе незначительна и компилятор её каким-то образом пропускает, итоговые метаданные плагина в рантайме могут оказаться частично пустыми, что обнаружится только при попытке прочитать конкретное ожидаемое поле через
metaData().