(Reproduced) In-depth analysis of qml engine --- (2) binding (binding)

The previous article talked about the process of loading the qml file by the QML engine. The general process is to parse the qml file and then create a corresponding c++ object for each element in the file. For example, if the Text type is used in the qml file , the engine will create an instance of the corresponding QQuickText class.

After the qml file is loaded by the engine, the QML engine does not participate much in the running phase, and the event processing and scene drawing are completed by the C++ class. For example, the c++ class corresponding to the TextInput control type in qml is QQuickTextInput . The input event of this control is handled by QQuickTextInput::keyPressEvent() , and the drawing is handled by ***QQuickTextInput::updatePaintNode()***. The QML engine does not Participate again.

At runtime, the QML engine is still involved in two things: Bound signal handlers and property binding updates . For example, the onClicked signal of the MouseArea control will correspond to a processing function, which is Bound signal handlers .

import QtQuick 2.0
Rectangle {
    
    
    width: 300
    height: 300
    color: "lightsteelblue"
    Text {
    
    
        anchors.centerIn: parent
        text: "Window Area: " + (parent.width * parent.height)
    }
}

As shown in the example above, it covers two types of property bindings:

  1. Simple property assignments ( Simple value assignments ). For example, the width property of QQuickRectangle is assigned a value of 300. The VME instruction corresponding to this assignment action is STORE_DOUBLE, this instruction will be executed when creating a c++ object, VME will call QMetaObject::metacall(QMetaObject::WriteProperty, …), this function Finally call QQuickRectangle::setWidth(). After the setting is completed, the QML engine will not change the value of the width property.
  2. Binding assignments . For example, in the above example, the text attribute of Text is associated with the width attribute of its parent. When the width attribute of its parent changes, the value of its text automatically changes accordingly. How to achieve it internally? Please see the following for binding analyze:

Creating the Binding
set QML_COMPILER_DUMP=1, we can see that the instructions contained in the bytecode are as follows (for bytecode, please refer to the previous article), the instruction corresponding to the two types of binding is STORE_COMPILED_BINDING .

...
9               STORE_COMPILED_BINDING  43      1       0
10              FETCH                   19
11              STORE_COMPILED_BINDING  17      0       1
...

The Compiled binding method is an optimized binding method. In this article, we first look at the ordinary binding. The instruction corresponding to the ordinary binding is STORE_BINDING.

Looking at the QQmlVME::run() code, you can find that this function will create a QQmlBinding object, which has an expression: " function $text() { return "Window Area: " + (parent.width * parent.height) } " . corresponds to a javaScript function. The "function $text()" part of the expression is added by the QML compiler. The reason for adding it is because QML uses the javaScript V8 engine, which only supports complete functions. . This JavaScript function will be compiled into a V8::Function object by the V8 compiler. At this time, the V8::Function object will not be executed, but it will always be retained.

(The traditional JavaScript engine compiles the JavaScript code into bytecode first, and then executes the bytecode through the interpreter. The V8 engine uses JIT technology, does not execute the bytecode through the interpreter, but directly compiles the JavaScript code into a running machine code on CPU (x86/x64/ARM)).

The creation of a binding by the STORE_BINDING instruction can be summarized as follows: a QQmlBinding object is created first , and then the object compiles the JavaScript function passed to it into a V8::Function object by means of the V8 engine.

Running the Binding
At some point, you need to run the bound function (the javaScript function mentioned above), then the V8 engine will run the number of bound lines and assign the result to the corresponding property. These are all done in the last phase of the creating phase (the phase of creating c++ objects). In the final phase, ***QQmlVME::complete() will call the update() function of each bound object, in our case it is QQmlBinding:: update()*** function. update() simply executes the v8:Function object and assigns the return value to the target property, which in our case is the text property of the Rectangle.

But how does the V8 engine know the values ​​of parent.width and parent.height? Actually, it doesn't know. The V8 engine does not have any clues to know which QObject objects are contained in the qml file (the c++ class corresponding to each qml basic type is inherited from the QObject class), nor does it know the hierarchical relationship between them, nor does it know each object What are the attributes.

When the V8 engine encounters an unknown class object or unknown property, it will ask an object wrapper (Object Wrapper) in the QML engine, and the object wrapper will find the correct class or property for it, and return them to V8 engine. Let's take a look at how the width property of QQuickItem is accessed through the stack information:

#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 can see that the wrapper is in qv8qobjectwrapper.cpp and ends up calling QObject::qt_metacall(QMetaObject::ReadProperty, …) to get the property value. The wrapper was called from v8 code, which itself was called by generated machine code of our v8::Function object. The generated machine code doesn’t have stack frames, and therefore GDB is unable to show the backtrace after the ??. I cheated a bit and pieced together this backtrace from two separate backtraces, which explains the inconsistent frame numbering.

From the above stack information, we found that the wrapper class in qv8qobjectwrapper.cpp finally calls the function QObject::qt_metacall(QMetaObject::ReadProperty,…) to get the property value.

So the v8 engine involves an object wrapper to get property values. In the same vein, it involves a context wrapper to find objects themselves, for example the parent object that is accessed during binding evaluation.

To sum up: A binding is evaluated by running the compiled v8::Function code. The v8 engine access unknown objects and properties by calling out to wrappers in Qt. The result returned by the v8::Function is then written to the target property.

Updating the Binding

Ok, now we know how the text property gets its initial value. But how is binding update implemented? How does the QML engine know to re-evaluate the binding when the height and width properties change?

The answer to this question is hidden in the object wrapper class. You should recall that it is called when the V8 engine needs to access a property. This object wrapper class doesn't just return property values: it also captures all properties that were accessed. Basically, when a property is accessed, the object wrapper class calls the bound object's capture function, in our case QQmlJavaScriptExpression::GuardCapture::captureProperty() (QQmlBinding is a subclass of QQmlJavaScriptExpression). In the internal implementation of the capture function, simply connect a slot function of the bound object to the NOTIFY signal of the captured property. When the NOTIFY signal is triggered, the slot function connected to it will be called and the bound value will be recalculated. If you haven't heard of the NOTIFY signal, don't worry, it's simple: when a property is declared with a Q_PROPERTY, a NOTIFY signal may be declared there. Whenever a property changes, the object that owns that property fires the NOTIFY signal. For example, the declaration of the width attribute of QQuickItem is similar to the following:

Q_PROPERTY(qrealwidth READ width WRITE setWidth NOTIFY widthChanged)

In our example, when the binding is run for the first time, when the width property is accessed, the property's capture function connects a slot in the binding object to the widthChanged() signal. After that, as long as the QQuickItem triggers the widthChanged() signal, the corresponding slot function will be called and the bound value will be recalculated.

That's why it's very important to have and fire a NOTIFY signal when your properties change. If you forget to do this, the bound value won't be recalculated, and basically, property binding won't work correctly. On the other hand, if you trigger the NOTIFY signal even though the property hasn't actually changed, the bound value will be recalculated pointlessly.

To sum up: When accessing a property, the object wrapper class will call the capture function of the bound object, which will connect a slot function of the bound object to the NOTIFY signal of the property, so that when the property changes, the bound value will be recalculated. value.

Ref:
http://www.kdab.com/qml-engine-internals-part-2-bindings/
https://www.jianshu.com/p/9b277a3ee613

Original link: https://blog.csdn.net/qq_35865125/article/details/85921581

Guess you like

Origin blog.csdn.net/u011942101/article/details/130109444