Home · All Classes · Grouped Classes · Annotated · Functions

Qtopia IPC Layer

Introduction

Qtopia 4.2 introduces a new inter-process communication (IPC) API aimed to make it extremely easy to send and receive messages between applications. It is designed to be used in several ways, and features an intelligent data serialization infrastructure based on the Qt meta-type system. This enables the user to easily send messages across applications that can contain custom types. The user only has to implement serialization and deserialization functions and register the custom type with the API.

The Qtopia IPC API is based on the notion of channels and messages. Messages can be used by by an application to notify another application to take some action. Messages can have arguments associated with them. In essence, a message can be thought of as a remote method call. A message consists of a function identifier followed by a list of types in parantheses. There is no whitespace in the message name.

Channels are unique string based identifiers which are used to logically group a set of messages together. Effectively, a set of messages on a particular channel defines an interface. By convention all channels within Qtopia start with "QPE/".

The Qtopia IPC system uses the event-processing loop. If you create a message it will not be sent until the next the event loop is entered.

Qtopia IPC system consists of three major classes. The QtopiaIpcAdaptor is the preferred way to interface with the system. In addition, QtopiaChannel and QtopiaIpcEnvelope classes are provided to ease the transition between the Qtopia QCopChannel based systems and the new API.

QtopiaIpcAdaptor

Overview

Qtopia IPC API is exposed to the user through the QtopiaIpcAdaptor class. QtopiaIpcAdaptor can be used in two ways: direct access and an inheritance based approach.

To use QtopiaIpcAdaptor directly, we first construct a new object by providing a channel as so:

    QtopiaIpcAdaptor *adaptor = new QtopiaIpcAdaptor("QPE/CannonExample");

The newly created adaptor object can now be used to send messages to remote applications, or to receive messages. To send messages use the send method calls:

    // First we aim the cannon
    QtopiaIpcSendEnvelope envelope = adaptor->send(MESSAGE(aimCannon(int,int)));
    envelope << direction << elevation;

    // Alternatively if there are less than 3 arguments we can use send directly
    adaptor->send(MESSAGE(shootCannon(int)), cannonPower);

Another application is responsible for actually aiming and shooting the cannon. Let us suppose that the cannon application can reply with two types of messages, a hit or a miss. E.g. the remote application will send a missed() or hit() message. We can listen for such messages in the following manner:

    class CannonListener : public QObject
    {
        Q_OBJECT

    public slots:
        void missed();
        void hit();
    };

    ...

    CannonListener *listener = new CannonListener;
    QtopiaIpcAdaptor *adaptor = new QtopiaIpcAdaptor("QPE/CannonExample");
    QtopiaIpcAdaptor::connect(adaptor, SIGNAL(missed()), listener, SLOT(missed()));
    QtopiaIpcAdaptor::connect(adaptor, SIGNAL(hit()), listener, SLOT(hit()));

We can also use QtopiaIpcAdaptor::connect to an object's signals to be sent over a channel automatically. For instance, we can rewrite the above example like this:

    class CannonController : public QObject
    {
        Q_OBJECT

    public slots:
        void missed();
        void hit();

    signals:
        void aimCannon(int dir, int elev);
        void shootCannon(int power);
    };

    ...

    CannonController *control = new CannonController;
    QtopiaIpcAdaptor *adaptor = new QtopiaIpcAdaptor("QPE/CannonExample");

    // Hookup remote messages to our object
    QtopiaIpcAdaptor::connect(adaptor, SIGNAL(missed()), control, SLOT(missed()));
    QtopiaIpcAdaptor::connect(adaptor, SIGNAL(hit()), control, SLOT(hit()));

    // Send our signals to the remote object
    QtopiaIpcAdaptor::connect(control, SIGNAL(aimCannon(int,int)), adaptor, SLOT(aimCannon(int,int)));
    QtopiaIpcAdaptor::connect(control, SIGNAL(shootCannon(int)), adaptor, SLOT(shootCannon(int)));

We should now recognize that for non-trivial interactions the last example is a quite common use case. It is thus desirable to avoid using QtopiaIpcAdaptor::connect for each connection, which can be quite error prone. QtopiaIpcAdaptor provides an alternate interaction technique based on inheritance. Instead of creating another class and using QtopiaIpcAdaptor::connect, we inherit directly from QtopiaIpcAdaptor and use the publish or publishAll methods to automatically publish all signals and slots on the channel. E.g.:

        class CannonController : public QtopiaIpcAdaptor
        {
            Q_OBJECT

        public:
            CannonController(QObject *parent = 0);
            void shoot();

        public slots:
            void missed();
            void hit();

        signals:
            void aimCannon(int dir, int elev);
            void shootCannon(int power);
        };

        CannonController::CannonController(QObject *parent)
            : QtopiaIpcAdaptor("QPE/CannonExample", parent)
        {
            publishAll(QtopiaIpcAdaptor::SignalsAndSlots);
        }

        void CannonController::missed()
        {
            qDebug() << "Cannon missed!";
        }

        void CannonController::hit()
        {
            qDebug() << "Cannon HIT!";
        }

        void CannonController::shoot()
        {
            emit aimCannon(0, 45);
            emit shootCannon(100);
        }

    ...

        CannonController *control = new CannonController;
        control->shoot();

Serialization System

Enumerations

To make the example complete, we show how to extend our example with custom data types. First let us suppose that we'd like to use a custom ammunition type. We define a new enumeration named appropriately AmmunitionType. In order to exchange these enumerations, we must first register it with the meta-type system. This is accomplished by using the Q_DECLARE_USER_METATYPE_ENUM macro. This macro must be used in the header file where the enumeration is defined. Additionally we must use the Q_IMPLEMENT_USER_METATYPE_ENUM macro to implement the necessary serialization and deserialization functions.

        // Header
        class CannonController : public QtopiaIpcAdaptor
        {
            Q_OBJECT

        public:
            enum AmmunitionType { Explosive = 0, ArmorPiercing, Napalm }

            CannonController(QObject *parent = 0);
            void shoot();

        public slots:
            void missed();
            void hit();

        signals:
            void aimCannon(int dir, int elev);
            void shootCannon(int power);
            void setAmmunitionType(AmmunitionType type);
        };

        Q_DECLARE_USER_METATYPE_ENUM(CannonController::AmmunitionType)

    ...

        // Implementation:

        Q_IMPLEMENT_USER_METATYPE_ENUM(CannonController::AmmunitionType)

Custom Classes

In order to shoot our cannon we now need to send three messages to the remote application. For such a complex and mission critical system we should really send only one message for efficiency purposes. We define a new class that encapsulates the entire shoot order:

        class CannonFireOrders
        {
        public:
            enum AmmunitionType { Explosive = 0, ArmorPiercing, Napalm }
            CannonFireOrders( AmmunitionType type, int direction, int elevation, int power);

            int direction() const;
            int elevation() const;
            int power() const;
            AmmunitionType type() const;

        private:
            int m_dir;
            int m_elev;
            int m_power;
            AmmunitionType m_type;
        };

        Q_DECLARE_USER_METATYPE_ENUM(CannonFireOrders::AmmunitionType)
        Q_DECLARE_USER_METATYPE(CannonFireOrders)

We also modify the CannonController class in the following manner:

        class CannonController : public QtopiaIpcAdaptor
        {
            Q_OBJECT

        public:
            CannonController(QObject *parent = 0);
            void shoot();

        public slots:
            void missed();
            void hit();

        signals:
            void shootCannon(const CannonFireOrders &orders);
        };

The only thing missing is a way to tell the Qtopia IPC API how to serialize the CannonFireOrders class so it can be shipped to a remote applications, and how the other side would deserialize the information and create a CannonFireOrders. To accomplish this, we must define two new methods in the CannonFireOrders class:

        class CannonFireOrders
    {
        public:

        ....

        template <typename Stream> void serialize(Stream &stream) const;
        template <typename Stream> void deserialize(Stream &stream);
        ...
    };

And in the implementation file we implement both methods. It is important to note this fact, as usually templates are defined in the header files!

        Q_IMPLEMENT_USER_METATYPE_ENUM(CannonFireOrders::AmmunitionType)
        Q_IMPLEMENT_USER_METATYPE(CannonFireOrders)

        ....

        template <typename Stream> void CannonFireOrders::serialize(Stream &s) const
        {
            s << m_dir;
            s << m_elev;
            s << m_power;
            s << m_type;
        }

        template <typename Stream> void CannonFireOrders::deserialize(Stream &s)
        {
            s >> m_dir;
            s >> m_elev;
            s >> m_power;
            s >> m_type;
        }

We can now successfully use custom classes across applications! Please note that both applications must link in the implementation of the custom class.

Migrating Legacy Qtopia Applications

Developers are encouraged to use the new QtopiaIpcAdapter API. It is extremely powerful and can easily support custom classes. However, to support legacy applications and make it easier to port such applications to the new IPC system, Qtopia introduces two new classes. QtopiaChannel and QtopiaIpcEnvelope. Both of these classes work exactly as their QCop counterparts.

The Application Channel

Each application listens on a channel called QPE/Application/appname , where appname is the executable name (the application identifier). Standard messages on this channel are:

QPE/Application/appname

The QPE/Application/appname channel has a special property: when messages are sent to these channels via QtopiaIpcEnvelope, the message is delivered even if the application is not yet running (the application is run and the message is then sent).

Command-line access to Qtopia IPC System

The qcop command-line tool can be used to access the Qtopia IPC system from shell scripts and for debugging purposes.


Copyright © 2008 Nokia Trademarks
Qtopia 4.3.3