The user types in Qt on D-Bus

时间:2022-02-12 15:39:03
On Habré there were articles about D-Bus in Qt  (time)  and have a little mentioned the user  types (two) . Here implementation of transfer of the user types, the related features, alternate paths will be considered.
Article will have instruction appearance, with small impregnation of snippets, both for itself and for colleagues.
Note: it was studied under Qt 4.7 (Squeeze thanks for it...), therefore some actions can be useless.


Introduction


Standard types for which transfer it is not required excess gestures are in  dock . Opportunity to transfer  the QVariant (QDBusVariant) type  on D-Bus is also implemented. It allows to transfer those types which QVariant can accept in the designer? from QRect to QVariantList and QVariantMap (two-dimensional arrays do not work as it is necessary). There is temptation to transfer the types transforming them to QVariant. Personally I would recommend to refuse such way as the receiving end will not be able to distinguish some different types? all of them will be for it QVariant. In my opinion it can potentially lead to errors and will complicate support.

We prepare the types


For a start we will describe types which will be used in applications.
The first type will be Money
[Money]
struct Money
{
    int summ;
    QString type;

    Money()
        : summ(0)
        , type()
    {}
};

To start it it is necessary to declare in type system:
Q_DECLARE_METATYPE(Money)
Before transfer of type, it is necessary to cause functions for registration of type 
qRegisterMetaType<Money>("Money"); qDBusRegisterMetaType<Money>();
On D-Bus it is necessary for type for possibility of its transfer will add methods of its analysis and collecting on  standard types  (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;
}

That it was not so boring, we will add some more types. Completely files with types look so:
[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;
}

I will note that for use of arrays QList can use and they do not require marshaling and unmarshalling if for variables already there are conversions.

The user types in Qt on D-Bus

We start building


Let's assume to eat two Qt of application which need to communicate on D-Bus. One application will be registrated as service, and the second to interact with this service.

I lazy and me laziness to create separate QDBus the adapter. Therefore, for this purpose what to separate internal methods and the D-Bus interface, I will note the interface methods Q_SCRIPTABLE macro.
[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();
};

The Q_NOREPLY tag notes that D-Bus should not wait for the answer from method.
For registration of service with methods of noted Q_SCRIPTABLE such code is used:
[Service registration]
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"));
}

Completely cpp the file looks so:
[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"));
}

This class can successfully work on D-Bus? at using usual constructions.
For method call of its interface QDBusConnection can use:: send:
[Method D-Bus call without answer]
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());
}

The qVariantFromValue method by means of black magic, void of pointers and that we have registered type, will transform it to QVariant. Back it can be received through QVariant method template:: value or through qvariant_cast.

If the answer of method is necessary, it is possible to use other QDBusConnection methods? for synchronous call and for asynchronous callWithCallback, asyncCall.
[Synchronous call of D-Bus of method with waiting of the answer]
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();

[Asynchronous call of D-Bus of method]
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());
}

It is also possible to use class QDBusAbstractInterface methods in which QDBusMessage does not participate.
By the way, for sending signals there is no need to register the interface, they can be sent by the same send method:
[Sending signal]
QDBusMessage msg = QDBusMessage::createSignal(dbus::servicePath(), dbus::serviceName(), "someSignal");
msg << signalArgument;
QDBusConnection::sessionBus().send(msg);

Let's return for example. The class which the class Student interacts with the interface is given below.
[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;
}

It is possible to download example  from here .

The user types in Qt on D-Bus

If everything goes not so smoothly


When developing I had had problem: at the appeal to D-Bus to the program interface with the user types the program fell. It was solved everything adding of XML description of the interface in class by means of Q_CLASSINFO macro. For example it is higher looks as follows:
[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);
?

The type parameter at arguments is their signature, it is described according to  the D-Bus specification . If you have marshaling of type, it is possible to learn its signature using undocumented opportunities of QDBusArgument, namely its currentSignature method ().
[Receiving signature of type]
QDBusArgument arg;
arg<<Parcel();
qDebug()<<"Parcel signature: "<<arg.currentSignature();


Testing of the interface with the user types

Testing of signals

For testing of signals it is possible to use qdbusviewer? it can will connect to signal and to show that for structure he sends. Also for this purpose dbus-monitor can approach? after the indication of the address it will show all outgoing messages of the interface.

Testing of methods

qdbusviewer does not call methods with the user types. For these purposes it is possible to use d-feet. In spite of the fact that for it it is difficult to find distinct documentation, it is able to call methods with types of any complexity. During the work with it it is necessary to consider some features:
[Work with d-feet]
Variables are listed through comma.

The main types (in brackets designation in signature):
int(i)? number (example: 42);
bool(b)? 1 or 0;
double(d)? number with point (example: 3.1415);
string(s)? line so-called (example:? string?);
Structures are bracketed? (? and?)?, variables go through comma, the comma should be put even when in structure one element.
Arrays? square brackets? [? and?]?, variables through comma.

Did not study the Variant and Dict types as there was no need.


Thanks for attention.

The used materials:
QtDbus? the darkness covered with secret. Part 1 Part 2
Qt docs
D-Bus Specification
KDE D-Bus Tutorial  generally and  CustomTypes  in particular