| 130 | |
| 131 | === QMetaObject and friends === |
| 132 | |
| 133 | This section provides some background on the introspection facilities in Qt, which are the basis of the JS binding. |
| 134 | |
| 135 | QMetaObject (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 | |
| 137 | Most 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 | |
| 139 | Class 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 | |
| 145 | Overloaded 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 | |
| 147 | In 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 | |
| 154 | Context: 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. |
| 155 | QtValueToJS() 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 | |
| 157 | Inputs: JS object reference, JS property name. |
| 158 | |
| 159 | Output: JS value. |
| 160 | |
| 161 | 1. Convert JS object reference to QObject*. |
| 162 | |
| 163 | 2. Convert the JS property name to a Qt (Latin-1) string. |
| 164 | |
| 165 | 3. Get the meta-object of Result(1) (QObject::metaObject()). |
| 166 | |
| 167 | 4. Query Result(3)'s property by name Result(2). |
| 168 | |
| 169 | 5. 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 | |
| 171 | 6. Query Result(3)'s method by name/signature Result(2). |
| 172 | |
| 173 | 7. If Result(6) is valid, return a JS method wrapper object for Result(6). |
| 174 | |
| 175 | 8. Query Result(1)'s dynamic property by name Result(2). |
| 176 | |
| 177 | 9. If Result(8) is valid, return QtValueToJS(value of property Result(8)). |
| 178 | |
| 179 | 10. Query Result(1)'s child objects by name Result(2). |
| 180 | |
| 181 | 11. If Result(10) is valid, return a JS wrapper object for Result(10). |
| 182 | |
| 183 | 12. 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 | |
| 187 | Context: 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 | |
| 189 | Inputs: JS "this"-object reference, JS method wrapper object reference (callee), JS arguments list (, optional "callback data"). |
| 190 | |
| 191 | Output: JS value, or an error thrown if something went wrong. |
| 192 | |
| 193 | 1. Convert the JS "this"-object reference to QObject*. |
| 194 | |
| 195 | 2. 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 | |
| 197 | 3. If the number of JS arguments is less than the method's number of parameters, throw a SyntaxError. |
| 198 | |
| 199 | 4. Get the parameter type IDs of the method. These can be calculated from QMetaMethod::parameterTypes(), or cached if possible. |
| 200 | |
| 201 | 5. Convert the JS arguments to C++ values. |
| 202 | |
| 203 | 6. If conversion failed, throw a TypeError. |
| 204 | |
| 205 | 7. Get the return type ID and reserve space for the return value. |
| 206 | |
| 207 | 8. Create an array of void* suitable for QMetaObject::metacall() (return value + arguments). |
| 208 | |
| 209 | 9. QMetaObject::metacall(Result(1), QMetaObject::InvokeMetaMethod, Result(2)'s index, Result(8)). |
| 210 | |
| 211 | 10. Return QtValueToJS(Result(9)). |
| 212 | |
| 213 | For 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 | |
| 215 | The 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). |