Handling of objects and types between C++ and QML

Safely exposing QSharedPointer objects to QML

Q: How can we expose objects governed by QSharedPointer to QML safely? (Assume that we are talking about a pointer to a QObject)

Currently, QML isn’t aware of QSharedPointer and friends, but internally uses its own guard-types to react to QObject deletion. If you want to ensure that the engine won’t delete the QObject you pass in, you need to explicitly set the ownership of the QObject to CppOwnership (there’s a QQmlEngine function to do so). Alternatively, if the QObject has a parent (ownership parent) set, the engine won’t delete it.

A: You need to observe these rules:

  • You need to be able to guarantee the life of your QObject beyond the life of the QDeclarativeEngine. [ Reason: It was suggested in the mailing-list thread that the engine could cache a reference to the object ]
  • It is too dangerous to mix QSharedPointer ownership with the QObject parent/child ownership.
  • [ Should the referenced class inherit QObject? ]

Thus this is the only way to go about it:

  1. Call QSharedPointer :: data() to get a pointer to the referenced class;
  2. Make sure the QML engine doesn’t assume ownership: QDeclarativeEngine :: setObjectOwnership (P). This step is necessary since the only other way of keeping the engine from assuming ownership would be to give the object a parent, which is out of the question since the shared pointer already has ownership.
  3. Hand over the raw QObject pointer to QML using either QObject :: setProperty or QDeclarativeContext :: setContextProperty

Ways of exposing references to QML and their effect on ownership

  1. The QML engine respects normal QObject parenting.
  2. By calling QDeclarative::setRootContext (ownership not transferred: http://qt-project.org/doc/qt-4.8/qdeclarativecontext.html#setContextProperty).
    • no ownership change.
  3. By calling QObject::setProperty on the instantiated component (ownership?)
    • no ownership change
  4. By QML calling a method on a C++ object and getting a response (ownership IS transferred to QML if and only if the QObject has no parent)
    • ownership change if ownership semantics were not previously explicitly set; e.g.
    • if the QObject returned from a Q_INVOKABLE function to JS does not have CppOwnership explicitly set, it will become JavaScriptOwnership owned. To avoid that, you can explicitly set the ownership semantic prior to returning it.
  5. By QML accessing a Q_PROPERTY on a C++ object (ownership?).
    • no ownership change

Issues arising from type handling of objects exposed; relevant when specifying properties in QML

  • QML understand only 2 property types: JavaScript vars, and QVariant properties.
  • It builds type information from the loaded component set, and enforces type safety when assigning to properties of those types.
  • But internally, all properties are stored as either QVariants or JavaScript vars.

Crossing C++ / QML boundaries issues.

  • Q: What are the exact mechanisms that governs value conversion from one space to another?
  • A: Whenever a value stored in a QVariant property is exposed to a JavaScript [removed]binding, signal handler, dynamic method, etc), it is converted to a JavaScript value:
    • In case of component-defined QObject-derived types, we create a JavaScript object which is “special” – we install some functions on that object such that any property lookup or function call on it, is passed to our code in C++ which performs the appropriate lookup / call.
    • There are quite a few different types for which we do this (sequence types such as QList<int> etc, internal implementation detail classes for which we need to intercept symbol resolution, etc).
    • Whenever a value in JavaScript is assigned to a QVariant-backed property of a QML item, we do a similar (but inverse) conversion; JavaScript string, convert it QString, “special” JavaScript object, we “pull out of it” the embedded pointer to the actual object.

Special semantics for null and undefined

  • if “undefined” is assigned to a QVariant property which is Reset-able, we reset the property.
  • Null is generally treated as a null-ptr when assigned to QObject-derived-type properties, but otherwise may fail, depending on the property type.

Improvements in the works (Qt5)

  • Simpler and more intuitive way to expose Collections (QList, QMap, QHash, QSet) to QML:
    • At the moment, QList of int, bool, qreal, QString, QUrl.
  • QVariantMap is converted to a generic JavaScript object with key-value pairs set as correctly as possible (according to the conversion semantics for each value’s type).
  • To avoid the type conversion edge cases, thoughts to use more of a property var in the implementation.