Pass opaque JavaScript type to and from C++?
-
I want to create a JavaScript object in QML, give it to a C++ module for storage, and retrieve it later (in the same program run). The object could be any JavaScript type, but I don't need to access it in C++, just store it. Is there a C++ or Qt type that I can use for this, and is there a way of ensuring that JavaScript won't garbage collect it while it is in the C++ domain?
-
I would say
QJSValue
is the correct type. I am not sure about the garbage collection aspect, but I believe it could work out of the box. -
In case of simple objects, like map, you also can try
QVariantMap
, it can be transparently passed bettween C++ and QML without any convertations. -
I wouldn't be so sure about the no conversion part of passing a QVariantMap between C++ and QML.
In pderocco's case
QJSValue
is the way to go. -
I just want to say, that there is ability out of box, without any types registration, to pass JS Arrays/Objects between QML and C++.
QJSValue
can be useful if you are trying to pass some JS classes, which can contains methods, but since it is not an QObject you cannot change ownership of such object. I'm not 100% sure, but it looks like that after garbage collection this object can point to null value, so you should be carefull with this. -
@intruderexcluder Well, I did a simple test. I created a dummy QML app, adding code to main.cpp to register a JSValue type:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "JSValue.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); qmlRegisterType<JSValue>("JSValue", 1, 0, "JSValue"); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
The JSValue is defined in a simple header, JSValue.h:
#include <QJSValue> #include <QObject> class JSValue: public QObject { Q_OBJECT QJSValue value; public: JSValue(QObject* p = nullptr): QObject(p) {} Q_INVOKABLE void set(QJSValue v) { value = v; } Q_INVOKABLE QJSValue get() { return value; } };
And the app is defined in main.qml:
import QtQuick 2.13 import QtQuick.Window 2.13 import JSValue 1.0 Window { visible: true width: 400 height: 400 title: "QJSValueTest" JSValue { id: jsvalue } Component.onCompleted: { var v = function(x) { return x * x } jsvalue.set(v) v = null console.log(jsvalue.get()(2)) gc() console.log(jsvalue.get()(2)) } }
This creates a function closure, gives it to the JSValue object, clears the JavaScript variable that refers to it, fetches it back from the JSValue object and calls it, then runs the garbage collector, and does it again. It produces the correct result before and after calling gc(). So either QJSValue objects are automatically registered as reachable from JavaScript's root object (which would seem sensible to me), or there is some other reference to the function closure that I don't see, or gc() doesn't necessarily do a full garbage collection synchronously when asked.
Does this look like a good test?
-
I decided to ask this question specifically in a separate post, just in case a JS memory manager expert might not have found the title of this post interesting. Here's the other post:
https://forum.qt.io/topic/106655/does-qjsvalue-protect-its-value-from-garbage-collection