[Из песочницы] Пользовательские типы в Qt по D-Bus

imageНа хабре были статьи о D-Bus в Qt (раз) и немного затронули пользовательские типы (два). Здесь будет рассмотрена реализация передачи пользовательских типов, связанные с ней особенности, обходные пути.Статья будет иметь вид памятки, с небольшим вкраплением сниппетов, и для себя и для коллег.Примечание: изучалось под Qt 4.7(Спасибо Squeeze за это...), поэтому некоторые действия могут оказаться бесполезными.Введение Стандартные типы, для передачи которых не требуется лишних телодвижений есть в доке. Также реализована возможность передавать по D-Bus тип QVariant (QDBusVariant). Это позволяет передавать те типы, которые QVariant может принимать в конструкторе — от QRect до QVariantList и QVariantMap (двумерные массивы не работают как положено). Есть соблазн передавать свои типы преобразовывая их в QVariant. Лично я рекомендовал бы отказаться от такого способа, поскольку принимающая сторона не сможет различить несколько различных типов — все они будут для неё QVariant. На мой взгляд это может потенциально привести к ошибкам и усложнит поддержку.Готовим свои типы Для начала опишем типы, которые будут использоваться в приложениях.Первый тип будет Money[Money] struct Money { int summ; QString type;

Money() : summ(0) , type() {} }; Для начала его надо задекларировать в системе типов:Q_DECLARE_METATYPE(Money)Перед началом передачи типа, необходимо вызвать функции для регистрации типаqRegisterMetaType<Money>("Money"); qDBusRegisterMetaType<Money>();Для возможности его передачи по 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 <QString> #include <QDateTime> #include <QMap> #include <QMetaType> #include <QtDBus>

//Имя и путь 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<QVariant> 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 <QMetaType> #include <QtDBus>

//Регистрация типов статической структурой static struct RegisterTypes { RegisterTypes() { qRegisterMetaType<Money>("Money"); qDBusRegisterMetaType<Money>();

qRegisterMetaType<Letter>("Letter"); qDBusRegisterMetaType<Letter>();

qRegisterMetaType<Stuff>("Stuff"); qDBusRegisterMetaType<Stuff>();

qRegisterMetaType<Parcel>("Parcel"); qDBusRegisterMetaType<Parcel>(); } } 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 и для них не требуется маршаллизация и демаршаллизация, если для переменных уже есть преобразования.27d88243d140d55b2bb22f2184a008e9.png

Начинаем строить Предположим есть два Qt приложения, которым нужно общаться по D-Bus. Одно приложение будет регистрироваться как сервис, а второе с этим сервисом взаимодействовать.Я ленивый и мне лень создавать отдельный QDBus адаптер. Поэтому, для того что бы разделять внутренние методы и интерфейс D-Bus, интерфейсные методы отмечу макросом Q_SCRIPTABLE.

[student.h] #include <QObject> #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()<<(QString("DBus register service successfully")); } Полностью cpp файл выглядит так:[student.cpp] #include "student.h" #include <QDBusConnection> #include <QDebug> #include <QDBusError>

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()<<(QString("DBus register service successfully")); } Этот класс может успешно работать по D-Bus’у используя привычные конструкции.Для вызова метода его интерфейса можно использовать QDBusConnection::send:[Вызов D-Bus метода без ответа] const QString studentMethod = "reciveParcel"; QDBusMessage sendParcel = QDBusMessage::createMethodCall(dbus::serviceName(), dbus::servicePath(), "", studentMethod);

QList<QVariant> arg; arg.append(qVariantFromValue(parentsParcel));

sendParcel.setArguments(arg);

if ( !QDBusConnection::sessionBus().send(sendParcel) ) { qDebug()<<QString("D-bus %1 calling error: %2").arg(studentMethod).arg(QDBusConnection::sessionBus().lastError().message()); } Метод 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<QVariant> arg; arg.append(qVariantFromValue(parentsParcel));

sendParcel.setArguments(arg);

int timeout = 25; //Максимальное время ожидания ответа, если не указывать - 25 секунд QDBusReply<int> reply = QDBusConnection::sessionBus().call(sendParcel, QDBus::Block, timeout); //QDBus::Block блокирует цикл событий(event loop) до получения ответа

if (!reply.isValid()) { qDebug()<<QString("D-bus %1 calling error: %2").arg(studentMethod).arg(QDBusConnection::sessionBus().lastError().message()); } int returnedValue = reply.value(); [Асинхронный вызов D-Bus метода] const QString studentMethod = "reciveParcel"; QDBusMessage sendParcel = QDBusMessage::createMethodCall(dbus::serviceName(), dbus::servicePath(), "", studentMethod);

QList<QVariant> 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) { qDebug()<<QString("D-bus %1 calling error: %2").arg(studentMethod).arg(QDBusConnection::sessionBus().lastError().message()); } Также можно воспользоваться методами класса QDBusAbstractInterface, в которых не участвует QDBusMessage.Кстати, для отправки сигналов нет необходимости регистрировать интерфейс, их можно отправлять тем же методом send:[Отправка сигнала] QDBusMessage msg = QDBusMessage::createSignal(dbus::servicePath(), dbus::serviceName(), "someSignal"); msg << signalArgument; QDBusConnection::sessionBus().send(msg); Вернёмся к примеру. Ниже представлен класс, который взаимодействует с интерфейсом класс Student.[parents.h] #include <QObject> #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 <QDBusConnection> #include <QDebug>

Parents::Parents(QObject *parent) : QObject(parent) { connectToDBusSignal(); }

Parents::~Parents() { }

void Parents::reciveLetter(const Letter letterFromStudent) { qDebug()<<"Letter recived: "; qDebug()<<"Letter text: "<<letterFromStudent.text; qDebug()<<"Letter date: "<<letterFromStudent.letterDate; sendHelpToChild(letterFromStudent); }

void Parents::connectToDBusSignal() { bool isConnected = QDBusConnection::sessionBus().connect( "", dbus::servicePath(), dbus::serviceName(), "needHelp", this, SLOT(reciveLetter(Letter))); if(!isConnected) qDebug()<<"Can't connect to needHelp signal"; else qDebug()<<"connect to needHelp signal";

}

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);

QList<QVariant> arg; arg.append(qVariantFromValue(parentsParcel));

sendParcel.setArguments(arg);

if ( !QDBusConnection::sessionBus().send( sendParcel) ) { qDebug()<<QString("D-bus %1 calling error: %2").arg(studentMethod).arg(QDBusConnection::sessionBus().lastError().message()); } }

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 { Stuff food; food<<"Russian donuts"; food<<"Meat dumplings"; return food; } Скачать пример можно отсюда.2249956291e04023433bd1e66e1f5f4c.png

Если всё идёт не настолько гладко При разработке у меня возникла проблема: при обращении к 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", "" "<interface name=\"org.student.interface\">\n" " <signal name=\"needHelp\">\n" " <arg name=\"reason\" type=\"((is)s((iii)(iiii)i))\" direction=\"out\"/>\n" " <annotation name=\"com.chameleon.QtDBus.QtTypeName.Out0\" value=\"Letter\"/>\n" " </signal>\n" " <method name=\"reciveParcel\">\n" " <arg name=\"parcelFromParents\" type=\"(av((is)s((iii)(iiii)i)))\" direction=\"in\"/>\n" " <annotation name=\"org.qtproject.QtDBus.QtTypeName.In0\" value=\"Parcel\"/>\n" " <annotation name=\"org.freedesktop.DBus.Method.NoReply\" value=\"true\"/>\n" " </method>\n" )

public: Student(QObject *parent = 0); … Параметр type у аргументов это их сигнатура, она описывается по спецификации D-Bus. Если у вас есть маршализация типа, можно узнать его сигнатуру используя недокументированные возможности QDBusArgument, а именно его метод currentSignature().[Получение сигнатуры типа] QDBusArgument arg; arg<<Parcel(); qDebug()<<"Parcel signature: "<<arg.currentSignature(); Тестирование интерфейса с пользовательскими типами Тестирование сигналов Для тестирования сигналов можно использовать qdbusviewer — он может приконнектится к сигналу и показать что за структуру он отправляет. Также для этого может подойти dbus-monitor — после указания адреса он будет показывать все исходящие сообщения интерфейса.Тестирование методов qdbusviewer не вызывает методы с пользовательскими типами. Для этих целей можно использовать d-feet. Несмотря на то, что для него трудно найти внятную документацию, он умеет вызывать методы с типами любой сложности. При работе с ним нужно учесть некоторые особенности:[Работа с d-feet] Переменные перечисляются через запятую.Основные типы(в скобках обозначение в сигнатуре):int(i) — число (пример: 42);bool(b) — 1 или 0;double(d) — число с точкой (пример: 3.1415);string(s) — строка в кавычках (пример: ”string”);Структуры берутся в скобки “(“ и “)”, переменные идут через запятую, запятую надо ставить даже когда в структуре один элемент.Массивы — квадратные скобки “[“ и ”]”, переменные через запятую.

Типы Variant и Dict не изучал, так как не было необходимости.

Спасибо за внимание.

Использованные материалы:QtDbus — тьма, покрытая тайною. Часть 1, Часть 2Qt docsD-Bus SpecificationKDE D-Bus Tutorial в общем и CustomTypes в частности

© Habrahabr.ru