Marshalling Custom Types with Qt 4

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 14 | Документация

by Harald Fernengel

Qt uses MOC, the meta-object compiler, to gather information aboutcustom classes. This information is used for signals and slots andmakes introspection of Qt applications possible. With Qt 4, three newfeatures---dynamic slot invocation, queued signals, andsupport for custom variant types---have required us to extendQt's support for meta-type information.

Содержание

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

qRegisterMetaType<Employee>("Employee");

The Employee is now registered. QMetaTypenow knows how to construct, copy, and destroy instances of thatclass. 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 ofEmployee 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 weneed to be aware of. Qt calls construct() and destroy() whenit needs to store these custom types. In the following sections, wewill see what Qt 4 features build upon QMetaType.

[править] Dynamic Slot Invocation

QMetaObject allows us to introspect any QObject subclass forits 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 wasby 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, soany registered type can be passed to QArgument or Q_ARG().Basic types such as int and Qt types such as QString arepreregistered, so we can pass them without formality.

[править] Queued Connections

In Qt 3, signals were always delivered synchronously. Qt 4 introducesthe concept of queued connections, where the slot is invokedasynchronously. Similarly to posting an event, the signal will bedelivered 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 connectedto mySlot(const QString &) using Qt::QueuedConnectionas the connection type, and that the signal is emitted:

emit mySignal("OK");

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

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

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

Queued connections are especially useful in multithreadedapplications. See the article Qt 4's Multithreading Enhancementsfor more information on this topic.

[править] Custom Types in QVariant

QVariant is a storage class for basic types and Qt types. With Qt4, you can now register any type, provided that it is registeredusing 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, &amp;employee);

At any time, we can use QVariant::constData() to retrieve apointer to the stored object. More interestingly, we can call one of QVariant's template member functions that work with custom typesas 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 lackssupport for template member functions), you must use globalfunctions instead:

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

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

Q_DECLARE_METATYPE(Employee)

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

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

Also, the QSettings class in Qt 4 uses QVariant throughoutits API and allows you to store custom types, provided that they canbe streamed to and from QDataStream using operator<<() andoperator>>(). This requires you to callqRegisterMetaTypeStreamOperators() in addition toqRegisterMetaType().