Changes between Version 3 and Version 4 of QtScript


Ignore:
Timestamp:
Aug 5, 2010 7:16:33 AM (9 years ago)
Author:
kent.hansen@nokia.com
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • QtScript

    v3 v4  
    128128
    129129'''Supporting QScriptable''': The string-based (slow) QObject::qt_metacast() is used to determine if the object implements the QScriptable interface. This is done per method call / property access. This information should also be cached in the meta-method/property wrapper if possible.
     130
     131=== QMetaObject and friends ===
     132
     133This section provides some background on the introspection facilities in Qt, which are the basis of the JS binding.
     134
     135QMetaObject (http://doc.trolltech.com/latest/qmetaobject.html) is the class that enables a QObject to be introspected at runtime. An object's meta-object can be obtained by calling QObject::metaObject().
     136
     137Most importantly, the meta-object contains information about the ("static") properties, and methods (signals/slots/Q_INVOKABLEs), of the class. Dynamic properties and child objects are handled per instance, i.e. they don't concern the meta-object.
     138
     139Class members are queried by index: QMetaObject::property(int) and QMetaObject::method(int). Functions QMetaObject::indexOfProperty(const char*) and QMetaObject::indexOfMethod(const char*) can be used to map a property name or (normalized) function signature to an index. They're pretty slow since they just perform a linear search w/ string compare (no hashing).
     140
     141'''Properties''': QMetaObject::property() returns a QMetaProperty. The QMetaProperty can be used to query various flags of the property (writable, scriptable, ...), the type name and meta-type ID. To read (write) the property of an actual instance, call QMetaProperty::read(QObject*) (QMetaProperty::write(QObject*, QVariant)). read() returns a QVariant and write() expects a QVariant as value, so you will typically have to convert from/to QVariant when dealing with these functions.
     142
     143'''Methods''': QMetaObject::method() returns a QMetaMethod. The QMetaMethod can be used to query the kind (signal/slot/method), attributes, access (private/protected/public), the parameter names, and parameter types and return type (in _string_ form only!). To invoke a method of an actual instance, one can call QMetaMethod::invoke(). invoke() can't be invoked with a variable-length argument list; you need to explicitly pass all arguments (up to 10). This makes it badly suited for use in bindings. Instead, in the bindings we use an internal function, QMetaObject::metacall(). (Potentially this function could be used for reading and writing properties as well, since it would get rid of some overhead, e.g. creating QVariants.) When using the low-level metacall() function, all that's needed is a pointer to the QObject, index of property/method, and storage for the return value and arguments (passed as an array of void*).
     144
     145Overloaded methods will have separate entries in the meta-object (with unique signatures). This also includes methods that have default arguments (they will appear as overloads in the meta-object).
     146
     147In order to allocate storage for the return value and parameters, the corresponding meta-type IDs are needed; then one can create a QVariant containing such a type or call QMetaType::construct(). For properties, the type ID is stored directly in the meta-data for built-in types; but for methods, only the signature is stored, so a (slow) string-to-ID lookup needs to be performed. It would be a nice addition if type IDs for methods were stored in the meta-data as well, as it would allow
     148
     149'''Super-class members''': A QMetaObject's data only includes members declared by its class, _not_ inherited members. The QMetaObject's superClass() function returns the QMetaObject of the super-class. However, note that the property() and method() functions both take an index that's absolute, starting at the ultimate base class, QObject; for example, property(0) will always return the property descriptor for QObject::objectName, regardless of which class's meta-object you query. The members declared by the class itself start at QMetaObject::propertyOffset() and QMetaObject::methodOffset() (i.e., this offset equals the total number of inherited members). The number of non-inherited members is simply propertyCount() - propertyOffset() and methodCount() - methodOffset().
     150
     151
     152=== The Abstract Property Lookup Algorithm ===
     153
     154Context: A JS wrapper object has been created from a QObject*. Then a property of the wrapper object is read from JS, and we want to return the proper value.
     155QtValueToJS() is a helper function that converts a QVariant to a JS value (not shown). (Note: The algorithm currently doesn't take wrap options, e.g. that exclude child objects, into account; nor QScriptable.)
     156
     157Inputs: JS object reference, JS property name.
     158
     159Output: JS value.
     160
     1611. Convert JS object reference to QObject*.
     162
     1632. Convert the JS property name to a Qt (Latin-1) string.
     164
     1653. Get the meta-object of Result(1) (QObject::metaObject()).
     166
     1674. Query Result(3)'s property by name Result(2).
     168
     1695. If Result(4) is valid, return QtValueToJS(value of property Result(4) for Result(1)). (Alternatively, return a getter/setter accessor for the property.)
     170
     1716. Query Result(3)'s method by name/signature Result(2).
     172
     1737. If Result(6) is valid, return a JS method wrapper object for Result(6).
     174
     1758. Query Result(1)'s dynamic property by name Result(2).
     176
     1779. If Result(8) is valid, return QtValueToJS(value of property Result(8)).
     178
     17910. Query Result(1)'s child objects by name Result(2).
     180
     18111. If Result(10) is valid, return a JS wrapper object for Result(10).
     182
     18312. The property by the given name should not be handled by the QObject binding; return a status indicating so.
     184
     185=== The Abstract Method Invocation Algorithm ===
     186
     187Context: A JS method wrapper object has been returned to JS (as described in the previous section), and is now being called as a function; this should cause the underlying C++ method to be invoked, and the result, if any, passed back to JS.
     188
     189Inputs: JS "this"-object reference, JS method wrapper object reference (callee), JS arguments list (, optional "callback data").
     190
     191Output: JS value, or an error thrown if something went wrong.
     192
     1931. Convert the JS "this"-object reference to QObject*.
     194
     1952. Figure out which meta-method is being invoked. It would be possible to have a single method wrapper that looks up the method by name each time, but that's going to be slow. A per-method (per class) wrapper can store the index of the meta-method for fast access.
     196
     1973. If the number of JS arguments is less than the method's number of parameters, throw a SyntaxError.
     198
     1994. Get the parameter type IDs of the method. These can be calculated from QMetaMethod::parameterTypes(), or cached if possible.
     200
     2015. Convert the JS arguments to C++ values.
     202
     2036. If conversion failed, throw a TypeError.
     204
     2057. Get the return type ID and reserve space for the return value.
     206
     2078. Create an array of void* suitable for QMetaObject::metacall() (return value + arguments).
     208
     2099. QMetaObject::metacall(Result(1), QMetaObject::InvokeMetaMethod, Result(2)'s index, Result(8)).
     210
     21110. Return QtValueToJS(Result(9)).
     212
     213For overloaded methods, things get a bit more complicated. First, we try to find an overload whose number of parameters is equal to the number of JS arguments passed. If that doesn't help, for each overload, we try to convert the JS arguments to the overload's expected C++ types, and use a heuristic to figure out which one's the best match. If a perfect match is found, that one is called immediately. Otherwise, the overload with the best matching argument conversion wins. If there's more than one "winner" (no single method that had the best conversion score), an ambiguity error is thrown.
     214
     215The above algorithm doesn't consider the case where the QObject inherits QScriptable. In that case, an internal member must be set on the QScriptable before invoking the C++ method, so that the C++ method has access to the original JS environment of the call (see http://doc.trolltech.com/qscriptable.html).