Search code examples
c++qtqt5dbusqdbusxml2cpp

How to generate synchronous interface class with qdbusxml2cpp?


Problem summary: qdbusxml2cpp generates a QDBusAbstractInterface sub-class whose methods fetch D-Bus replies asynchronously, but I want it to be synchronous (i.e. it should block until the reply is received).

XML input:

<?xml version="1.0"?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="some.interface.Foo">
    <method name="GetValues">
    <arg name="values" type="a(oa{sv})" direction="out"/>
        <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList&lt;SomeStruct&gt;" />
    </method>
</interface>
</node>

With this command a header and .cpp file (not shown) is generated:

qdbusxml2cpp-qt5 -c InterfaceFoo -p interface_foo  foo.xml

The generated header:

class InterfaceFoo: public QDBusAbstractInterface
{
    Q_OBJECT
public:
    static inline const char *staticInterfaceName()
    { return "some.interface.Foo"; }

public:
    InterfaceFoo(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0);

    ~InterfaceFoo();

public Q_SLOTS: // METHODS
    inline QDBusPendingReply<QList<SomeStruct> > GetValues()
    {
        QList<QVariant> argumentList;
        // NOTICE THIS LINE.
        return asyncCallWithArgumentList(QStringLiteral("GetValues"), argumentList);
    }

Q_SIGNALS: // SIGNALS
};

namespace some {
namespace interface {
    typedef ::InterfaceFoo Foo;
}
}
#endif

As you can see the method generated asyncCallWithArgumentList() is asynchronous: it expects to be connect()ed to a slot that is triggered when the D-Bus reply arrives.

Instead I want to be able to do:

 some::interface::Foo *interface =  new some::interface::Foo("some::interface", "/Foo", QDBusConnection::systemBus(), this);
 // THIS SHOULD block until a reply is received or it times out.
 QList<SomeStruct> data = interface->GetValues();

Solution

  • You could use value() on the return value to block, with GetValues() as-is:

    auto reply = interface->GetValues();
    auto data = reply.value<QList<QVariant>>();
    

    Alas, the generated interface is meant to be generated once and then become a part of your sources. You should modify it to use a blocking call, and add a conversion from the variant to a concrete type:

    inline QDBusPendingReply<QList<SomeStruct>> GetValues()
    {
        QList<QVariant> argumentList;
        auto msg = callWithArgumentList(QDBus::Block, QStringLiteral("GetValues"), argumentList);
        Q_ASSERT(msg.type() == QDBusMessage::ReplyMessage);
        QList<SomeStruct> result;
        for (auto const & arg : msg.arguments())
          result << arg.value<SomeStruct>();
        return result;
    }
    

    Finally, you might wish to reconsider making it synchronous: real world is asynchronous, so writing the code as if it wasn't is often counterproductive. If your code runs in the main thread and there's a gui, you'll be giving bad user experience. If you move your code to a worker thread, you're wasting an entire thread just to support a synchronous coding style. Even if you're writing what amounts to a non-interactive batch-style utility/service, you're potentially increasing the latency by e.g. not issuing calls in parallel.