Using DBus within CASA

Introduction

DBus is a single-host messaging system that facilitates the implementation of systems based upon cooperating processes. DBus is used by many applications, and as a result, many language bindings to DBus have been developed. These allow DBus services to be developed which can be used by any other applications connected to the bus. The client and server applications can be developed in any of the languages with a DBus language binding.

DBus supports multiple buses for each user. Upon login under Linux, the user typically gets two busses by default, a "session" bus and a "system" bus. The "system" bus is typical reserved for communications between the operating system services, e.g. between device drivers. CASA starts a private bus for use by CASA processes. Processes can connect to this bus, query what services are available, and connect to a specific service. This document describes the CASA tools which make implementing, launching, and using DBus services easier.

Implementing a Qt Service

Qt provides the nicest DBus binding of all compiled language bindings. CASA has implemented a very thin layer to make the use of Qt's binding a little easier and more consistent. To explore the use of this layer, we will develop a very simple Qt application and make its member functions addressable from DBus.

Application
The application is simply a Qt entry widget:

ddbus mainwindow.jpg

We will allow users to get and put the contents of the edit box.

The convention for creating a DBus binding to a Qt application is to create an "adaptor class" which will act as the gateway to connect to DBus. This will provide a layer of independence from the classes used to implement your application. This enables binding to another messaging system in the future, and it does not mix DBus details with the implementation classes, leaving a clean interface for using the classes directly within C++.

Our implementation class is very simple:
    class EditlineGui : public QMainWindow {
        Q_OBJECT
        public:
            EditlineGui( const std::string &dbus_name="", QWidget *parent = 0 );
            ~EditlineGui( );

            void set( const QString &txt ) { le->setText(txt); }
            QString get( ) { return le->text( ); }

        private:
            QLineEdit *le;
            EditlineGuiAdaptor *adaptor;
    };

Adaptor
The DBus adaptor is the adaptor private member of the implementation class. The DBus adaptor for this class is equally simple:
    class EditlineGuiAdaptor: public QDBusAbstractAdaptor, public QtDBusApp {
        Q_OBJECT
        Q_CLASSINFO("D-Bus Interface", "edu.nrao.casa.editlinegui")
        public:
            QString dbusName( ) const { return "editlinegui"; }
            bool connectToDBus( const QString &dbus_name="" )
                        { return QtDBusApp::connectToDBus( parent(), dbus_name ); }

            EditlineGuiAdaptor( EditlineGui *elg ) : QDBusAbstractAdaptor(new QObject()), editline_(elg) { }
            ~EditlineGuiAdaptor( ) { }

        public slots:
            void set( const QString &txt ) { editline_->set(txt); }
            QString get( ) { return editline_->get( ); }

        private:
            EditlineGui *editline_;
    };
All of the public slots of our adaptor class are exported as methods available from DBus. By deriving our adaptor class from QtDBusApp, we get access to the thin CASA layer which provides both uniformity and ease of connecting to DBus. QtDBusApp is an abstract base class. It requires that you provide two member functions:

  • QString dbusName( ) const
  • bool connectToDBus( const QString &dbus_name="" )

dbusName( ) just provides the default basename for our object in DBus land. A standard implementation of connectToDBus( ) is provided by the QtDBusApp base class. This implementation should be suitable for most purposes, and it in this case, we use it. The connection to DBus for our object is created in the constructor of EditlineGui:
    inline EditlineGui::EditlineGui(  const std::string &dbus_name, QWidget *parent ) :
                           QMainWindow(parent),
                           le(new QLineEdit("*some*text*",this)),
                           adaptor(new EditlineGuiAdaptor(this)) {
        le->setMinimumWidth(200);
        setCentralWidget(le);
        setFixedHeight(30);

        adaptor->connectToDBus( dbus_name.c_str( ) );
    }
Our object will be known as "/casa/editlinegui" with an interface of "edu.nrao.casa.editlinegui". This setup is done within the connectToDBus( ) function. In practice, it is a good idea to provide a parameter which allows the user to specify the DBus name, especially if there could be more then one such object connected to the bus. As we will see below, the launch templated function automatically generates a unique name by default.

We also need a process which will provide access to our object. This is typically a regular Qt process:
    int main ( int argc, char *argv[ ] ) {
        std::string dbus_name;
        std::string launch_flag("--dbusname");
        for (int x=0; x < argc; ++x ) {
            if ( launch_flag == argv[x] && (x + 1 < argc) )
                dbus_name = argv[++x];
        }

        QApplication app( argc, argv );
        casa::EditlineGui elgui(dbus_name);
        elgui.show( );
        return app.exec( );
    }

The launch( ) function (see below) specifies the name which we should use to connect to DBus with --dbusname so we look for this flag in the argument list. You can look at the complete source in dDBus.qo.h and dDBus.cc.

Using a Service from Python

Assuming we have compiled dDBus.cc into a binary and launched it from a terminal, it will be available on the "session" bus. Every implementation of python provides a DBus binding, and it is reasonably straightforward to access our object. The python module for DBus is called dbus:
    -bash-3.2$ python
    Python 2.5.2 (r252:60911, Apr 15 2008, 10:32:52) 
    [GCC 4.2.1 (Apple Inc. build 5553)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import dbus
CASA's objects appear on the "session" bus (although from within casapy the "session" bus is actually a private CASA bus):
    >>> bus = dbus.SessionBus( )
We can obtain a connection to our object with:
    >>> proxy = bus.get_object('edu.nrao.casa.editlinegui','/casa/editlinegui')
and exercise our get( ) and set( ) functions:
    >>> proxy.get( )
    dbus.String(u'*some*text*')
    >>> proxy.set('Hello world!')
    >>> proxy.get( )
    dbus.String(u'Hello world!')
The DBus module also provides other functionality e.g. listing what is currently available on the bus:
    >>> bus.list_names()
    dbus.Array([dbus.UTF8String('org.freedesktop.DBus'), dbus.UTF8String(':1.194'), dbus.UTF8String(':1.196'), dbus.UTF8String('edu.nrao.casa.editlinegui')], signature=dbus.Signature('s'))
    >>> 
It is straight forward to wrap the interface to our object up within a python class so that the user is completely isolated from the interaction with python's dbus module. The viewertool.py script provides such a wrapper for the casaviewer. It also includes code for pre-launching the viewer, connecting to it later, retrieving CASA configuration information, and handling errors.

Using a Service from C++

When the primary aim is to provide scriptability from python, there is no need to provide for connections to DBus objects from C++. What we have done up to this point is sufficient for this task. However, it is occasionally useful to be able to connect to DBus objects from C++. To accomplish this, we use dbus-c++. dbus-c++ provides a very thin layer on top of the standard DBus libraries, which are implemented in C. This minimizes external dependencies which exist with other bindings, e.g. the Qt library for the Qt binding or gnome for the glib binding.

dbus-c++ provides an XML to C++ generator which creates the boiler plate for connecting to a DBus object. The XML for our object looks like:
    <?xml version="1.0" ?>
    <node name="/casa/editlinegui">
      <interface name="edu.nrao.casa.editlinegui">
        <method name="get">
          <arg direction="out" type="s" />
        </method>
        <method name="set">
          <arg direction="in" type="s" name="txt" />
        </method>
      </interface>
    </node>
Running this XML (EditlineGuiProxy.dbusproxy.xml) through the dbus-c++ generator (dbusxx-xml2cpp):
    -bash-3.2$ dbusxx-xml2cpp EditlineGuiProxy.dbusproxy.xml --proxy=EditlineGuiProxy.dbusproxy.h
    generating code for interface edu.nrao.casa.editlinegui...
    -bash-3.2$ 
creates a proxy header file (EditlineGuiProxy.dbusproxy.h) for our use. The arguments and return types are implemented using the C++ standard template library (STL). There are standard rules in the CASA build system to automatically generate the *.dbusproxy.h files from *.dbusproxy.xml files.

We can use the class defined in this generated header file to easily create the class which will be used by C++ users who wish to connect to our new DBus service:
    class EditlineGuiProxy :
        private edu::nrao::casa::editlinegui_proxy,
        public DBus::IntrospectableProxy,
        public DBus::ObjectProxy {

    public:

        static const char **execArgs( ) {
            static const char *args[] = { "dDBus", (char*) 0 };
            return args;
        }
        static std::string dbusName( ) { return "editlinegui"; }

        EditlineGuiProxy( const std::string &name=dbusName( ) ) : 
                    DBus::ObjectProxy( DBusSession::instance().connection( ),
                                dbus::object(name).c_str(), 
                                dbus::path(name).c_str() ) { }

        std::string get( ) { return edu::nrao::casa::editlinegui_proxy::get( ); }
        void set( const std::string &txt ) { edu::nrao::casa::editlinegui_proxy::set( txt ); }
    };
Creating our own class and inheriting from the generated class allows us to specify any default values for arguments and perform any necessary conversion of parameters or results. The two static member functions:

  • std::string dbusName( )
  • static const char **execArgs( )

allow the launch( ) templated function to launch a new server for the editlinegui DBus service and connect to the object it provide via the session bus. Here is an example of how this proxy class can be used with launch( ):

    int main ( int argc, char *argv[ ] ) {
        char buf[2048];
        struct timeval tv = {0,0};
        EditlineGuiProxy *el = casa::dbus::launch<EditlineGuiProxy>( );
        if ( el == 0 ) { exit(1); }
        std::string v = el->get( );
        for ( int i=0; i < 10; ++i ) {
            gettimeofday( &tv, 0 );
            strftime( buf, 2048, "%F %T", localtime(&tv.tv_sec) );
            el->set( buf );
            sleep(1);
        }
        el->set( v );
    }
This example (see use_EditlineGuiProxy.cc) launches the dDBus process (from dDBus.cc) which must be in your $PATH. The call to launch( ) will either succeed by returning a pointer to a proxy connected to our EditlineGui or it will fail by returning zero. If it succeeds, the program will spend ten seconds updating the current time in our edit box. After ten seconds, it restores the edit window to its original state.

By convention, EditlineGui should include a done( ) function in addition to the get( ) and set( ) member functions so that those using the service could dismiss the process (in this case dDBus) when it is no longer needed. It is usually trivial to implement such a function, i.e. clean up and exit.
Topic revision: r11 - 2010-04-23, DarrellSchiebel
This site is powered by FoswikiCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding NRAO Public Wiki? Send feedback