Введение
Помимо плагинов общего назначения, рассмотренных в предыдущих статьях, у Qt Quick есть собственный специализированный механизм плагинов — QQmlExtensionPlugin, позволяющий упаковать набор QML-типов (C++ классы, зарегистрированные для использования в QML) в отдельный, динамически подключаемый модуль, импортируемый в QML через обычный import. В статье разберём, как создать такой плагин и почему этот механизм отличается от общего QPluginLoader.
Концепция
QQmlExtensionPlugin — специализированный базовый класс именно для QML-модулей: вместо ручной загрузки через QPluginLoader и явного qobject_cast, движок QML сам обнаруживает и загружает такие плагины при обработке директивы import в QML-файле, если модуль с соответствующим именем установлен в одном из путей поиска QML-модулей (QML_IMPORT_PATH или стандартные системные пути). Современный подход (Qt 6, с CMake-функцией qt_add_qml_module) автоматизирует значительную часть ручной работы по сравнению с более старым, полностью ручным API QQmlExtensionPlugin.
Пример кода
// temperaturesensor.h — C++ тип, который будет доступен в QML после импорта модуля
#pragma once
#include <QObject>
#include <QQmlEngine>
class TemperatureSensor : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(double celsius READ celsius NOTIFY celsiusChanged)
public:
explicit TemperatureSensor(QObject *parent = nullptr) : QObject(parent) {}
double celsius() const { return m_celsius; }
Q_INVOKABLE void simulateReading(double value)
{
m_celsius = value;
emit celsiusChanged();
}
signals:
void celsiusChanged();
private:
double m_celsius = 0.0;
};
# CMakeLists.txt — плагин QML-модуля через современный API Qt 6
find_package(Qt6 REQUIRED COMPONENTS Qml)
qt_standard_project_setup()
qt_add_qml_module(SensorsPlugin
URI Hardware.Sensors
VERSION 1.0
SHARED # компилируется как динамический плагин
SOURCES
temperaturesensor.h
QML_FILES
SensorPanel.qml # можно также добавлять обычные QML-компоненты в модуль
)
// SensorPanel.qml — QML-компонент, включённый в тот же модуль плагина
import QtQuick
Rectangle {
id: root
width: 200; height: 80
property alias sensor: temperatureSensor
TemperatureSensor { id: temperatureSensor }
Text {
anchors.centerIn: parent
text: temperatureSensor.celsius.toFixed(1) + " °C"
font.pixelSize: 24
}
}
// Использование модуля из любого QML-приложения, установившего этот плагин
import QtQuick
import Hardware.Sensors 1.0
Window {
visible: true
width: 300; height: 200
SensorPanel {
id: panel
anchors.centerIn: parent
}
Timer {
interval: 1000; running: true; repeat: true
onTriggered: panel.sensor.simulateReading(20 + Math.random() * 5)
}
}
Пояснения к коду
qt_add_qml_module(... SHARED ...) — современный, декларативный способ описать QML-модуль как динамический плагин: CMake автоматически генерирует весь необходимый boilerplate-код регистрации (включая реализацию QQmlExtensionPlugin «под капотом», без необходимости писать её вручную), на основе указанных SOURCES (C++ классы с QML_ELEMENT) и QML_FILES (обычные QML-компоненты, тоже включаемые в состав модуля как часть единого пакета).
SensorPanel.qml демонстрирует, что QML-модуль не обязан состоять исключительно из C++ типов — он может включать готовые, написанные на QML компоненты, использующие эти C++ типы внутри себя, что даёт конечным потребителям модуля более высокоуровневый, готовый к использованию виджет, а не только «сырые» примитивы данных. Применение модуля в конечном приложении выглядит как обычный import Hardware.Sensors 1.0, технически не отличимый для пишущего QML-код разработчика от использования стандартных модулей самого Qt Quick.
Подводные камни
- Путь установки скомпилированного QML-плагина должен точно соответствовать структуре, которую ожидает движок QML для разрешения
import(директория, названная в соответствии с URI модуля, содержащая сам бинарный плагин и файлqmldir, описывающий типы модуля) — несоответствие структуры путей приводит к ошибке «module not found» при попытке импорта, даже если сам плагин технически скомпилирован успешно. - Версия модуля в
import Hardware.Sensors 1.0должна совпадать с версией, объявленной вVERSION 1.0при сборке плагина — попытка импортировать несуществующую или несовместимую версию модуля завершится ошибкой на этапе загрузки QML, аналогично версионированию обычных Qt-модулей. - Смешивание QML-плагинов (через
QQmlExtensionPlugin/qt_add_qml_module) с общими C++ плагинами (черезQPluginLoader) для разных целей в одном приложении требует понимания, что это два разных, не пересекающихся механизма с разными жизненными циклами загрузки — QML-плагины загружаются движком QML при разбореimport, тогда как обычные плагины (статьи 180–186) загружаются явно вызываемым кодом приложения; путаница между этими двумя подходами — частый источник недопонимания у разработчиков, впервые работающих с расширяемыми Qt Quick приложениями. - Статическая линковка QML-плагина (
STATICвместоSHAREDвqt_add_qml_module) меняет модель его использования аналогично статическим плагинам общего назначения (статья 182) — типы становятся частью основного бинарника без отдельного файла на диске, что подходит для встраиваемых сценариев или приложений с фиксированным набором модулей, но не позволяет конечному пользователю устанавливать новые QML-модули независимо от пересборки приложения.