In a previous post, we looked at how the QML engine loads files. I recall that first the QML files are parsed by the parser, then compiled into an intermediate bytecode, and finally following the bytecode instruction, a C ++ object is created for each element in each QML file. For example, we saw that when a QML file contains a Text element, the engine creates an instance of C ++ of the QQuickText class.
In fact, file uploading is almost everything that the QML engine does. After downloading, it ceases to interfere with the application process. Event handling and element rendering in Runtime falls entirely on C ++. For example, the TextInput element handles events like QQuickTextInput :: keyPressEvent () and updates QQuickTextInput :: updatePaintNode () , without the involvement of the QML engine. ')
But there are two important things that the QML engine still affects at runtime: Bound signal handlers and property binding updates. Related signal handlers are things like the onClicked handler for MouseArea . Today we will look at the Bindings. Example:
This example contains two types of property assignment:
A simple assignment of properties, such as assigning a value of 300 to the width property of a Rectangle element belonging to the C ++ class QQuickRectangle. In this case, VME simply executes the STORE_DOUBLE bytecode during the creation of this component. The VME simply calls the QMetaObject :: metacall (QMetaObject :: WriteProperty, ...) function, which ends with a call to QQuickRectangle :: SetWidth () . After this assignment. the QML engine just forgets about this assignment.
Binding assignment, such as in the example above, “Window Area:” + (parent.width * parent.height) to the text property or parent to the centerIn property. Thanks to the magic of the bindings, the text property will be automatically updated whenever there is a change in the height or width of the Rectangle element. How it works? Actually no magic, read on to find the answer.
Creating a binding
If we set the parameter QML_COMPILER_DUMP = 1, then we will see that the bindings are created with the instruction STORE_COMPILED_BINDING:
Compiled bindings, this is actually an optimized version. Let's first take a look at the normal bindings that are created with the STORE_BINDING statement. The QQmlVME :: run () function creates a QQmlBinding object, to which a string of the form is passed.
That is, each binding is a JavaScript function. Part of the function “function $ text ()” is added by the QML compiler, so since V8 , which is the JS engine of QML, can, as you can easily guess, execute only the JS code. The string function is then compiled into a v8 :: Function object by the v8 compiler. The v8 engine, due to the built-in JIT compiler , creates native code. It does not execute the created v8 :: Function object, but saves it for the future.
Total: When executing the STORE_BINDING instruction, a QQmlBinding object is created that compiles the function passed to it as a string into the v8 :: Function object using the built-in JS engine V8.
Performing bind
At some point, our binding will need to be performed, which means that V8 will have to perform the function passed to it with the binding body and return the execution result to assign it to the target property. This call occurs primarily at the end of the creation phase, the QQmlVME :: complete () function sequentially calls the update () method for each binding. In our case, the QQmlBinding :: update () function is called . The update () method simply executes the contents of the v8 :: Function object and writes the result of the execution to the text property of our rectangle.
But wait a minute, how does V8 know about the meaning of the variables parent.width and parent.height? Indeed, how does he know about the parent object? Answer: V8 has no idea about any QObject type objects represented in the QML file, nor about the composition of their properties. When V8 encounters an unknown object or an unknown property of an object, it asks the question of a wrapper object to the QML engine, which finds a suitable object or property and passes it (or its value) back to V8. Let's see how the width property of the QQuickItem object is accessed using this dump as an example:
#0 QQuickItem::width (this=0x6d8580) at items/qquickitem.cpp:4711 #1 0x00007ffff78e592d in QQuickItem::qt_metacall (this=0x6d8580, _c=QMetaObject::ReadProperty, _id=8, _a=0x7fffffffc270) at .moc/debug-shared/moc_qquickitem.cpp:675 #2 0x00007ffff7a61689 in QQuickRectangle::qt_metacall (this=0x6d8580, _c=QMetaObject::ReadProperty, _id=9, _a=0x7fffffffc270) at .moc/debug-shared/moc_qquickrectangle_p.cpp:526 #3 0x00007ffff7406dc3 in ReadAccessor::Direct (object=0x6d8580, property=..., output=0x7fffffffc2c8, n=0x0) at qml/v8/qv8qobjectwrapper.cpp:243 #4 0x00007ffff7406330 in GenericValueGetter (info=...) at qml/v8/qv8qobjectwrapper.cpp:296 #5 0x00007ffff49bf16a in v8::internal::JSObject::GetPropertyWithCallback (this=0x363c64f4ccb1, receiver=0x363c64f4ccb1, structure=0x1311a45651a9, name=0x3c3c6811b7f9) at ../3rdparty/v8/src/objects.cc:198 #6 0x00007ffff49c11c3 in v8::internal::Object::GetProperty (this=0x363c64f4ccb1, receiver=0x363c64f4ccb1, result=0x7fffffffc570, name=0x3c3c6811b7f9, attributes=0x7fffffffc5e8) at ../3rdparty/v8/src/objects.cc:627 #7 0x00007ffff495c0f1 in v8::internal::LoadIC::Load (this=0x7fffffffc660, state=v8::internal::UNINITIALIZED, object=..., name=...) at ../3rdparty/v8/src/ic.cc:933 #8 0x00007ffff4960ff5 in v8::internal::LoadIC_Miss (args=..., isolate=0x603070) at ../3rdparty/v8/src/ic.cc:2001 #9 0x000034b88ae0618e in ?? () ... [more ?? frames from the JIT'ed v8::Function code] ... #1 0x00007ffff481c3ef in v8::Function::Call (this=0x694fe0, recv=..., argc=0, argv=0x0) at ../3rdparty/v8/src/api.cc:3709 #2 0x00007ffff7379afd in QQmlJavaScriptExpression::evaluate (this=0x6d7430, context=0x6d8440, function=..., isUndefined=0x7fffffffcd23) at qml/qqmljavascriptexpression.cpp:171 #3 0x00007ffff72b7b85 in QQmlBinding::update (this=0x6d7410, flags=...) at qml/qqmlbinding.cpp:285 #4 0x00007ffff72b8237 in QQmlBinding::setEnabled (this=0x6d7410, e=true, flags=...) at qml/qqmlbinding.cpp:389 #5 0x00007ffff72b8173 in QQmlBinding::setEnabled (This=0x6d7448, e=true, f=...) at qml/qqmlbinding.cpp:370 #6 0x00007ffff72c15fb in QQmlAbstractBinding::setEnabled (this=0x6d7448, e=true, f=...) a /../../qtbase/include/QtQml/5.0.0/QtQml/private/../../../../../../qtdeclarative/src/qml/qml/qqmlabstractbinding_p.h:98 #7 0x00007ffff72dcb14 in QQmlVME::complete (this=0x698930, interrupt=...) at qml/qqmlvme.cpp:1292 #8 0x00007ffff72c72ae in QQmlComponentPrivate::complete (enginePriv=0x650560, state=0x698930) at qml/qqmlcomponent.cpp:919 #9 0x00007ffff72c739b in QQmlComponentPrivate::completeCreate (this=0x698890) at qml/qqmlcomponent.cpp:954 #10 0x00007ffff72c734c in QQmlComponent::completeCreate (this=0x698750) at qml/qqmlcomponent.cpp:947 #11 0x00007ffff72c6b2f in QQmlComponent::create (this=0x698750, context=0x68ea30) at qml/qqmlcomponent.cpp:781 #12 0x00007ffff79d4dce in QQuickView::continueExecute (this=0x7fffffffd2f0) at items/qquickview.cpp:445 #13 0x00007ffff79d3fca in QQuickViewPrivate::execute (this=0x64dc10) at items/qquickview.cpp:106 #14 0x00007ffff79d4400 in QQuickView::setSource (this=0x7fffffffd2f0 at items/qquickview.cpp:243 #15 0x0000000000400d70 in main ()
We see that the wrapper in the qv8qobjectwrapper.cpp file calls the QObject :: qt_metacall function (QMetaObject :: ReadProperty, ...) to get the value of the desired property. The wrapper (wrapper object) was called from the compiled V8 code stored in V8 :: Function. The generated machine code unfortunately does not have a call stack, so GDB is not able to show what is going on for ?? . I deceived you a little and showed above two different call stacks, which explains a little the gap in line numbering.
Once again: V8 uses an object wrapper to get property values. In the same way, it uses a context wrapper that allows you to search for the objects themselves. for example, the parent object we are accessing during the execution of the binding.
So, the binding is done by running the V8 :: Function code. The V8 engine gains access to unknown objects and properties by calling wrappers from Qt. The output of the V8 :: Function is written to the target property.
Binding update
Well, we now know how the text property got its original value. What about his update? Where does the QML engine learn about the need to update this property when the height or width of the parent rectangle changes?
The answer to this question lives in the same object wrapper (object wrapper), which, as you remember, is called when V8 is required to access the property. Our wrapper does little more than just return the value of a property: it records all the properties to which access was requested. Essentially, when a property is accessed, the wrapper calls the binding capture function that is currently running. In our example, this is QQmlJavaScriptExpression :: GuardCapture :: captureProperty () (QQmlBinding is a subclass of the QQmlJavaScriptExpression class).
In the capture function, the binding is simply simply attached to the NOTIFY type signal of the property that was requested. Now, when the NOTIFY signal is triggered, the binding slot connected to it will be called and the binding itself will be restarted. If you have never heard of a NOTIFY signal, do not worry, its logic is simple: When a property is defined using the Q_PROPERTY macro, then there will always be a NOTIFY signal that will be sent whenever the property is changed.
For example, here is the declaration of the width property in the QQuickItem class:
In our scenario: when the width property is addressed for the first time, during the first start of our binding, the function of capturing the property connects the widthChanged () signal and the launch slot of our binding. Now that the QQuickItem object emits a widthChanged () signal while the application is running, all the bindings attached to it will be called and restarted.
It is very important to have a NOTIFY signal in self-defined properties and send it out whenever our property changes. If you forget to do this, the binding will not be restarted and, accordingly, the property binding will not work correctly. On the other hand, if the NOTIFY signal is sent at the time when the property has not been changed, the binding will be idle.
To summarize: When a property is accessed, the wrapper object calls the capture function from the binding, which attaches this binding to the NOTIFY signal of the property so that when the property changes, the binding is performed again ...
Conclusion
Today we looked at how bindings work in QML. Briefly: a binding is a JS function that is executed every time the properties involved in it change.
I hope you were interested in this article, for example it was very interesting for me to study the internal logic of how bindings work.
In our next post we will look at the different types of bindings. All that we have reviewed today is a basic binding, or QQmlBinding, but we know that there are other types, for example, coiled bindings. We will solve their secret in the near future, stay tuned!