For the past few days I've been trying to get more complex data sent via D-Bus. As one would say here in Ireland:
Je'zus H. Christ
Playing with python was easy. There are lots of examples on communicating complex data structures. But with Qt... It's a bit more complex, because there are only a set of simple examples.
To keep things simple, I chose the car example:
- The car/ is the server. It receives the request from the client and sends a reply.
- The controller/ is the client. It "calls" a method on the server, gets the results and processes them
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/com/trollech/examples/car">
<interface name="com.trolltech.Examples.CarInterface">
<method name="accelerate"></method>
<method name="decelerate"></method>
<method name="turnLeft"></method>
<method name="turnRight"></method>
<method name="structer">
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0"
value="MyStruct"></annotation>
<arg type="(sxs)" direction="out"></arg>
<arg type="x" direction="out"></arg>
</method>
<signal name="crashed"></signal>
</interface>
</node>
Notice the extra method called structer
with two outputs:
- a structure
MyStruct
- a
qlonglong
For the purpose of this test, MyStruct
is a simple structure:
class MyStruct {
public:
QString element1;
qlonglong element2;
QString element3;
};
The client calls structer
. The server updates the qlonglong
and the MyStruct
object and sends them back to the client to be used.
The D-Bus translation
First, note the "arg0" annotation, required for the struct type:
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="MyStruct"></annotation>
This actually helps the qdbusxml2cpp to typ-ify the generated code.
On the client (controller) part, we use the following command:
qdbusxml2cpp -c CarInterface
-p car_interface_p.h:car_interface.cpp
-i mystruct.h
car.xml
On the server (car) part, use the command:
qdbusxml2cpp -i mystruct.h
-c CarAdaptor
-a car_adaptor_p.h:car_adaptor.cpp
car.xml
for the same car.xml file.
The XML resulted from the introspection description for structer
is translated by the Qt into:
public Q_SLOTS:
MyStruct structer(qlonglong &out1);
aka first "out" argument is passed as the return argument for the function. The other arguments are passes as function arguments. It's good to know this, because you can implement your own method without looking at the *_p.h files for guidance.
... but not yet.
The Server Side
First issue is that qdbusxml2cpp is not smart enough to implement custom marshaling. It offers instead a guideline for us:
MyStruct CarAdaptor::structer(qlonglong &out1)
{
// handle method call com.trolltech.Examples.CarInterface.structer
//return static_cast<yourobjecttype *>(parent())->structer(out1);
}
once we change the commented line to something like:
MyStruct s = static_cast<car *>(parent())->structer(out1);
qDebug() < < "Parameters:" << out1;
qDebug() << " " << s.element1 << s.element2 << s.element3;
return s;
we're good to go. The line
MyStruct s = static_cast<car *>(parent())->structer(out1);
implies that the parent()
object is a pointer to Car
, which, in turn contains a structer()
implementation. You can change the call into whatever code capable of updating the out1
parameter and returning a MyStruct
struct.
For the purpose of this exercise, we implement a structer()
method in our car.cpp like so:
MyStruct Car::structer(qlonglong &out1) {
printf("Calling structer (car.cpp)n");
s.element1 = "one";
s.element2 = 2;
s.element3 = "three";
out1 = 12345;
return s;
}
and include the car.h in.
The downfall of this server approach is that it requires manual adjustment of a generated file. So, once you have the .cpp and the _p.h files available, save them to your repository. Have them regenerated only if needed. Note that Trolltech's Qt tarball contains already generated files, instead of scripting :)
The Client Side
Here the fun begins! First, open the .ui file and add a new button to call the structer()
. In controller.h
complete the on_XXXX_clicked()
with the new button ID (let's call it on_structer clicked()
)
The implementation follows the D-Bus guidelines from the other buttons:
void Controller::on_structer_clicked()
{
qlonglong r2;
QDBusReply<mystruct> r1 = car->structer(r2);
printf(">>>>>> %dn", r2);
}
The result is that every time you press the button, it's going to call the server (car) and get the MyStruct
structure from the server.
... but not yet! :)
The Glue
D-Bus is a relatively simple mechanism and supports a predefined list of types. Composite types are sent as structures and have to have custom marshaling implemented. This is done via <<
/>>
C++ operators.
Once you have the structure defined, the Qt way is a three-step process.
Declare the Metatype
Place the line
Q_CREATE_METATYPE(MyStruct)
somewhere outside body/class/namespace blocks. I've put it in mystruct.h
after the struct declaration.
Declare/Implement the Marshaller
This actually means you need to declare/implement the <</>>
operators. For convenience, I've declared them in mystruct.h as well and implemented them in mystruct.cpp like this:
mystruct.h
QDBusArgument &operator < < (QDBusArgument &arg,
const MyStruct &mystruct);
const QDBusArgument &operator >> (const QDBusArgument &arg,
MyStruct &mystruct);
mystruct.cpp
// Marshall the MyStructure data into a D-BUS argument
QDBusArgument &operator < < (QDBusArgument &arg,
const MyStruct &mystruct)
{
arg.beginStructure();
arg << mystruct.element1 << mystruct.element2 << mystruct.element3;
arg.endStructure();
return arg;
}
// Retrieve the MyStructure data from the D-BUS argument
const QDBusArgument &operator >> (const QDBusArgument &arg,
MyStruct &mystruct)
{
arg.beginStructure();
arg >> mystruct.element1 >> mystruct.element2 >> mystruct.element3;
arg.endStructure();
return arg;
}
Register the Type with D-Bus
Once you have done the above steps, everything is almost ready. Now you only need to add the type to the list of custom marshallers with qDBusRegisterMetaType
.
In mystruct.h, add:
inline void registerCommTypes() {
qDBusRegisterMetaType<mystruct>();
}
and have it called in both the client and server, somewhere before the actual D-Bus methods are called (e.g. in main() or in some constructor).
Look at the example for details.
Good luck at hacking your own D-Bus stuff!
Member discussion: