简体   繁体   中英

What are the rules for a C++ object returned from a Q_INVOKABLE to be owned and collected by QML?

I have a C++ class that is exposed in QML as a singleton that produces other types

qmlRegisterSingletonType<DomainManager>("my.pkg", 1, 0, "DomainManager", domain_provider);
qmlRegisterUncreatableType<Control>("my.pkg", 1, 0, "Control", "Get it fresh from DomainManager");

The DomainManager has a function

Q_INVOKABLE Control* controlWriter(QString partition);

According to http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership an object returned from a Q_INVOKABLE is owned by QML and should be deleted by the GC. The only exception seems to be when a parent is set on the returned object, which is not the case.

I have a StackView which contains panels with a property:

property Control ctrl: DomainManager.controlWriter(dummy.name)

I have verified that when I pop the panel off the stack, Component.onDestruction gets called, so the panel does get deleted.

However, I put the following destructor on the C++ object, and it does not get deleted until the whole application exits.

~Control() { qDebug() << "deleting control"; };

The only way I've found to get rid of the Control object is to call ctrl.destroy() in Component.onDestruction to manually free it.

Why does QML not free this object?

The full QML file holding the property is as follows. ctrl is not used outside this file.

import QtQuick 2.11
import my.pkg 1.0

Image {
  id: dummy
  property string name
  property Control ctrl: DomainManager.controlWriter(dummy.name)

  source: "dummy.jpg"
  fillMode: Image.PreserveAspectFit

  Connections {
    target: gamepad
    onAxisLeftYChanged: {
      ctrl.id = dummy.name
      ctrl.x = gamepad.axisLeftY * 32767
      ctrl.yaw = gamepad.axisLeftX * 32767
      ctrl.publish()
    }
    onAxisLeftXChanged: {
      ctrl.id = dummy.name
      ctrl.x = gamepad.axisLeftY * 32767
      ctrl.yaw = gamepad.axisLeftX * 32767
      ctrl.publish()
    }
  }
  Component.onDestruction: {
    // not sure why this requires manual clean-up
    ctrl.destroy()
  }
}

Let's find out what's QML GC:

main.cpp

DomainManager *example = nullptr;

static QObject *domain_provider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
    Q_UNUSED(engine)
    Q_UNUSED(scriptEngine)
    return example;
}


int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    example = new DomainManager;

    qmlRegisterSingletonType<DomainManager>("my.pkg", 1, 0, "DomainManager", domain_provider);
    qmlRegisterUncreatableType<Control>("my.pkg", 1, 0, "Control", "Get it fresh from DomainManager");

    QQmlApplicationEngine engine;
    QObject::connect(example, &DomainManager::collectGarbage,
                     [&engine]() {
        engine.collectGarbage();
        qDebug("collectGarbage");
    });
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

QML

Window
{
    visible: true
    height: 640
    width: 480

    Component {
        id: test
        Item {
            property Control ctrl: DomainManager.controlWriter("test")
        }
    }

    Component.onCompleted: {
        var c = test.createObject()
        console.log("Control created")
        c.destroy()
        //DomainManager.collectGarbage()
        console.log("Window onCompleted")
    }
}

In the above code, I add an additional signal collectGarbage of DomainManager to demonstrate GC.

The object is owned by JavaScript. When the object is returned to QML as the return value of a method call, QML will track it and delete it if there are no remaining JavaScript references to it and it has no QObject::parent().

  1. The output of the above:
 qml: Control created qml: Window onCompleted 
  1. Then uncomment DomainManager.collectGarbage() tells QML engine to collectGarbage . The output will be:
 qml: Control created collectGarbage qml: Window onCompleted deleting control 
  1. Change ownership of control object to Cpp QQmlEngine::setObjectOwnership(control, QQmlEngine::CppOwnership); The output:
 qml: Control created collectGarbage qml: Window onCompleted 

Conclusion:

The code demo shows what if I force release the garbage. GC is not the smart pointer. When the object's reference count becomes zero, QML GC will destroy the object which returns from Q_INVOKABLE function except CppOwnership . But not immediately. Which I consider it similar to Java GC.

collectGarbage:

Normally you don't need to call this function; the garbage collector will automatically be invoked when the QJSEngine decides that it's wise to do so (ie when a certain number of new objects have been created). However, you can call this function to explicitly request that garbage collection should be performed as soon as possible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM