filcuc/nimqml

need clarification of "QVariant: delete"

R3D9477 opened this issue · 7 comments

Hi. I tried to take out window's initialization code to separated proc, but application crashed:
main.nim:

import nimqml, os, ospaths
import mainWindow

proc loadResources(app: QApplication) =
  for resfile in walkFiles(joinPath(app.applicationDirPath, "*.rcc")):
    QResource.registerResource(resfile)

proc loadMainWindow(app: QApplication, engine: QQmlApplicationEngine) =
  let mainWindowLogic = newMainWindowLogic(app, engine)
  defer: mainWindowLogic.delete
  let mainWindowLogicVar = newQVariant(mainWindowLogic)
  defer: mainWindowLogicVar.delete
  let qurl = newQUrl("qrc:///mainWindow.qml");
  defer: qurl.delete
  engine.load(qurl)
  engine.setRootContextProperty("mainWindow", mainWindowLogicVar)

proc mainProc() =
  var app = newQApplication()
  defer: app.delete()
  
  loadResources(app)
  
  var engine = newQQmlApplicationEngine()
  defer: engine.delete()
  
  loadMainWindow(app, engine)
  
  app.exec()

when isMainModule:
  mainProc()
  GC_fullcollect()

crashes when button clicked (onClicked: mainWindow.btnClicked())

and this code works fine:

import nimqml, os, ospaths
import mainWindow

proc loadResources(app: QApplication) =
  for resfile in walkFiles(joinPath(app.applicationDirPath, "*.rcc")):
    QResource.registerResource(resfile)

proc mainProc() =
  var app = newQApplication()
  defer: app.delete()
  
  loadResources(app)
  
  var engine = newQQmlApplicationEngine()
  defer: engine.delete()
  
  let mainWindowLogic = newMainWindowLogic(app, engine)
  defer: mainWindowLogic.delete
  let mainWindowLogicVar = newQVariant(mainWindowLogic)
  defer: mainWindowLogicVar.delete
  let qurl = newQUrl("qrc:///mainWindow.qml");
  defer: qurl.delete
  engine.load(qurl)
  engine.setRootContextProperty("mainWindow", mainWindowLogicVar)
  
  app.exec()

when isMainModule:
  mainProc()
  GC_fullcollect()

simple_app.zip

What differents?
Thanks.

it works (exception in self.QObject.delete)

main.nim

proc loadMainWindow(app: var QApplication, engine: var QQmlApplicationEngine) =
  let mainWindowLogic = newMainWindowLogic(app, engine)
  #defer: mainWindowLogic.delete
  let mainWindowLogicVar = newQVariant(mainWindowLogic)
  defer: mainWindowLogicVar.delete
  let qurl = newQUrl("qrc:///mainWindow.qml");
  defer: qurl.delete
  engine.load(qurl)
  engine.setRootContextProperty("mainWindow", mainWindowLogicVar)

proc mainProc() =
  var app = newQApplication()
  defer: app.delete()
  loadResources(app)
  var engine = newQQmlApplicationEngine()
  defer: engine.delete()
  loadMainWindow(app, engine)
  app.exec()

mainWindow.nim:

import nimqml

QtObject:
  type MainWindowLogic* = ref object of QObject
    app: QApplication
    engine: QQmlApplicationEngine
  
  proc delete*(self: MainWindowLogic) =
    self.QObject.delete
  
  proc setup(self: MainWindowLogic) =
    self.QObject.setup
  
  proc newMainWindowLogic*(app: var QApplication, engine: var QQmlApplicationEngine): MainWindowLogic =
    new(result)
    result.app = app
    result.engine = engine
    result.setup()
  
  proc btnClicked*(self: MainWindowLogic) {.slot.} =
    echo "button clicked!"

I will check asap

For the first example you have the same behaviour as you would have written the following code in c++

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

class Logic : public QObject
{
    Q_OBJECT
public:
    Logic()
        : QObject(nullptr)
    {}
};


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

    QGuiApplication app(argc, argv);

    Logic* logic = new Logic();

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("logic", logic);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    delete logic;

    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

#include "main.moc"

import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")


    Button {
        anchors.centerIn: parent
        onClicked: logic.onClicked()
    }
}

Mind the delete of the logic. Basically you are deleting the QObject from the QML engine and you app will crash. This caused by the fact that the QML engine doesn't keep track of the QObject injected with a rootContext().setContextProperty() So the error is not in the QVariant but in the fact that you're deleting the Logic too early

longer explanation coming.. :D (give me an hour)

In contrast the following code doesn't crash

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

class Logic : public QObject
{
    Q_OBJECT
public:
    Logic()
        : QObject(nullptr)
    {}
};


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

    QGuiApplication app(argc, argv);

    Logic* logic = new Logic();

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    QObject* window = engine.rootObjects().front();
    window->setProperty("logic", QVariant::fromValue(logic));
    delete logic;

    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

#include "main.moc"

import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    property QtObject logic: null
    onLogicChanged: console.log("Logic changed to", logic)

    Button {
        anchors.centerIn: parent
        onClicked: logic.onClicked()
    }
}

The reason why this example doesn't crash comes in how the QML engine handle QtObject properties. In this case we write the Window logic property with the QObject pointer created in main but now the QQmlEngine keeps track of QObject deletion (thus the qml example doesn't crash).
In other words in this case the Qml creates a QPointer/QWeakPointer for properly monitor the QObject deletion (again this because we declared a QtObject property in Window qml)

This doesn't happen by using the QQmlContext ::setContextProperty

For concluding your crashes are due to the fact that you delete the QObject logic too early. In general the QObject you inject in main should last longer than the QQmlEngine

@filcuc many thanks! it became a little clearer :)

will memory leak, if I don't delete logic at all?
is this procedure mandatory?

for example (code from example№3):

proc loadMainWindow(app: var QApplication, engine: var QQmlApplicationEngine) =
  let mainWindowLogic = newMainWindowLogic(app, engine)
  #defer: mainWindowLogic.delete
  let mainWindowLogicVar = newQVariant(mainWindowLogic)
  defer: mainWindowLogicVar.delete
  let qurl = newQUrl("qrc:///mainWindow.qml");
  defer: qurl.delete
  engine.load(qurl)
  engine.setRootContextProperty("mainWindow", mainWindowLogicVar)

proc mainProc() =
  var app = newQApplication()
  defer: app.delete()
  loadResources(app)
  var engine = newQQmlApplicationEngine()
  defer: engine.delete()
  loadMainWindow(app, engine)
  app.exec()

(commented line)