Avoiding a trap with marshalling to array or map with QDBusArgument

If you are going to send custom data types over D-Bus and for this write your own marshalling and demarshalling functions (as explained in the QDBusArgument API docs),
QDBusArgument& operator<<(QDBusArgument& argument, const MyStructure& mystruct)
const QDBusArgument& operator>>(const QDBusArgument& argument, MyStructure& mystruct)
there is a nice (not documented) trap you can fall into. I did, bah…

QtDBus internally uses the first of these both methods to initially calculate the signature to which your type is marshalled. For this it passes an instance of the class, constructed with the default constructor, in our example MyStructure().

So that is why this piece of code, where I marshal a custom class with two string properties into a map with a key of type string and a value of type string, does not work with QtDBus (will fail similar for creating an array):

QDBusArgument& operator<<( QDBusArgument& argument, const MyStructure& mystruct )
{
    static QString typeKey = QLatin1String("type");
    static QString dataKey = QLatin1String("data");
    argument.beginMap( QVariant::String, QVariant::String );
        argument.beginMapEntry();
            argument << typeKey << mystruct.type();
        argument.endMapEntry();
        argument.beginMapEntry();
            argument << dataKey << mystruct.data();
        argument.endMapEntry();
    argument.endMap();

    return argument;
}

you will get an error like this in the log:
QDBusMarshaller: type `MyStructure' produces invalid D-BUS signature `a{ss}ssss' (Did you forget to call beginStructure() ?)
Strange I thought, as this code should be kind of an unrolled version what e.g. the template code for QMap looks like:

template
inline QDBusArgument &operator<<(QDBusArgument &arg, const QMap &map)
{
    int kid = qMetaTypeId();
    int vid = qMetaTypeId();
    arg.beginMap(kid, vid);
    typename QMap::ConstIterator it = map.constBegin();
    typename QMap::ConstIterator end = map.constEnd();
    for ( ; it != end; ++it) {
        arg.beginMapEntry();
        arg << it.key() << it.value();
        arg.endMapEntry();
    }
    arg.endMap();
    return arg;
}

Well, it works for QMap, as with an empty QMap the for-loop will not be reached, so arg << it.key() << it.value(); will not be executed and thus not result in additional bogus info appended to the signature string.

So the solution for my code was to add an empty/invalid state to MyStructure and to set any instance to that if constructed with the default constructor, along with wrapping the map entry insertion with a check for isValid():

QDBusArgument& operator<<( QDBusArgument& argument, const MyStructure& mystruct )
{
    static QString typeKey = QLatin1String("type");
    static QString dataKey = QLatin1String("data");
    argument.beginMap( QVariant::String, QVariant::String );
    if( mystruct.isValid() )
    {
        argument.beginMapEntry();
            argument << typeKey << mystruct.type();
        argument.endMapEntry();
        argument.beginMapEntry();
            argument << dataKey << mystruct.data();
        argument.endMapEntry();
    }
    argument.endMap();

    return argument;
}

Yes, need to file a bug with Qt about this missing in the QDBusArgument docs, yet to be done.

Advertisements

3 thoughts on “Avoiding a trap with marshalling to array or map with QDBusArgument

    • In this case the target signature a{ss} is given by some D-Bus API already, so MyStruct has to be mapped to it, even while itself does not have a QMap nature.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s