QtMiddleTechnical

Как QML интегрируется с C++ (выставление C++-классов в QML)?

C++-класс регистрируется в QML через qmlRegisterType() или макрос QML_ELEMENT. После этого Q_PROPERTY, Q_INVOKABLE-методы и сигналы класса становятся доступны в QML напрямую.

Интеграция QML с C++

Qt предоставляет несколько механизмов для того, чтобы C++-классы стали «первоклассными» объектами в QML. Выбор метода зависит от того, нужно ли создавать экземпляры типа прямо в QML или только обращаться к одному заранее созданному объекту.

Способ 1: qmlRegisterType (Qt 5 / совместимо с Qt 6)

Регистрирует класс как инстанцируемый тип QML. После регистрации можно писать MyClass { } прямо в .qml-файле.

// counter.h
#include <QObject>
class Counter : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int value READ value NOTIFY valueChanged)
public:
    explicit Counter(QObject *parent = nullptr) : QObject(parent), m_value(0) {}
    int value() const { return m_value; }
public slots:
    void increment() { ++m_value; emit valueChanged(); }
signals:
    void valueChanged();
private:
    int m_value;
};
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "counter.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    // uri="com.example", major=1, minor=0, qml-имя="Counter"
    qmlRegisterType<Counter>("com.example", 1, 0, "Counter");
    QQmlApplicationEngine engine;
    engine.load(QUrl("qrc:/main.qml"));
    return app.exec();
}
// main.qml
import QtQuick 2.15
import com.example 1.0

Item {
    Counter {
        id: counter
    }
    Text { text: counter.value }
    MouseArea {
        anchors.fill: parent
        onClicked: counter.increment()
    }
}

Способ 2: QML_ELEMENT / QML_NAMED_ELEMENT (Qt 6, предпочтительный)

Макрос QML_ELEMENT в теле класса и настройка qt_add_qml_module в CMake автоматически регистрируют тип без ручного вызова qmlRegisterType:

// counter.h
#include <QObject>
#include <QtQml/qqmlregistration.h>
class Counter : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(int value READ value NOTIFY valueChanged)
    // ... (как выше)
};
# CMakeLists.txt
qt_add_qml_module(app
    URI com.example
    VERSION 1.0
    SOURCES counter.h counter.cpp
    QML_FILES main.qml
)

Способ 3: Singleton через qmlRegisterSingletonType

Когда нужен ровно один экземпляр (например, сервис настроек):

qmlRegisterSingletonType<AppSettings>(
    "com.example", 1, 0, "AppSettings",
    [](QQmlEngine *, QJSEngine *) -> QObject * {
        return new AppSettings();
    });
import com.example 1.0
Text { text: AppSettings.theme }  // нет id, обращение по имени типа

Способ 4: setContextProperty

Передать уже существующий объект в корневой контекст (см. вопрос о QML-контексте). Подходит для «глобальных» объектов уровня приложения.

Что доступно из QML автоматически

  • Q_PROPERTY — читается и пишется как свойство QML, изменения через NOTIFY-сигнал запускают биндинги.
  • Публичные slots и методы с макросом Q_INVOKABLE — вызываются как обычные JS-функции.
  • Q_ENUM / Q_FLAG — становятся перечислениями вида MyClass.ValueA.

Подводные камни

  • Отсутствие Q_OBJECT: без макроса Q_OBJECT moc не обработает класс, сигналы и Q_PROPERTY просто не будут работать — и ошибку компилятора при этом не получить.
  • Потоки: QML-движок работает в GUI-потоке; вызов слотов из фонового потока требует Qt::QueuedConnection.
  • Владение памятью: объекты, созданные в C++ и переданные QML, по умолчанию остаются под управлением C++; объекты, созданные внутри QML, управляются движком — смешивать без осторожности опасно (двойное удаление).
  • Устаревший QQmlEngine::setObjectOwnership: явно задавайте QQmlEngine::CppOwnership для объектов, которые QML не должен удалять.
  • Версия URI: в Qt 6 с QML_ELEMENT версии мажорного/минорного модуля контролируются CMake-конфигурацией, ручные вызовы qmlRegisterType с другой версией конфликтуют.
  • Типы возврата: методы, возвращающие QObject*, работают; возвращающие пользовательские C++ типы без регистрации в мета-системе — нет.
  • Отладка: включайте QML_IMPORT_PATH и запускайте с -qmljsdebugger для диагностики ошибок регистрации типов.

Common mistakes

  • Объяснять QML и C++ integration только по синтаксису, без жизненного цикла и стоимости.
  • Игнорировать ошибки, null/empty состояния, порядок инициализации или режим сборки.
  • Давать пример, который работает в демо, но ломается при изменении владельца ресурса.
  • Смешивать signals/slots с generic callbacks и забывать про thread affinity.

What the interviewer is testing

  • Кандидат формулирует точную модель для QML и C++ integration, а не только определение.
  • Пример компилируем, безопасен по lifetime и соответствует версии технологии.
  • Названы trade-off, ограничения и диагностируемые симптомы ошибки.
  • Понимает границу между C++ кодом, runtime/framework metadata и editor/UI слоем.

Sources

Related topics