我在使用 Qt5(5.8,Arch Linux)和 QML 时遇到了一些严重的鼻恶魔。基本上,代码公开了一个 QQmlListProperty,它的成员是动态创建的,并将它们的所有权转移给 JavaScript。当 itemChanged() 信号被发出数千次(在我的实际代码中仅几次就足够了)程序段错误时。
根据 Valgrind + GDB,看起来“A”被 QObject::event 释放,然后被 QML 读取,导致段错误。
奇怪的是,即使将变量“游戏”的名称更改为其他名称,也会改变我必须单击按钮以使程序崩溃的次数。删除转发器或矩形或使用不同的矩形颜色表达式会影响程序是否崩溃。另外,只要不将所有权转移给 JS,就完全没有问题。可悲的是,这也会导致内存泄漏,因为没有任何东西可以释放“A”对象。
由于这些细节,我相信代码中的某处存在未定义的行为,但我不知道在哪里。
这是我可以创建的最短示例:
主.cpp:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QObject>
#include <QQmlListProperty>
template <typename T>
T* make_js() {
T* t = new T();
QQmlEngine::setObjectOwnership(t, QQmlEngine::JavaScriptOwnership);
return t;
}
class A: public QObject {
Q_OBJECT
Q_PROPERTY(int id READ id CONSTANT)
public:
int id() const {return 1;}
};
class B: public QObject {
Q_OBJECT
Q_PROPERTY(A* item READ item NOTIFY itemChanged)
Q_PROPERTY(QQmlListProperty<A> list READ list CONSTANT)
public:
A *item() const { return make_js<A>(); }
Q_INVOKABLE void change() { emit itemChanged(); }
QQmlListProperty<A> list() {
return QQmlListProperty<A>(
this, nullptr,
[](QQmlListProperty<A> *) {return 1;},
[](QQmlListProperty<A> *, int ) {return make_js<A>();});
}
signals:
void itemChanged();
};
#include "main.moc"
int main(int argc, char *argv[]) {
QGuiApplication a(argc, argv);
B* game = new B();
QQmlApplicationEngine engine;
qmlRegisterType<B>("example", 1, 0, "B");
qmlRegisterType<A>("example", 1, 0, "A");
engine.rootContext()->setContextProperty("thing", game);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
a.exec();
delete game;
return 0;
}
main.qml:
import QtQuick 2.0
import QtQuick.Layouts 1.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
import example 1.0
Window {
visible: true
width: 1280
height: 800
title: qsTr("Example")
ColumnLayout {
anchors.fill: parent
Repeater {
model: thing.list
Rectangle {
color: thing.item.id === 1 ? "red" : "blue"
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Button {
text: "Click me a bunch of times to crash!"
anchors.fill: parent
onClicked: {for(var i=0;i<1000;++i) thing.change();}
}
}
}
}
这种行为的原因是什么?您碰巧知道解决方法吗?难道 QQmlListProperty 在某种程度上与 JavaScript 所有权不兼容?
编辑:我找到了一个对我来说足够好的解决方法。改变
Q_PROPERTY(QQmlListProperty<A> list READ list CONSTANT)
进入
Q_PROPERTY(QQmlListProperty<A> list READ list NOTIFY listChanged)
并在 change() 中发出 listChanged() 以及 itemChanged() 修复它。通过额外的测试,我注意到 Qt 只从 list() 调用索引 lambda 一次,然后释放返回的 'A' -object。它不再调用 lambda 并且似乎试图重用已经释放的“A”。
但是,我仍然很想知道是什么首先导致了这种情况。