Embedded Linux Qt DBus with Custom Types

Embedded Linux Qt DBus with Custom Types


This writeup shows how to use Qt with Dbus to send custom types between processes using Dbus as the IPC protocol. The custom type being sent is a Google Protocol Buffer ( http://code.google.com/p/protobuf/ ). Note that typically you would use the Protocol Buffer serializeToString() and parseFromString(), or similar built-in methods, to create a binary blob that would be sent rather than an instance of the Protocol Buffer class itself. However, this example sends an instance of the class to show how to send custom types.

Download and build the protocol buffer library and compiler from Google from this link --> http://code.google.com/p/protobuf/. There is a README.txt file with clear instructions on how to do this.

First, make a project called protoReceiver in QtCreator. Define a file named data.proto that will include the name of class to be generated by the protocol buffer class as well as the properties that class contains and can serialize. Mine looked like:

message data {

required int32 name = 1;

required int32 id = 2;

optional int32 email = 3;

}

Now in a terminal, run the following command to generate the C++ class from the .proto file:

>> protoc --cpp_out=. data.proto

Add the data.pb.h and data.pb.cc files to your project by modifying the .pro file like so:

SOURCES += main.cpp \

data.pb.cc \

dbusreadwrite.cpp

HEADERS += \

data.pb.h \

data.proto \

dbusreadwrite.h \

Next, create a Qt C++ class (heredbusreadwrite.*) in QtCreator that models the methods you would like to be available over DBus. The methods should be defined as Public Slots.

Mine were:

1.) QString read();

2.) QString write();

3.) QString SendMessage(const QString &cmd);

Now create an XML stub to define the DBus interface from this Qt Class by running the Qt provided tool qdbus2cml:

>> qdbus2xml -Mdbusreadwrite.h -o com.data.proj.com.xml

Now create an adaptor class from the XML file by running the Qt Provided tool qdbusxml2cpp:

>> qdbusxml2cpp -c ifadapter -a ifadapter.h:ifadaptor.cpp com.data.proj.com.xml

Add the generated ifadapter.h and ifadapter.cpp files to your .pro file. Instantiate the adapter class and create a connection to your system's DBus session bus in the constructor for your main class ( here dbusreadwrite.cpp ):

new ifadaptor(this); // Cleans itself up

QDBusConnection dbus = QDBusConnection::sessionBus(); // Use session bus

dbus.registerObject("/dbusReadWrite",this); // Register object on the bus

dbus.registerService("com.data.proj.command.readwrite"); // Expose interface to others

Now in your project's main.cpp, instantiate an instance of your main class:

// Create a dbus instance

new dbusReadWrite();

The last thing to do is register our custom Protocol Buffer Qt C++ class type with the Qt Meta Type system so it knows how to handle our custom class.

To the header file for your Protocol Buffer, add the following:

1.) Near the very top of the file:

// PROTOBUF-MODIFICATION-DBUS

#include <QMetaType>

#include <QDBusMetaType>

2.) In the method declaration:

// PROTOBUF-MODIFICATION-DBUS

friend QDBusArgument &operator<<(QDBusArgument &argument, const Data &dataToWrite);

friend const QDBusArgument &operator>>(const QDBusArgument &argument, Data &dataToWrite);

3.) Near the end of the file:

// PROTOBUF-MODIFICATION-DBUS

Q_DECLARE_METATYPE(Data)

To the implementation file for your Protocol Buffer, add the following:

1.) Near the very top of the file:

// PROTOBUF-MODIFICATION-DBUS

#include <QDebug>

#include <QMetaType>

#include <QDBusMetaType>

2.) Also, near the top of the implementation file implement the >> and << operators:

// PROTOBUF-MODIFICATION-DBUS

// Marshall the Data data into a D-Bus argument

QDBusArgument &operator<<(QDBusArgument &argument, const Data &dataToWrite)

{

argument.beginStructure();

// Break out the various properties of dataToWrite protocol buffer

int name = dataToWrite.name();

int id = dataToWrite.id();

int email = dataToWrite.email();

argument << name;

argument << id;

argument << email;

argument.endStructure();

return argument;

}

// PROTOBUF-MODIFICATION-DBUS

// Retrieve the Data data from the D-Bus argument

const QDBusArgument &operator>>(const QDBusArgument &argument, Data &dataToWrite)

{

int name, id, email;

argument.beginStructure();

argument >> name;

argument >> id;

argument >> email;

argument.endStructure();

dataToWrite.set_name(name);

dataToWrite.set_id(id);

dataToWrite.set_email(email);

return argument;


}

3.) Now modify the Protocol Buffer constructor to look like:

Data::Data()

: ::google::protobuf::Message() {

// PROTOBUF-MODIFICATION-DBUS

qRegisterMetaType<Data>("Data");

qDBusRegisterMetaType<Data>();

SharedCtor();

}

That concludes the protoReceiver program.

Now, make a project called protoSender in QtCreator and create a new Qt C++ class named dbussender. Add the .proto and modified protocol buffer header and implementation files to protoSender by copying them to the project directory and modifying the .pro file. Mine looked like:

SOURCES += main.cpp \

data.pb.cc \

dbussender.cpp

HEADERS += \

data.pb.h \

data.proto \

dbussender.h

Modify the dbussender class header file to look like below. This class implements our methods we want to call over DBus.

#ifndef DBUSSENDER_H

#define DBUSSENDER_H

#include <QtCore/QObject>

#include <QtCore/QByteArray>

#include <QtCore/QList>

#include <QtCore/QMap>

#include <QtCore/QString>

#include <QtCore/QStringList>

#include <QtCore/QVariant>

#include <QtDBus/QtDBus>

#include "data.pb.h"

/*

* Proxy class for interface com.data.proj.protobuf

*/

class dbussender: public QDBusAbstractInterface

{

Q_OBJECT

public:

static inline const char *staticInterfaceName() {

return "com.data.proj.command";

}

public:

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

~dbussender();

public Q_SLOTS: // METHODS

// In protoReceiver it looks like:

// Basic test method for a string

// QString dbusReadWriteNvRam::SendMessage(const QString &cmd)

inline QDBusPendingReply<QString> SendMessage(const QString &cmd)

{

QList<QVariant> argumentList;

argumentList << QVariant::fromValue(cmd);

return asyncCallWithArgumentList(QLatin1String("SendMessage"), argumentList);

}

// In protoReceiver it looks like:

// read()

// bool dbusReadWrite::read()

inline QDBusPendingReply<QString> read()

{

//QList<QVariant> argumentList;

//argumentList << QVariant::fromValue(cmd);

return asyncCall(QLatin1String("read"));//, argumentList);

}

// In protoReceiver it looks like:

// write()

// bool dbusReadWrite::write()

inline QDBusPendingReply<QString> write(Data dataToWrite)

{

qDebug() << "Sending " << dataToWrite.name();

qDebug() << "Sending " << dataToWrite.id();

qDebug() << "Sending " << dataToWrite.email();

QList<QVariant> argumentList;

argumentList << QVariant::fromValue<Data>(dataToWrite);

return asyncCallWithArgumentList(QLatin1String("write"), argumentList);

}

Q_SIGNALS: // SIGNALS

};

namespace com {

namespace data {

namespace proj {

typedef ::dbussender command;

}

}

}

#endif

Now modify the implementation file to look like:

#include "dbussender.h"

/*

* Implementation of interface class dbussender

*/

dbussender::dbussender(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)

{

}

dbussender::~dbussender()

{

}

Now modify the main.cpp file to look like below. Here we create our Protocol Buffer and

set it's properties. Then we call the write function over DBus while passing it our instance

of the Protocol Buffer.

#include <QtCore/QCoreApplication>

#include <iostream>

#include <QtDBus/QtDBus>

#include "dbussender.h"

#include "data.pb.h"

int main(int argc, char *argv[])

{

QCoreApplication a(argc, argv);

dbussender* client = new dbussender("com.data.proj.command.readwrite", "/dbusReadWrite", QDBusConnection::sessionBus(), 0);

// Create a protocol buffer class and provide its properties with values

Data dataToWrite;

dataToWrite.set_name(2);

dataToWrite.set_id(3);

dataToWrite.set_email(4);

QString command3 = "Contacting Protobuf Receiver and calling WRITE...";

QString response3 = client->write(dataToWrite);

std::cout << "Command: " << command3.toStdString() << std::endl;

std::cout << "Response: " << response3.toStdString() << std::endl;

return 0; // a.exec();

}

That's it for the protoSender program. You should run the protoReceiver program in it's own terminal and then run the protoSender in another terminal. You will see that your protocol buffer instance is being sent over DBus between the 2 processes:

me@ubuntu:~/Desktop/dbus-example/protoReceiver-build$ ./protoReceiver

WRITE COMMAND CALLED

"Received: NAME: 2 ID: 3 EMAIL: 4"

me@ubuntu:~/Desktop/dbus-example/protoSender-build$ ./protoSender

Sending 2

Sending 3

Sending 4

Command: Contacting Protobuf Receiver and calling WRITE...

Response: NAME: 2 ID: 3 EMAIL: 4




ClassyBits 2016