QtScript to QML Migration - Notes from the Trenches

2 minute read

Ostinato uses the QtScript module to support custom protocol userscripts.

Qt5 had deprecated the QtScript module and replaced it with a new Javascript engine in the QML module. With Qt6, the QtScript module has been removed.

To migrate Ostinato from Qt5 to Qt6, I needed to figure out the migration changes for QScriptEngine and related classes which were part of QtScript.

A search for a migration guide from QScriptEngine to QJSEngine didn’t return any useful results - which is very unlike Qt documentation!

In the end, the changes needed turned out to be straightforward. YMMV but I’m sharing my notes here - this should get you started in the right direction.

Qmake/CMake changes

To start, I suggest you make changes to your qmake/cmake files to replace QtScript module with QML module. Make this change first so that you can trigger builds to identify compilation issues after every change.

In your qmake .pro file(s), replace

- QT += script
+ QT += qml

If using CMake, replace

- find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Script)
+ find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Qml)

QScriptEngine ⇒ QJSEngine

The main scripting engine class QScriptEngine has been replaced with QJSEngine. So do a find-replace for that along with the header file change -

-#include <QScriptEngine>
+#include <QJSEngine>

Note that instead of QScriptEngine::hasUncaughtException() you should use QJSEngine::evaluate() and check for isError() of the returned QJSValue.

-    engine_.evaluate(script);
-    if (engine_.hasUncaughtException())
+    QJSValue value = engine_.evaluate(script);
+    if (value.isError())

QScriptValue ⇒ QJSValue

The QScriptValue equivalent is QJSValue. But some method names differ between the two. e.g.

  • QScriptValue::isValid()!QJSValue::isUndefined()
  • QScriptValue::isFunction()QJSValue::isCallable()
  • QScriptValue::call()QJSValue::callWithInstance() (for object method calls)
  • QScriptValue::toUInt32()QJSValue::toUInt()
  • QScriptValue::toInt32()QJSValue::toInt()

You can do a find-replace for the above or redefine QScriptValue to inherit from QJSValue and override the methods as shown below.

class QScriptValue : public QJSValue
{
    bool isValid() const { return !isUndefined(); }
    bool isFunction() const { return isCallable(); }
    uint toUInt32() const { return toUInt(); }
    int toInt32() const { return toInt(); }    
    // ...
}

I used this QScriptValue redefinition to start with to quickly get things compiling and working. Once compilation was successful and my tests were passing, I did find-replace for QScriptValue with QJSValue alongwith the changed methods and removed the redefinition.

These were the only changes I needed to make. You may find more changes are needed but hopefully this will get you a good way there.

Gotchas

There are a couple of gotchas though e.g. A C++ QObject’s dynamic properties are not accessible in the script which has access to the object. However, properties added to the object in the script are accessible to the C++ code.

Leave a Comment