Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QML exposed objects across shared library boundaries


  • Moderators

    Hello,

    Say I have an application where I add some QML files to a module (through the generated template):

    qt_add_qml_module(ApplicationName
        URI MyNamespace
        VERSION MyVersion
        QML_FILES
            SomeQMLFile
        NO_LINT
        NO_CACHEGEN
    )
    

    Is it possible for me to inject into this namespace a (QML) singleton object from a shared library?

    What I have currently in the application code for testing is:

    int main(int argc, char ** argv)
    {
        MySingletonClass singleton;
        QQmlApplicationEngine engine;
        // ... init code for engine
        qmlRegisterSingletonInstance("MyNamespace", 0, 1, "MySingletonClassName", &singleton);
        // ...
        engine.load(url);
        return app.exec();
    }
    

    Is this the right way to do it?
    Must I also add the QML_ELEMENT and QML_SINGLETON macros to the MySingletonClass, or if I don't use a QML module in the library I can skip this?


  • Moderators

    Well, I forgot about this a little bit, but I'm adding the solution anyway for future reference.

    After some reading through the documentation and experimenting, it appears that dynamically registering a foreign class to a qml module declared as above isn't possible (nor encouraged, nor supported, as per the docs). The "old" way of plugin whatever you want into the registered QML namespace doesn't work anymore.

    The proper solution is to wrap the class as described in the docs and use that wrapper to expose it to the QML engine. Like follows (Tsc::Engine is what I'm attempting to inject):

    Header:

    #include <QQmlEngine>
    #include <QJSEngine>
    
    struct EngineForeign
    {
        Q_GADGET
        QML_FOREIGN(Tsc::Engine)
        QML_SINGLETON
        QML_NAMED_ELEMENT(Engine)
    
    public:
        static Tsc::Engine * create(QQmlEngine *, QJSEngine *);
        static QJSEngine * currentJsEngine;
    };
    

    Source:

    #include "TscQml.h"
    
    QJSEngine * EngineForeign::currentJsEngine = nullptr;
    
    Tsc::Engine * EngineForeign::create(QQmlEngine *, QJSEngine * jsEngine)
    {
        Tsc::Engine * engine = Tsc::Engine::instance();
        Q_ASSERT(engine->thread() == jsEngine->thread());
    
        Q_ASSERT(!currentJsEngine || currentJsEngine == jsEngine);
        currentJsEngine = jsEngine;
    
        QJSEngine::setObjectOwnership(engine, QJSEngine::CppOwnership);
        return engine;
    }
    
    #include "moc_TscQml.cpp"
    

    After that everything works as expected and I can see and use it in QML:

    import QtQuick
    import QtQuick.Controls
    import Tsc
    
    ApplicationWindow {
        width: 640
        height: 480
        visible: true
        title: qsTr("Hello World")
    
        Rectangle {
            anchors.fill: parent
            color: 'red'
    
            Connections {
                target: Engine.simulation
                function onAdvanced() {
                    console.log('advanced')
                }
            }
        }
    }
    

    Notably, this must be done for the singleton itself only. simulation is a QObject and a registered property (Q_PROPERTY) of the Tsc::Engine instance, which hasn't been wrapped nor does it appear necessary for this class to be exposed explicitly to the QML engine.


Log in to reply