Qt/QML Exposer une variable C++ au QML
-
Il y a t-il un moyen d'exposer une variable C++ au QML sans avoir de risque de fuite mémoire ? J'ai reproduit le problème dans cette exemple :
Bug.pro :QT += quick \ widgets CONFIG += c++17 # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Refer to the documentation for the # deprecated API to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ connection.cpp \ device.cpp \ main.cpp RESOURCES += qml.qrc # Additional import path used to resolve QML modules in Qt Creator's code model QML_IMPORT_PATH = # Additional import path used to resolve QML modules just for Qt Quick Designer QML_DESIGNER_IMPORT_PATH = # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target HEADERS += \ connection.h \ device.h
Device.h :
#ifndef DEVICE_H #define DEVICE_H #include <QObject> class Device : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name CONSTANT) private: QString m_name; public: explicit Device(QObject *parent = nullptr); Device(const Device & device); Device(QString name); QString name() const; }; //Q_DECLARE_METATYPE(Device) #endif // DEVICE_H
Device.cpp :
#include "device.h" Device::Device(QObject *parent) : QObject(parent) { m_name = "Device name"; } Device::Device(const Device & device) : QObject() { m_name = device.m_name; } Device::Device(QString name) : QObject() { m_name = name; } QString Device::name() const { return m_name; }
Connection.h :
#ifndef CONNECTION_H #define CONNECTION_H #include <QObject> #include "device.h" class Connection : public QObject { Q_OBJECT public: explicit Connection(QObject *parent = nullptr); public slots: void call(); signals: void signalWithoutPointer(Device device); void signalWithPointer(Device * device); }; #endif // CONNECTION_H
Connection.cpp :
#include "connection.h" #include <QDebug> Connection::Connection(QObject *parent) : QObject(parent){} void Connection::call() { qDebug() << "Function call emitted"; Device device = Device("Device"); Device * pointer = new Device("Pointeur"); emit signalWithoutPointer(device); //undefined or converted to QVariant emit signalWithPointer(&device); //use after free (only if the receiver is in another thread i.e. Qt::QueuedConnection) emit signalWithPointer(pointer); //memory leak }
main.cpp:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQuickItem> #include "connection.h" #include "device.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); qmlRegisterSingletonType<Connection>("com.bug.connection", 1, 0, "Connection", [](QQmlEngine * engine, QJSEngine * scriptEngine) -> QObject * { Q_UNUSED(engine) Q_UNUSED(scriptEngine) Connection * connection = new Connection(); return connection; }); qmlRegisterType<Device>("com.bug.device", 1, 0, "Device"); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml"))); component.create(); int exec = app.exec(); return exec; }
main.qml :
import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Window 2.12 import com.bug.connection 1.0 import com.bug.device 1.0 Window { visible: true width: 640 height: 480 title: qsTr("Hello World") Text { id: text1 text: qsTr("Default") } Connections { target: Connection onSignalWithoutPointer: (device) =>{ console.log("Signal emitted without pointer : " + device) console.log("Device's name without pointer : " + device.name) text1.text = device.name } onSignalWithPointer: (device) =>{ console.log("Signal emitted with pointer : " + device) console.log("Device's name with pointer : " + device.name) text1.text = device.name } } Button { id: button x: 270 y: 220 text: qsTr("Button") onClicked: { console.log("clicked button") Connection.call() } } }
-
Bonjour,
Dans votre fonction, call, le device est détruit à la fin de la fonction,
Le signal signalWithoutPointer ne peut pas marcher car cela demanderai un copy de l'objet Device mais il s'agit d'un QObject ou le constructeur par copy est désactivé.
Le passage d'un pointeur vers Device, ben, la moment juste après le pointeur est null.
Le passage avec signal sur pointeur "pointer", lui il marche mais c'est la responsabilité de la classe connection de détruire pointer (et donc de garder une variable dessus).Le plus simple c'est de faire un retour de fonction si vous voulez vous simplifiez la vie.
// in Connection.cpp Device* Connection::call() { return new Device("Pointeur"); }
//main.qml (à la place de la Connections) property Device device: Connection.call()
Dans ce cadre cas là, par passage par pointer via un retour de fonction, l'objet appartiendra au QML et sera détruit par le QML Engine.
Cependant, je recommande plutot de passer par une propriété.
// in connection.h // je suggère de renommer cette classe en ConnectionController (pour éviter la confusion avec le type qml Connections class Connection : public QObject { Q_OBJECT Q_PROPERTY(Device* device READ device CONSTANT) public: explicit Connection(QObject *parent = nullptr); Device* device() const; private: std::unique_ptr<Device> m_device; }; // in connection.cpp Connection::Connection(QObject *parent) : QObject(parent), m_device(new Device("Pointeur"){} Device* Device::device() const { return m_device.get(); } // main.qml property Device device: Connection.device
Ici le device reste la responsabilité de la classe Connection, mais c'est géré par le uinique_ptr.
Si le device peut changer, il conviendra d'ajouter un signal deviceChanged et de supprimer le CONSTANT de la Q_PROPERTY pour y mettre un "NOTIFY deviceChanged".