Qt и Q_REGISTER_METATYPE — что это такое, и как его использовать?
Для класса QAbstractSocket генерируется сигнал error с параметром QAbstractSocket::SocketError socketError. В документации написано: «QAbstractSocket::SocketError is not a registered metatype, so for queued connections, you will have to register it with Q_REGISTER_METATYPE», но про этот Q_REGISTER_METATYPE нет упоминания вообще нигде. Сталкивался-ли кто-нить с этим жЫвотным? (А я пока погуглю, мало-ли).
one_more_hokum ★★★
08.08.08 11:28:17 MSD
Пользовательские типы в Qt по D-Bus

На хабре были статьи о D-Bus в Qt (раз) и немного затронули пользовательские типы (два). Здесь будет рассмотрена реализация передачи пользовательских типов, связанные с ней особенности, обходные пути.
Статья будет иметь вид памятки, с небольшим вкраплением сниппетов, и для себя и для коллег.
Примечание: изучалось под Qt 4.7(Спасибо Squeeze за это. ), поэтому некоторые действия могут оказаться бесполезными.
Введение
Стандартные типы, для передачи которых не требуется лишних телодвижений есть в доке. Также реализована возможность передавать по D-Bus тип QVariant (QDBusVariant). Это позволяет передавать те типы, которые QVariant может принимать в конструкторе — от QRect до QVariantList и QVariantMap (двумерные массивы не работают как положено). Есть соблазн передавать свои типы преобразовывая их в QVariant. Лично я рекомендовал бы отказаться от такого способа, поскольку принимающая сторона не сможет различить несколько различных типов — все они будут для неё QVariant. На мой взгляд это может потенциально привести к ошибкам и усложнит поддержку.
Готовим свои типы
Для начала опишем типы, которые будут использоваться в приложениях.
Первый тип будет Money
struct Money < int summ; QString type; Money() : summ(0) , type() <>>;
Для начала его надо задекларировать в системе типов:
Перед началом передачи типа, необходимо вызвать функции для регистрации типа
(«Money»);
qDBusRegisterMetaType();>
Для возможности его передачи по D-Bus типу необходимо добавит методы его разбора и сбора на стандартные типы (marshalling & demarshalling).
marshalling & demarshalling
friend QDBusArgument& operator <<(QDBusArgument& argument, const Money& arg) < argument.beginStructure(); argument << arg.summ; argument << arg.type; argument.endStructure(); return argument; >friend const QDBusArgument& operator >>(const QDBusArgument& argument, Money& arg) < argument.beginStructure(); argument >> arg.summ; argument >> arg.type; argument.endStructure(); return argument; >
Чтобы не было так скучно, добавим еще несколько типов. Полностью файлы с типами выглядят так:
[types.h]
#include #include #include #include #include //Имя и путь D-Bus интерфейса для будущего сервиса namespace dbus < static QString serviceName() < return "org.student.interface"; >static QString servicePath() < return "/org/student/interface"; >> struct Money < int summ; QString type; Money() : summ(0) , type() <>friend QDBusArgument &operator<<(QDBusArgument &argument, const Money &arg); friend const QDBusArgument &operator>>(const QDBusArgument &argument, Money &arg); >; Q_DECLARE_METATYPE(Money) struct Letter < Money summ; QString text; QDateTime letterDate; Letter() : summ() , text() , letterDate() <>friend QDBusArgument &operator<<(QDBusArgument &argument, const Letter &arg); friend const QDBusArgument &operator>>(const QDBusArgument &argument, Letter &arg); >; Q_DECLARE_METATYPE(Letter) //Добавим к типам массив пользовательских писем typedef QList Stuff; Q_DECLARE_METATYPE(Stuff) struct Parcel < Stuff someFood; Letter letter; Parcel() : someFood() , letter() <>friend QDBusArgument &operator<<(QDBusArgument &argument, const Parcel &arg); friend const QDBusArgument &operator>>(const QDBusArgument &argument, Parcel &arg); >; Q_DECLARE_METATYPE(Parcel)
[types.cpp]
#include "types.h" #include #include //Регистрация типов статической структурой static struct RegisterTypes < RegisterTypes() < qRegisterMetaType("Money"); qDBusRegisterMetaType(); qRegisterMetaType("Letter"); qDBusRegisterMetaType(); qRegisterMetaType("Stuff"); qDBusRegisterMetaType(); qRegisterMetaType("Parcel"); qDBusRegisterMetaType(); > > RegisterTypes; //------------------------ QDBusArgument& operator <<(QDBusArgument& argument, const Money& arg) < argument.beginStructure(); argument << arg.summ; argument << arg.type; argument.endStructure(); return argument; >const QDBusArgument& operator >>(const QDBusArgument& argument, Money& arg) < argument.beginStructure(); argument >> arg.summ; argument >> arg.type; argument.endStructure(); return argument; > //------------------------ QDBusArgument& operator <<(QDBusArgument& argument, const Letter& arg) < argument.beginStructure(); argument << arg.summ; argument << arg.text; argument << arg.letterDate; argument.endStructure(); return argument; >const QDBusArgument& operator >>(const QDBusArgument& argument, Letter& arg) < argument.beginStructure(); argument >> arg.summ; argument >> arg.text; argument >> arg.letterDate; argument.endStructure(); return argument; > //------------------------ QDBusArgument& operator <<(QDBusArgument& argument, const Parcel& arg) < argument.beginStructure(); argument << arg.someFood; argument << arg.letter; argument.endStructure(); return argument; >const QDBusArgument& operator >>(const QDBusArgument& argument, Parcel& arg) < argument.beginStructure(); argument >> arg.someFood; argument >> arg.letter; argument.endStructure(); return argument; >
Отмечу, что для использования массивов можно использовать QList и для них не требуется маршаллизация и демаршаллизация, если для переменных уже есть преобразования.

Начинаем строить
Предположим есть два Qt приложения, которым нужно общаться по D-Bus. Одно приложение будет регистрироваться как сервис, а второе с этим сервисом взаимодействовать.
Я ленивый и мне лень создавать отдельный QDBus адаптер. Поэтому, для того что бы разделять внутренние методы и интерфейс D-Bus, интерфейсные методы отмечу макросом Q_SCRIPTABLE.
[student.h]
#include #include "../lib/types.h" class Student : public QObject < Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.student.interface") public: Student(QObject *parent = 0); ~Student(); signals: Q_SCRIPTABLE Q_NOREPLY void needHelp(Letter reason); void parcelRecived(QString parcelDescription); public slots: Q_SCRIPTABLE void reciveParcel(Parcel parcelFromParents); void sendLetterToParents(QString letterText); private: void registerService(); >;
Тег Q_NOREPLY отмечает, что D-Bus не должен ждать ответа от метода.
Для регистрации сервиса с методами отмеченных Q_SCRIPTABLE используется такой код:
[Регистрация сервиса]
void Student::registerService() < QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, dbus::serviceName()); if (!connection.isConnected()) qDebug()<<(QString("DBus connect false")); else qDebug()<<(QString("DBus connect is successfully")); if (!connection.registerObject(dbus::servicePath(), this, QDBusConnection::ExportScriptableContents)) < qDebug()<<(QString("DBus register object false. Error: %1").arg(connection.lastError().message())); >else qDebug() <<(QString("DBus register object successfully")); if (!connection.registerService(dbus::serviceName())) < qDebug()<<(QString("DBus register service false. Error: %1").arg(connection.lastError().message())); >else qDebug()
Полностью cpp файл выглядит так:
[student.cpp]
#include "student.h" #include #include #include Student::Student(QObject *parent) : QObject(parent) < registerService(); >Student::~Student() < >void Student::reciveParcel(Parcel parcelFromParents) < QString letterText = parcelFromParents.letter.text; letterText.append(QString("\n Money: %1 %2").arg(parcelFromParents.letter.summ.summ).arg(parcelFromParents.letter.summ.type)); Stuff sendedStuff = parcelFromParents.someFood; QString stuffText; foreach(QVariant food, sendedStuff) < stuffText.append(QString("Stuff: %1\n").arg(food.toString())); >QString parcelDescription; parcelDescription.append(letterText); parcelDescription.append("\n"); parcelDescription.append(stuffText); emit parcelRecived(parcelDescription); > void Student::sendLetterToParents(QString letterText) < Letter letterToParents; letterToParents.text = letterText; letterToParents.letterDate = QDateTime::currentDateTime(); emit needHelp(letterToParents); >void Student::registerService() < QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, dbus::serviceName()); if (!connection.isConnected()) qDebug()<<(QString("DBus connect false")); else qDebug()<<(QString("DBus connect is successfully")); if (!connection.registerObject(dbus::servicePath(), this, QDBusConnection::ExportScriptableContents)) < qDebug()<<(QString("DBus register object false. Error: %1").arg(connection.lastError().message())); >else qDebug() <<(QString("DBus register object successfully")); if (!connection.registerService(dbus::serviceName())) < qDebug()<<(QString("DBus register service false. Error: %1").arg(connection.lastError().message())); >else qDebug()
Этот класс может успешно работать по D-Bus’у используя привычные конструкции.
Для вызова метода его интерфейса можно использовать QDBusConnection::send:
[Вызов D-Bus метода без ответа]
const QString studentMethod = "reciveParcel"; QDBusMessage sendParcel = QDBusMessage::createMethodCall(dbus::serviceName(), dbus::servicePath(), "", studentMethod); QList arg; arg.append(qVariantFromValue(parentsParcel)); sendParcel.setArguments(arg); if ( !QDBusConnection::sessionBus().send(sendParcel) )
Метод qVariantFromValue с помощью черной магии, void указателей и тем, что мы зарегистрировали тип, преобразует его в QVariant. Обратно его можно получить через шаблон метода QVariant::value или через qvariant_cast.
Если нужен ответ метода, то можно использовать другие методы QDBusConnection — для синхронного call и для асинхронного callWithCallback, asyncCall.
[Синхронный вызов D-Bus метода с ожиданием ответа]
const QString studentMethod = "reciveParcel"; QDBusMessage sendParcel = QDBusMessage::createMethodCall(dbus::serviceName(), dbus::servicePath(), "", studentMethod); QList arg; arg.append(qVariantFromValue(parentsParcel)); sendParcel.setArguments(arg); int timeout = 25; //Максимальное время ожидания ответа, если не указывать - 25 секунд QDBusReply reply = QDBusConnection::sessionBus().call(sendParcel, QDBus::Block, timeout); //QDBus::Block блокирует цикл событий(event loop) до получения ответа if (!reply.isValid()) < qDebug()<int returnedValue = reply.value();
[Асинхронный вызов D-Bus метода]
const QString studentMethod = "reciveParcel"; QDBusMessage sendParcel = QDBusMessage::createMethodCall(dbus::serviceName(), dbus::servicePath(), "", studentMethod); QList arg; arg.append(qVariantFromValue(parentsParcel)); sendParcel.setArguments(arg); int timeout = 25; //Максимальное время ожидания ответа, если не указывать - 25 секунд bool isCalled = QDBusConnection::sessionBus().callWithCallback(sendParcel, this, SLOT(standartSlot(int)), SLOT(errorHandlerSlot(const QDBusMessage&)), timeout) if (!isCalled)
Также можно воспользоваться методами класса QDBusAbstractInterface, в которых не участвует QDBusMessage.
Кстати, для отправки сигналов нет необходимости регистрировать интерфейс, их можно отправлять тем же методом send:
[Отправка сигнала]
QDBusMessage msg = QDBusMessage::createSignal(dbus::servicePath(), dbus::serviceName(), "someSignal"); msg
Вернёмся к примеру. Ниже представлен класс, который взаимодействует с интерфейсом класс Student.
[parents.h]
#include #include "../lib/types.h" class Parents : public QObject < Q_OBJECT public: Parents(QObject *parent = 0); ~Parents(); private slots: void reciveLetter(const Letter letterFromStudent); private: void connectToDBusSignal(); void sendHelpToChild(const Letter letterFromStudent) const; void sendParcel(const Parcel parentsParcel) const; Letter writeLetter(const Letter letterFromStudent) const; Stuff poskrestiPoSusekam() const; >;
[parents.cpp]
#include "parents.h" #include #include Parents::Parents(QObject *parent) : QObject(parent) < connectToDBusSignal(); >Parents::~Parents() < >void Parents::reciveLetter(const Letter letterFromStudent) < qDebug()void Parents::connectToDBusSignal() < bool isConnected = QDBusConnection::sessionBus().connect( "", dbus::servicePath(), dbus::serviceName(), "needHelp", this, SLOT(reciveLetter(Letter))); if(!isConnected) qDebug()void Parents::sendHelpToChild(const Letter letterFromStudent) const < Parcel preparingParcel; preparingParcel.letter = writeLetter(letterFromStudent); preparingParcel.someFood = poskrestiPoSusekam(); sendParcel(preparingParcel); >void Parents::sendParcel(const Parcel parentsParcel) const < const QString studentMethod = "reciveParcel"; QDBusMessage sendParcel = QDBusMessage::createMethodCall(dbus::serviceName(), dbus::servicePath(), "", studentMethod); QListarg; arg.append(qVariantFromValue(parentsParcel)); sendParcel.setArguments(arg); if ( !QDBusConnection::sessionBus().send( sendParcel) ) < qDebug()<> Letter Parents::writeLetter(const Letter letterFromStudent) const < QString text = "We read about you problem so send some help"; Letter parentLetter; parentLetter.text = text; Money summ; summ.summ = letterFromStudent.text.count(",")*100; summ.summ += letterFromStudent.text.count(".")*50; summ.summ += letterFromStudent.text.count(" ")*5; summ.type = "USD"; parentLetter.summ = summ; parentLetter.letterDate = QDateTime::currentDateTime(); return parentLetter; >Stuff Parents::poskrestiPoSusekam() const

Скачать пример можно отсюда.
Если всё идёт не настолько гладко
При разработке у меня возникла проблема: при обращении к D-Bus интерфейсу программы с пользовательскими типами программа падала. Решилось это всё добавлением xml описания интерфейса в класс с помощью макроса Q_CLASSINFO. Для примера выше это выглядит следующим образом:
[student.h]
… class Student : public QObject < Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.student.interface") Q_CLASSINFO("D-Bus Introspection", "" "\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" ) public: Student(QObject *parent = 0); …
Параметр type у аргументов это их сигнатура, она описывается по спецификации D-Bus. Если у вас есть маршализация типа, можно узнать его сигнатуру используя недокументированные возможности QDBusArgument, а именно его метод currentSignature().
[Получение сигнатуры типа]
QDBusArgument arg; arg
Тестирование интерфейса с пользовательскими типами
Тестирование сигналов
Для тестирования сигналов можно использовать qdbusviewer — он может приконнектится к сигналу и показать что за структуру он отправляет. Также для этого может подойти dbus-monitor — после указания адреса он будет показывать все исходящие сообщения интерфейса.
Тестирование методов
qdbusviewer не вызывает методы с пользовательскими типами. Для этих целей можно использовать d-feet. Несмотря на то, что для него трудно найти внятную документацию, он умеет вызывать методы с типами любой сложности. При работе с ним нужно учесть некоторые особенности:
[Работа с d-feet]
Переменные перечисляются через запятую.
Основные типы(в скобках обозначение в сигнатуре):
int(i) — число (пример: 42);
bool(b) — 1 или 0;
double(d) — число с точкой (пример: 3.1415);
string(s) — строка в кавычках (пример: ”string”);
Структуры берутся в скобки “(“ и “)”, переменные идут через запятую, запятую надо ставить даже когда в структуре один элемент.
Массивы — квадратные скобки “[“ и ”]”, переменные через запятую.
Типы Variant и Dict не изучал, так как не было необходимости.
Qt4 и Custom Types
Известно, что, без преувеличения, основа Qt это moc и их система метатипов. В базе метатипов зарегистрированы все простые C/C++ типы и все сложные Qt типы, часто этого бывает с головой достаточно для написания программ: при использовании QVariant и QSettings или при организации вызовов типа сигнал-слот…
Но бывает, что в качестве параметра сигнала нужно передавать свою структуру или класс, или преобразовывать свой тип в QVariant. Для этого нужно зарегистрировать свой тип перед первым использованием (можно в функции main()), примерно так:
qRegisterMetaType("PhotoFormat");
После этого его можно использовать в качестве параметра в механизме сигнал-слот.
Следующим полезным шагом - приведение нашего типа к QVariant, для этого, в дополнение к предыдущему, в заголовочном файла где располагается объявление нашего типа (на самом деле - в любом месте, но так будет логичнее) нужно разместить такую конструкцию:
Q_DECLARE_METATYPE(PhotoFormat);
Всё, после этого можно использовать подобные конструкции:
QVariant var = qVariantFromValue(PhotoFotmat("Name", 23, 22)); PhotoFormat format = qVariantValue(var);
Ну и последнее, сохранение и восстановление нашего класса при помощи QSettings. Для этого нужно, для начала, зарегистрировать потоковые операторы:
qRegisterMetaTypeStreamOperators("PhotoFormat");
Далее, обычно в заголовочном файле, где объявлен класс, объявляются операторы > :
QDataStream &operator<<(QDataStream &out, const PhotoFormat &obj); QDataStream &operator>>(QDataStream &in, PhotoFormat &obj);
Обращаю внимание - за пределами class <>;
А реализация примерно такая:
QDataStream & operator <<(QDataStream &out, const PhotoFormat &obj) QString name = obj.getFormatName(); QSizeF size = obj.getSize(); qreal dst1 = obj.getTopPateDistance(); qreal dst2 = obj.getNoseChinDistance(); out return out; > QDataStream & operator >>(QDataStream &in, PhotoFormat &obj) QString name; QSizeF size; qreal dst1; qreal dst2; in >> name >> size >> dst1 >> dst2; obj.setFormanName(name); obj.setSize(size); obj.setTopPateDistance(dst1); obj.setNoseChinDistance(dst2); return in; >
По сути, сериализация класса. Более подробная информация - на странице справки по классу QMetaType, и можно глянуть тут: http://www.crossplatform.ru/?q=node/281
Posted by Alexander Drozdov Oct 23, 2010 Tags: c++ programming qt
Тематика
Recent Posts
- Добавил ссылку на канал в Telegram
- Проброс последовательного порта по сети
- Снежная с севера
- Траверс Макарова-Макариха
- Семейная Фалаза или путешествие в зимнюю сказку
- Гора Сестра
- Традиционная Читинза
- mdns vs libmicrodns
- Временные метки на файловых системах
Tags (all)
Copyright © 2023 Alexander Drozdov - License - Powered by Hugo and Hugo-Octopress-based theme - Syntax Reference - Shortcodes
typedef и шаблоны
Шаблоны — один из самых пыльных уголков C++. Количество ситуаций, которые могут возникнуть при разных сочетаниях аргументов шаблона, аргументов функций, частичных специализациях и т.д. — чудовищно. Для прикладного разработчика это непочатый край возможностей, взять хотя бы тот же boost. Для разработчика компилятора — это непочатый край геморроя. Признанный лидер в искусстве разгребать эти Авгиевы конюшни это GCC, но и у него случаются заскоки.
Для программиста, пишущего на Qt, это всплывает при попытке использовать систему метаобъектов. Вот реальный пример:
namespace SCGenericSettings
// .
typedef qint64 setting_id_t;
// .
>
Q_DECLARE_METATYPE(SCGenericSettings::setting_id_t)
qint64 и setting_id_t — это разные типы или нет? Одна версия GCC считает, что одинаковые. Попробуешь скомпилировать приведённый выше код — получишь на строке с макросом ошибку переопределения ранее определённой специализации.
Прошёл годик, GCC обновился — и программа перестала компилироваться: Qt ругается на то, что тип setting_id_t не зарегистрирован. То есть теперь qint64 и setting_id_t стали разными типами. Ладно, добавили #ifdef:
#if (SC_GCC_VERSION < SC_GCC_VERSION_CHECK(4, 8, 4))
#define SC_GCC_CANT_DIFFERENTIATE_TYPEDEFED_IN_TEMPLATES
#endif
// .
#ifndef SC_GCC_CANT_DIFFERENTIATE_TYPEDEFED_IN_TEMPLATES
Q_DECLARE_METATYPE(SCGenericSettings::setting_id_t)
#endif
Прошёл ещё годик — и программа вдруг перестала работать: сигналы не доходят до слотов, ругаясь на то, что setting_id_t не зарегистрирован с помощью qRegisterMetaType(). Да как же он не зарегистрирован, если зарегистрирован?! Вот же:
const int metaType_setting_id_t = qRegisterMetaType();
Фишка в том, что теперь GCC не ругается при регистрации типа с помощью Q_DECLARE_METATYPE, но при этом умудряется раскрывать шаблон одинаково для обоих типов (qint64 и setting_id_t). То есть вместо того, чтобы зарегистрировать setting_id_t как метатип, вышеуказанный код просто присвоил переменной metaType_setting_id_t идентификатор метатипа для типа qint64. Метод QObject::connect() попытался найти метатип со строковым идентификатором «SCGenericSettings::setting_id_t» — и не нашёл. Проблема решилась добавлением «SCGenericSettings::setting_id_t» в качестве аргумента для qRegisterMetaType().
ПРАВИЛО: не использовать перегрузку функции qRegisterMetaType(), которая не имеет аргументов; вместо неё всегда нужно использовать ту версию, которая принимает текстовый идентификатор метатипа. (Обратите внимание, что он должен быть идентичен тому, что находится в скобках макроса Q_DECLARE_METATYPE).
По имеющимся исходникам имеет смысл пробежаться регулярным выражением