Wiki

Marshalling Custom Types with Qt 4

by Harald Fernengel
Qt uses MOC, the meta-object compiler, to gather information about custom classes. This information is used for signals and slots and makes introspection of Qt applications possible. With Qt 4, three new features---dynamic slot invocation, queued signals, and support for custom variant types---have required us to extend Qt's support for meta-type information.

Qt 4.0 introduces the QMetaType class to make it possible to register a value type at run-time. Any class or struct that has a public constructor, a public copy constructor, and a public destructor can be registered. For example:

qRegisterMetaType<Employee>("Employee");

The Employee is now registered. QMetaType now knows how to construct, copy, and destroy instances of that class. Each registered class is identified by a unique ID number, which can be retrieved using QMetaType::type():

int employeeId = QMetaType::type("Employee");

We can use this ID to construct, copy, and destroy instances of Employee dynamically:

void *original = QMetaType::construct(employeeId, 0);
void *copy = QMetaType::construct(employeeId, original);
QMetaType::destroy(employeeId, original);
QMetaType::destroy(employeeId, copy);

In most Qt applications, qRegisterMetaType() is the only function we need to be aware of. Qt calls construct() and destroy() when it needs to store these custom types. In the following sections, we will see what Qt 4 features build upon QMetaType.

Dynamic Slot Invocation

QMetaObject allows us to introspect any QObject subclass for its signals and slots. This is commonly used in testing frameworks, in scripting bindings, and even for accessibility utilities.

In earlier Qt versions, the only way to invoke a slot dynamically was by connecting the slot to a signal and emitting the signal. In Qt 4, a slot can be invoked by name using QMetaObject::invokeMember():

QMetaObject::invokeMember(object, "clear");

If the slot takes arguments, they can be passed using the QArgument class or, more conveniently, using the Q_ARG() macro:

QMetaObject::invokeMember(statusBar, "showMessage",
                          Q_ARG(QString, tr("Ready")),
                          Q_ARG(int, 2000));

Behind the scenes, QMetaType is used to marshal the types, so any registered type can be passed to QArgument or Q_ARG(). Basic types such as int and Qt types such as QString are preregistered, so we can pass them without formality.

Queued Connections

In Qt 3, signals were always delivered synchronously. Qt 4 introduces the concept of queued connections, where the slot is invoked asynchronously. Similarly to posting an event, the signal will be delivered after the application enters the event loop again. In fact, internally, queued signals are implemented as events.

Let's assume that mySignal(const QString &) is connected to mySlot(const QString &) using Qt::QueuedConnection as the connection type, and that the signal is emitted:

emit mySignal("OK");

The signal is emitted, but the slot isn't invoked until control returns to the event loop. To make this work, Qt must take a copy of the QString argument, store it in an event, deliver the event to the receiver object, and call the slot with the QString.

This is where QMetaType comes in. Since QMetaType knows how to copy and destroy classes, it can be used to construct a copy of the parameter and destroy it once the event is delivered. This makes it possible to emit queued signals with custom data types:

emit myOtherSignal(Employee(name, title, salary));

Queued connections are especially useful in multithreaded applications. See the article Qt 4's Multithreading Enhancements for more information on this topic.

Custom Types in QVariant

QVariant is a storage class for basic types and Qt types. With Qt 4, you can now register any type, provided that it is registered using QMetaType. To construct a QVariant storing a custom type, use the constructor that takes a type ID and a void pointer:

Employee employee(name, title, salary);
QVariant value(employeeId, &employee);

At any time, we can use QVariant::constData() to retrieve a pointer to the stored object. More interestingly, we can call one of QVariant's template member functions that work with custom types as well as predefined types: setValue(), value(), fromValue(), and canConvert(). For example:

QVariant variant;
...
if (variant.canConvert<Employee>()) {
    Employee employee = variant.value<Employee>();
    ...
}

If you need to build your application using MSVC 6 (which lacks support for template member functions), you must use global functions instead:

QVariant variant;
...
if (qVariantCanConvert<Employee>(variant)) {
    Employee employee = qVariantValue<Employee>(variant);
    ...
}

Either way, to make this template code compile, we must use the Q_DECLARE_METATYPE() macro in addition to qRegisterMetaType(). We can put the macro under the class definition or in any header file:

Q_DECLARE_METATYPE(Employee)

Custom variant types are very useful in conjunction with Qt 4's model/view classes, since the model and the delegate communicate with each other through QVariants.

The SQL module is another interesting application of custom variants. QSqlQuery::lastInsertId() returns a QVariant with the ID of the row that was inserted. This ID is database-dependent and we are not interested in its actual value (it could be a simple integer or a database-dependent structure pointing at a database row). But even without knowing its type, we can copy the ID around and use it as parameter in a SQL query via QSqlQuery::bindValue().

Also, the QSettings class in Qt 4 uses QVariant throughout its API and allows you to store custom types, provided that they can be streamed to and from QDataStream using operator<<() and operator>>(). This requires you to call qRegisterMetaTypeStreamOperators() in addition to qRegisterMetaType().


Copyright © 2005 Trolltech Trademarks