把一个QObject指针传给QML代码时,为什么会发生二次释放?
使用这段C++代码:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
class Returned : public QObject {
Q_OBJECT
public:
Returned() { qDebug() << "Returned"; }
~Returned() { qDebug() << "~Returned"; }
Q_INVOKABLE int invokable() { qDebug() << "invokable"; return 42; }
};
class Exposed : public QObject {
Q_OBJECT
public:
Q_INVOKABLE QObject* getSomething() { return &object; }
private:
Returned object;
};
int main(int argc, char* argv[]) {
QGuiApplication app(argc, argv);
Exposed e;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("exposed", &e);
engine.load("test.qml");
int result;
QMetaObject::invokeMethod(e.getSomething(), "invokable", qReturnArg(result));
qDebug() << result;
app.exec();
qDebug() << "out of event loop";
}
#include "test.moc"
以及这段QML代码:
import QtQuick
import QtQuick.Controls
ApplicationWindow {
visible: true
// THIS IS COMMENTED OUT Text { text: exposed.getSomething().invokable(); }
}
程序输出如下:
Returned
invokable
42
打开一个空窗口,等我把它关闭后就会结束,且没有错误:
out of event loop
~Returned
换句话说,类 Exposed 的对象可以返回指向另一个对象的指针,通过这个指针可以调用一个方法,这是在预期之中的。
然而,如果我尝试在QML中重复同样的操作(或者我认为是同样的操作),就会得到错误。
因此我把QML源码中的注释行去掉,使其变成:
import QtQuick
import QtQuick.Controls
ApplicationWindow {
visible: true
Text { text: exposed.getSomething().invokable(); }
}
为了便于理解,我们也简化C++代码:
int main(int argc, char* argv[]) {
QGuiApplication app(argc, argv);
Exposed e;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("exposed", &e);
engine.load("../test.qml");
app.exec();
qDebug() << "out of event loop";
}
随后程序的行为如下。首先它打印出:
Returned
invokable
并显示一个带有“42”的窗口(到此为止还好),但在我关闭窗口后,它以错误信息结束:
out of event loop
~Returned
double free or corruption (out)
我无法解释原因,因为我只是把一个非拥有指针传给QML,至少这是我的打算。
请问有人能解释这到底怎么回事吗?这个“二次释放”错误的原因是什么?
顺便说一句,它以SIGABRT结束,以下是在那一刻的调用栈:
__pthread_kill_implementation (@pthread_kill@@GLIBC_2.34:81)
__pthread_kill_internal (@pthread_kill@@GLIBC_2.34:59)
__GI___pthread_kill (@pthread_kill@@GLIBC_2.34:59)
__GI_raise (@raise:10)
__GI_abort (@abort:46)
__libc_message (@__libc_message:178)
malloc_printerr (@7ffff54a0cfc..7ffff54a0d65:3)
_int_free (@_int_free:474)
__GI___libc_free (@free:31)
Returned::~Returned() (/home/user/work/test/test.cpp:9)
QV4::QObjectWrapper::destroyObject(bool) (@QV4::QObjectWrapper::destroyObject(bool):96)
QV4::MemoryManager::sweep(bool, void (*)(char const*)) (@QV4::MemoryManager::sweep(bool, void (*)(char const*)):226)
QV4::MemoryManager::~MemoryManager() (@QV4::MemoryManager::~MemoryManager():23)
QV4::ExecutionEngine::~ExecutionEngine() (@QV4::ExecutionEngine::~ExecutionEngine():281)
QJSEngine::~QJSEngine() (@QJSEngine::~QJSEngine():16)
main (/home/user/work/test/test.cpp:29)
__libc_start_call_main (@__libc_start_call_main:29)
__libc_start_main_impl (@__libc_start_main@@GLIBC_2.34:43)
_start (@_start:15)
解决方案
通过方法调用将一个QObject从 C++传递给QML,会让QML默认拥有被指向对象的所有权。
文档中指出:
当数据从C++传输到QML时,数据的所有权始终属于C++。这一规则的例外是 当一个QObject从显式的C++方法调用返回时:在这种情况下,QML引擎会接管对象的所有权,除非通过调用QQmlEngine::setObjectOwnership() 并指定QQmlEngine::CppOwnership将对象所有权显式设置为由C++保留。
这样,对象在引擎终止时会被QML销毁,在C++父对象析构时也会被销毁。
要么通过前述的静态函数显式设置对象的所有权,要么把你的访问器改成一个 Q_PROPERTY,就应该解决你的问题。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。