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()
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)