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?


  • Qt Champions 2018

    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.



  • @pderocco You could pass it as a string using JSON.stringify() and JSON.parse() on the QML side.



  • @grecko That sure looks like the ticket. It'll take a while to integrate that into my app, but I'll report back here whether it works okay or not. Thanks for that pointer.



  • @tom_h That would work for simple objects, but the main thing I want to pass is function closures, so that I can set them up to be called in the future with values from the present.



  • In case of simple objects, like map, you also can try QVariantMap, it can be transparently passed bettween C++ and QML without any convertations.


  • Qt Champions 2018

    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


Log in to reply