13.5. Description of DCOP's Programming Interface

At its first implementation, the DCOP protocol offered only a handful of methods necessary for the proper functioning of interprocess communication. To date, it is possible to manually implement the DCOP mechanism in a program using only primordial DCOP methods: send(), call() and process(). An application has to make use of the default DCOPClient object offered by the base KDE class KApplication and then use the send() and/or call() methods. Also, part of the objects of an application can inherit the DCOPObject class and then overload the virtual process() method.

In order to make the use of DCOP even simpler, a compiler is provided for the DCOP IDL (Interface Description Language). This compiler, named dcopidl, while fulfilling a job similar to CORBA's integrated IDL compiler, remains simpler to use. This is because dcopidl's functioning principle is similar to Qt's moc pre-compiler. Special preprocessor specifiers placed in a header (.h) file are enough for dcopidl to automatically generate _skel.cpp and _stub.cpp files for the future DCOP client.

13.5.1. Starting it All

Every application that complies with KDE's API can be easily invested with DCOP client functionality. A call to the KApplication::dcopClient() method determines an instantiation of a DCOPClient object inside the current KApplication. The programmer is provided with a pointer to this instance:


   1 
   2 DCOPClient *client = kapp->dcopClient();
   3 

Up to here, the provided tools are inert. In order to actually enable DCOP, the client has to be "attached" to the server. A code line such as


   1 
   2 bool done = client->attach();
   3 

will accomplish this. At this moment, if the answer from the attaching call is true, the client is capable of communications because the dcopserver accepted an anonymous registration from it. For a few reasons (the most common of which is that the server is not available), the value returned by the attach() call can be false, in which case the KApplication object will pop up an error dialog box.

If the current client needs to send and also receive messages, and then process data extracted from these messages, a proper registration with the server is needed:


   1 
   2 QCString realAppId = client->registerAs(kapp->name());
   3 

The parameter to registerAs() only suggests a registration identifier (id) for the current application. The returned value actually indicates the real application id, as decided by the server. In fact, a second parameter to registerAs(), which has an implicit value of true, imposes that the operating system process identifier (the PID) be attached to the application name. Application identifiers have to be allowed to differ from the requested id because an application can exist in multiple instances on the desktop at a given moment. But each instance needs a unique identifier in order for the communication to remain possible.

This chapter discusses in more detail later a special case of DCOPClient: a KUniqueApplication. It is important to mention that for clients based on KUniqueApplication, no attaching or registration to the dcopserver is needed, because in such a case these are both performed automatically.

A brief statement is required here about efficiency issues of the DCOP client implementation. If the KApplication:dcopClient() method never gets called or if its call is unsuccessful, a DCOPClient instance is not created, and hence no memory allocation occurs.

13.5.2. Using send(), call(), process(), and Friends

If you are a programmer who needs a better understanding of how DCOP functions, you'll want to carefully read this section. Manual usage of the desktop protocol is explained here and the syntaxes and use of the send(), call(), and process() methods are described. If you believe that you would be better served by an automatic mechanism, you can safely skip this section. An automatic mechanism that builds DCOP capabilities in KDE applications is described later in the chapter.

13.5.2.1. Send and Forget

Client"A" sends a message to client "B". The communication occurs only in one sense. The originator of the message doesn't want to know whether the recipient takes action as a consequence. This is the simplest method of communication provided by DCOP. Client "B" doesn't need to be different from "A" and doesn't need to be unique. Details of broadcast communication are covered later in this chapter. A client uses DCOPClient::send() as illustrated in Listing 13.3.


Example 13.3. Typical Use of DCOPClient::send()

   1 
   2 1: QByteArray  data;                     // "raw support" for data
   3 2: QDataStream arg(data, IO_WriteOnly);  // "container" provides
   4                                          // easy access to data
   5 3: int a_number = 3;
   6 4: arg << a_number;                      // put information on the
   7                            // "support" in the "container"
   8 5: if (!client->send("otherClientId",    // identify the recipient
   9 6:                   "anObject/aChildOject", //hierarchically designate
  10                            // the targeted object
  11 7:                   "readAnInt(int)",   // signature of the method
  12                            // that will handle sent data
  13 8:                   data));             // the data
  14 9:   kdDebug << "Sending data over DCOP failed" << endl;
  15 

First, the sender client needs to indicate the complete hierarchy of the object providing the method designated to process the sent data (line 6). Second, the method's signature, as marked in line 7, indicates the types of parameters the method accepts. It doesn't provide the type returned because the C++ standard distinguishes overloaded methods by number and types of parameters and neglects the return type.

A second form of the method DCOPClient::send() (see Listing 13.4), provided for convenience, uses QString (compare line 8 of Listing 13.3 with line 5 of Listing 13.4) instead of QDataStream as a data carrier. This kind of usage occurs frequently.


Example 13.4. DCOPClient::send() with QString Data

   1 
   2 1: bool send_fast = true;
   3 2: client->send("travelingInTheAlps",
   4 3:              "happyMan/hmWithBigVoice",
   5 4:              "countTheEchos(QString)", // *example*
   6 5:              QString("Hello World"),   // shouting off a cliff :-)
   7 6:             send_fast));               // use "fast" IPC mechanisms
   8 

Line 6 in Listing 13.4 describes the feature of DCOP that allows a client to recommend use of a faster mechanism of communication. Such a mechanism isn't guaranteed to always be available. It will work only during communications between clients existing on the same local machine.

As already indicated, the sender client issues the message and continues his normal functions without waiting for communication acknowledgments. This is a very common need in the desktop environment. Often, such a send-and-forget message has to be issued to many clients at once. In a hypothetical situation, a configuration module notifies all existing konsole instances about a configuration change, using "konsole_*" as the first parameter of DCOPClient::send().

Theoretically, a global broadcast (that is, using "*" as a first parameter of the DCOPClient::send() method) is also possible. Yet, because DCOPClient::send() doesn't check for acknowledgments, no guarantee is offered that even one client processed the message. Wildcards are also allowed in the second parameter (the objects hierarchy). Using many wildcards in DCOP communications is a bad idea, though, because it generates large amounts of IPC traffic.

Note

A special mention is necessary: Use of wildcards assumes special support on the side of recipient clients. Their DCOPObject::process() method (see the section "Analyze and Take Action" later in this chapter) has to offer special code for handling wildcards. This is usually available with clients built using dcopidl (explained further later in this chapter) but seldomly so with manually written clients.

13.5.2.2. Call and Listen

Client"A" calls the peer client "B" and waits for an answer. This two-way communication is achieved through the use of the DCOPClient::call() method (see Listing 13.5).


Example 13.5. Typical Use of DCOPClient::call()

   1 
   2  1: QByteArray  data, reply_data;    // also prepare a byte array
   3                          // for the reply
   4  2: QCString reply_type; // will contain the type of the reply
   5  3: QDataStream arg(data, IO_WriteOnly);
   6  4: int a_number = 3;
   7  5: arg << a_number;
   8  6: if (!client->call("otherClientId",
   9  7:                   "anObject/aChildOject",
  10  8:                   "readAnIntAndAnswer(int)",
  11              // signature of method to handle data and answer
  12  9:                   data,                      // sent data
  13 10:                   reply_type,
  14              // type of data contained in the answer
  15 11:                   reply_data);         // the answer
  16 12:   kdDebug << "Calling over DCOP failed!" << endl;
  17 13: else {
  18 14:   QDataStream answer(reply_data, IO_ReadOnly);
  19 15:   if (reply_type == "Qstring") {
  20 16:     QString result;
  21 17:     answer >> result;
  22 18:     this->doSomething(result);
  23 19:   } else
  24 20:     kdDebug << "Calling over DCOP succeeded,\
  25              but the answer had wrong type!" << endl;
  26 

Use of wildcards(broadcasting) isn't allowed with the DCOPClient::call() method because the communication is established from peer to peer. In other words, the originator client waits for exactly one answer. Of course, this can be a problem when peer clients are registered with the server by identifiers different from their name (for example, clients registered with the form appname-pid). Yet, the server gains heuristic capabilities that allow use of generic identifiers. This way, DCOPClient::call() can use a generic but sensible name (for example, "konqueror"). The server will pick up and establish connection with the first available instance from the group of clients whose identifiers are matching the generic name (for example, "konqueror-NNN", where "NNN" are operating system's process identifiers, or PIDs).

13.5.2.3. Analyze and Take Action

The previous two sections described how a DCOP client can generate DCOP messages. These messages are sent over communication channels that the client establishes with the DCOP server during the initial phases (attach(), registerAs()). The server is only expected to pass the message over to the designated recipient—only this client knows how to process the transmitted data.

A client gains reception abilities through the inheritance of a special class provided by the DCOP mechanism. This usually means that a receiving client uses multiple inheritance:

  • It inherits its normal parent; for example, a QWidget, a KCModule, or a KApplication class.

  • It inherits the DCOPObject class available in the DCOP API.

A sample implementation is shown in Listing 13.6.


Example 13.6. Simple Object that Implements DCOP Processing

   1 
   2 File asmartwidget.h
   3 ------------------------------------------------
   4  1: #include <qwidget.h>
   5  2: #include <qlabel.h>
   6  3: #include <qlayout.h>
   7  4: #include <dcopobject.h>
   8  5:
   9  6: class ASmartWidget : public QWidget, public DCOPObject {
  10  7:
  11  8: protected:
  12  9:   QLabel *l_front;
  13 10:
  14 11: public:
  15 12:   ASmartWidget(const char* name);
  16 13:
  17 14:   bool setFront(QString&);
  18 15:   QString& front() { return l_front->text();};
  19 16:
  20 17: protected:
  21 18:   bool process(const QCString &fun,  // the function to be called
  22 19:                const QByteArray &data,
  23                              // data passed to the function
  24 20:                QCString &reply_type, // indicate what type has
  25                              // the reply data
  26 21:                QByteArray &reply_data);// the answer (reply data)
  27 22:
  28 23: };
  29 
  30 File asmartwidget.cpp
  31 ------------------------------------------------
  32  1: #include <qbitarray.h>
  33  2: #include <qdatastream.h>
  34  3:
  35  4: ASmartWidget::ASmartWidget(const char* name):
  36  5:   QWidget(name),
  37  6:   DCOPObject() {
  38  7:
  39  8:     QVBoxLayout *lay = new QVBoxLayout (this, 10, 10);
  40  9:     l_front = new QLabel(this, "Hello, I'm a smart widget);
  41 10:     lay->addWidget (front);
  42 11:
  43 12:   }
  44 13:
  45 14: bool ASmartWidget::setFront(QString& l) {
  46 15:   // a bit of data processing - eventually filter contents of l
  47 16:   if (l.find("smart") != -1) {
  48 17:     l_front->setText( l );
  49 18:     return true;
  50 19:   } else
  51 20:     return false;
  52 21: }
  53 22:
  54 23: bool ASmartWidget::process(const QCString &fun,
  55                      const QByteArray &data,
  56 24:                            QCString &reply_type,
  57                      QByteArray &reply_data) {
  58 25:
  59 26:   if (fun == "setFront(QString&)") {
  60 27:     QDataStream arg(data, IO_ReadOnly);
  61 28:     QString& atext;
  62 29:     arg >> atext;
  63 30:     bool result = setFront(atext);
  64 31:
  65 32:     QDataStream answer(reply_data, IO_WriteOnly);
  66 33:    answer << result;
  67 34:     reply_type = "bool";
  68 35:     return true;
  69 36:   } else {
  70 37:       kdDebug << "Processing DCOP call failed. Function unknown!"
  71             << endl;
  72 38:       return false;
  73 39:   }
  74 40: }
  75 

The preceding code is very easy to understand and even easier to use, in combination with what you learned already about DCOPClient::send() and DCOPClient::call(). It is straightforward to make the preceding class a member of a proper KDE application, start this application, and then from another DCOP client, issue a send() of the form


   1 
   2 client->send("someApplication",
   3              "AsmartWidget",
   4              "setFront(QString&)",
   5              QString("To be smart is not enough"));
   6 

This action will make your small widget acknowledge its human-like limitations.

As previously mentioned, a DCOPObject::process() method becomes part of an object's interface through inheritance of the DCOPObject class. The programmer needs to ensure inheritance and implementation for each and every object of his application that has to offer DCOP reception capabilities. Yet, it is possible to build DCOP call processing mechanisms directly at an application-wide level. Two ways of achieving this are explained here.

The setDefaultObject() method accepts one unique parameter, a QCString that denominates the object that receives and processes all application-wide DCOP calls. Its pair method, DCOPClient::defaultObject(), returns a QCString with the name of this special object member of the application.

The API of the class DCOPClient also offers a DCOPClient::process() method. In the initial phases of the development of the DCOP technology, the process() capabilities were achieved by an application through inheritance from the DCOPClient class. The DCOPClient::process() method has the same definition as the DCOPObject::process() method. It offers a second method of implementation for application-wide DCOP call processing. Developers should prefer the use of DCOPObject or DCOPObjectProxy classes for this purpose, however.

13.5.2.4. Longer Calls Become Transactions

Time is an important component of communications processes. This affirmation is obviously valid in the real world (information about a large storm heading to Bill's house has no value for Bill if it arrives after the storm has already calmed). And it remains valid, while gaining strong connotations, in the programming world. There are two aspects in the involvement of time in process communication:

  • Conjuncture—Events have to occur at the right moment (proper handling of erratic events is mandatory).

  • Duration—Events have to behave in a smart way in relation to the time needed for them to be transmitted and/or processed.

The first aspect is less important at this point in the discussion. In relation to the duration of events, the DCOP mechanism needs some explanation. As presented in the previous sections, the DCOPClient::call() is a blocking method. Its use implies awareness of GUI refresh issues and effective event loop treatments, as well as concerns related to the continuous processing of DCOP calls.

Fortunately, things are made easy by methods provided by the DCOPClient class. The family of transaction methods enlists the following:


   1 
   2 DCOPClientTransaction* DCOPClient::beginTransaction()
   3 
   4 Q_INT32                DCOPClient::transactionId()
   5 
   6 void
   7 DCOPClient::endTransaction(DCOPClientTransaction* newTr,
   8                         //a handle of the negociated transaction
   9                            QCString& reply_type,
  10                         // data type and data stream that were not
  11                            QDataStream& reply_data)
  12 // not available as an immediate answer to a call
  13 

The signatures shown in the code are implying that a transaction lives like an object of type class DCOPClientTransaction (defined and implemented in the DCOP API). The transaction identifier is an integer declared with the platform-independent type macros offered by the Qt library.

Understanding the functionality offered by these methods is straightforward, as exemplified by the following code. Assume that the method of our humanly smart widget, which changed the text on the front label, executes a time-consuming filtering operation instead of simply detecting the word "smart" in the input. The implementation of our class needs to be changed as shown in Listing 13.7


Example 13.7. DCOP Processing with Transactions

   1 
   2 File asmartwidget.h
   3 ------------------------------------------------
   4  1: #include <qwidget.h>
   5  2: #include <qlabel.h>
   6  3: #include <qlayout.h>
   7  4: #include <dcopclient.h>
   8  5: #include <dcopobject.h>
   9  6:
  10  7: class ASmartWidget : public QWidget, public DCOPObject {
  11  8: Q_OBJECT
  12  9:
  13 10: protected:
  14 11:   QLabel *l_front;
  15 12:
  16 13: public:
  17 14:   ASmartWidget(const char* name);
  18 15:
  19 16:   void changeFront(DCOPClientTransaction*, QString&);
  20 17:   QString& front() { return l_front->text();};
  21 18:
  22 19: public slots:
  23 20:   void frontIsChanged(DCOPClientTransaction* ,
  24                   QByteArray&, QDataStream &);
  25 21:
  26 22:
  27 23: protected:
  28 24:   bool process(const QCString &fun,   // the function to be called
  29 25:                const QByteArray &data,
  30                               // data passed to the function
  31 26:                QCString &reply_type,  // indicate what type has
  32                             // the reply data
  33 27:                QByteArray &reply_data);// the answer (reply data)
  34 28:
  35 29: };
  36 
  37 File asmartwidget.cpp
  38 ------------------------------------------------
  39  1: #include <qbitarray.h>
  40  2: #include <qdatastream.h>
  41  3:
  42  4: ASmartWidget::ASmartWidget(const char* name):
  43  5:   QWidget(name),
  44  6:   DCOPObject() {
  45  7:
  46  8:     QVBoxLayout *lay = new QVBoxLayout (this, 10, 10);
  47  9:     l_front = new QLabel(this, "Hello, I'm a smart widget);
  48 10:     lay->addWidget (front);
  49 11:
  50 12:   }
  51 13:
  52 14: void ASmartWidget::changeFront(DCOPClientTransaction* aTransaction,
  53                          DCOPQString& l) {
  54 15:
  55 16:   bool succeeded = false;
  56 17:
  57 18:   // time consuming data processing -
  58 19:   //    complex filter and cruncher for the contents of l
  59 20:   // for (…) {
  60 21:   // …
  61 22:   // }
  62 23:
  63 24:   if (l.find('smart') != -1) { // or other interesting condition
  64 25:     l_front->setText( l );
  65 26:     succeeded = true;
  66 27:   } else
  67 28:     succeeded = false;
  68 29:
  69 30:   frontIsChanged(aTransaction, succeeded);
  70 31: }
  71 32:
  72 33: bool ASmartWidget::process(const QCString &fun,
  73                      const QByteArray &data,
  74 34:                            QCString &reply_type,
  75                       QByteArray &reply_data) {
  76 35:
  77 36:   if (fun == "setFront(QString&)") {
  78 37:
  79 38:     DCOPClientTransaction *myTransaction;
  80 39:     newTransaction = kapp->dcopClient()->beginTransaction();
  81 40:
  82 41:     QDataStream arg(data, IO_ReadOnly);
  83 42:     QString& atext;
  84 43:     arg >> atext;
  85 44:
  86 45:     Q_INT32 trId = kapp->dcopClient()->transactionId();
  87                 // trId == 0 if no transaction
  88 46:     if (trId) {
  89 47:       changeFront(newTransaction, atext);
  90 48:      kdDebug << "Transaction " << trId << " established!" << endl;
  91 49:       return true;
  92 50      } else {
  93 51:       kdDebug << "Processing DCOP call failed.\
  94                 No transaction accepted!" << endl;
  95 52:       return false;
  96 53:
  97 54:   } else {
  98 55:       kdDebug << "Processing DCOP call failed. Function unknown!"
  99             << endl;
 100 56:       return false;
 101 57: }
 102 58:
 103 59: void ASmartWidget::frontIsChanged(DCOPClientTransaction* aTransaction,
 104 60:    bool data) {
 105 61:   QByteArray reply_data;
 106 62:   QDataStream answer(reply_data, IO_WriteOnly);
 107 63:   answer << data;
 108 64:   QCString reply_type = "bool";
 109 65:   kapp->dcopClient()->endTransaction(aTransaction,
 110                              reply_type, reply_data);
 111 66:
 112 67: }
 113 

This example makes clear that the transaction methods have a proper usage only inside the implementation of the DCOPObject::process() method. Using transactions is obviously more complex and a bit heavier, both in terms of programming and resource usage (more DCOP communication traffic). Of course, transactions can also generate complex puzzles of application functionality and usability. When a call() can be answered with a transaction, all assumptions about the linearity of caller's functioning are wrong. On the other side, the main reason for the existence of transaction methods is to allow implementations of non-blocking DCOP calls. Consequently, attention and consideration in the use of transactions is advised.

Note

Another tool referring to blocking calls is the signal DCOPClient::blockUserInput(bool). This signal is automatically used by KApplication to block (parameter is true) or release (parameter is false) the graphical interface while the client waits for an answer to a DCOP call. The programmer doesn't normally have a use for manually emitting this signal.

13.5.2.5. Handling the Connection

An attached client can cut all communication with the DCOP server by detaching itself. This operation is achieved through a call to the DCOPClient::detach() method. Such a call is automatically performed during the client's normal stop (during the call of client's main destructor). A manual call is also allowed.

Situations may occur in which the connection with the DCOP server has to be deactivated temporarily. For example, when the user is prompted to decide upon a DCOP related situation, the program can halt communication for a while using the method DCOPClient::suspend(). If the user's decision allows for continuing, the program calls DCOPClient::resume() to reestablish the communication. The developer has to pay attention to the fact that suspending the connection for a relatively long time might be a bad idea. If other clients are attempting to perform call() connections to the currently suspended application, they will hang (see the section"Using send(), call(), process(), and Friends," earlier in the chapter).

13.5.3. Automated Elegance—dcopIDL

The preceding section discussed how to proceed for a manual implementation of DCOP capabilities in a KDE application. As already mentioned, an automated way of developing DCOP support exists. To this purpose, the DCOP authors created a set of IDL compiling tools: dcopidl and dcopidl2cpp. These compilers make use of a special syntax of header files to generate standard encapsulation methods for the DCOP messaging. A new iteration of part of the smart widget code will help illustrate this (see Listing 13.8).


Example 13.8. Using dcopidl

   1 
   2 File asmartwidget.h
   3 ------------------------------------------------
   4  1: #include <qwidget.h>
   5  2: #include <qlabel.h>
   6  3: #include <qlayout.h>
   7  4: #include <dcopobject.h>
   8  5:
   9  6: class ASmartWidget : public QWidget, public DCOPObject {
  10  7: K_DCOP
  11  8: Q_OBJECT
  12  9:
  13 10: protected:
  14 11:   QLabel *l_front;
  15 12:
  16 13: public:
  17 14:   ASmartWidget(const char* name);
  18 15
  19 16:   QString& front() { return l_front->text();};
  20 17:
  21 18: k_dcop:
  22 19:   bool changeFront(QString& l);
  23 20:
  24 21: };
  25  File: asmartwidget,cpp
  26 -----------------------------------
  27  1:  #include <qbitarray.h>
  28  2:  #include <qdatastream.h>
  29  3:  #include "asmartwidget.h"
  30  4:
  31  5:  ASmartWidget::ASmartWidget(const char* name)
  32  6:    : QWidget(0, name),
  33  7:      DCOPObject()
  34  8:    {
  35  9:      QVBoxLayout *lay = new QVBoxLayout (this, 10, 10);
  36 10:      l_front = new QLabel(this, "Hello, I'm a smart widget");
  37 11:      lay->addWidget (l_front);
  38 12:    }
  39 13:
  40 14:  bool ASmartWidget::changeFront(const QString& l) {
  41 15:
  42 16:    bool succeeded = false;
  43 17:
  44 18:    if (l.find("smart") != -1) { // or other interesting condition
  45 19:      l_front->setText( l );
  46 20:      succeeded = true;
  47 21:    } else
  48 22:      succeeded = false;
  49 23:
  50 24:    return succeeded;
  51 25:  }
  52 

When comparing Listing 13.8 with the code shown in Listing 13.7, the simplifications of using DCOP provided by the dcopidl mechanism become evident. The asmartwidget.cpp file is simplified accordingly (no need to implement the ::process() method). New elements to pay attention to in this last code example appear in lines 7, 18, and 19.

K_DCOP (line 7) is a preprocessor macro that helps the dcopidl compiler to decide that the ASmartWidget class has to be processed with respect to DCOP functionalities.

The construct k_dcop: present on line 18 is similar to standard C++ scope delimiters (public, private, protected) and helps the dcopidl compiler to detect the methods that will gain DCOP messaging envelopes. All methods entailed between a k_dcop: label and any other valid C++ or Qt delimiters will be included in the DCOP interface of the current object.

Finally, it's important to note that the QString parameter (line 19) of the changeFront() method has assigned an explicit name. A rule of use for the dcopidl compiler is that, while the C++ standard allows anonymous method parameters, all parameters in DCOP-enabled methods need explicit names.

Suppose you create a KDE application having ASmartWidget as its main widget(see Listing 13.9).


Example 13.9. A Typical Application that Uses DCOP

   1 
   2 File myapp.cpp
   3 -------------------------------
   4  1: #include <kapp.h>
   5  2: #include <dcopclient.h>
   6  3: #include "asmartwidget.h"
   7  4:
   8  5: int main(int nargs, char** argv) {
   9  6:
  10  7:   KApplication* a = new KApplication(nargs, argv, "myapp");
  11  8:   ASmartWidget* asw = new ASmartWidget("smart");
  12  9:   a->setMainWidget(asw);
  13 10:
  14 11:   client = a.dcopClient();
  15 12:   client.attach();
  16 13:   client.registerAs("myapp");
  17 14:
  18 15:   return a.exec();
  19 16: }
  20 

Line 13 shows that your application (as all applications built to receive and process DCOP messages) needs a non-anonymous registration with the dcopserver.

With the help of a little Makefile magic (described in the following section) and with heavy use of the dcopidl tools, the application will be compiled with built-in DCOP functionality. The tools will automatically generate a few files:

  • asmartwidget.kidl is a helper file containing XML code generated by the dcopidl tool.

  • asmartwidget_skel.cpp is a skeleton file, in which the dcopidl2cpp tool writes the autogenerated ::process() method needed to envelop the DCOP enabled methods picked up by processing of the header file.

  • asmartwidget_stub.h is an autogenerated header file that will be installed with the KDE system and then included in DCOP clients willing to use the DCOP interface that myapp offers.

A stub file can be also written by hand. Listing 13.10 is a live example, extracted from the KDE 2 desktop panel, Kicker:


Example 13.10. Example of a Handmade Stub File

   1 
   2  1: #ifndef KICKER_INTERFACE_H
   3  2: #define KICKER_INTERFACE_H
   4  3:
   5  4: #include <dcopobject.h>
   6  5:
   7  6: class KickerInterface : virtual public DCOPObject
   8  7: {
   9  8:    K_DCOP
  10  9:
  11 10:    k_dcop:
  12 11:
  13 12:        virtual void configure() = 0;
  14 13: };
  15 14:
  16 15: #endif // Included this file.
  17 

A DCOP client that wants to communicate via DCOP with the new smarter widget has only to include the published interface file (asmartwidget_stub.h). An example of the implementation of such a client is shown in Listing 13.11.


Example 13.11. DCOP Client Using the Automatically Generated Interface of Another DCOP Client

   1 
   2 File aclient.cpp
   3 ---------------------------------
   4  1: #include <kapp.h>
   5  2: #include <dcopclient.h>
   6  3: #include "asmartwidget_stub.h"
   7  4:
   8  5: int main( int argc, char** argv )
   9  6: {
  10  7:   // client doesn't need GUI hence set fourth parameter to false
  11  8:   KApplication app( argc, argv, "autoclient", false);
  12  9:
  13 10:   app.dcopClient()->attach();
  14 11:
  15 12:   ASmartWidget_stub iface( "myapp", "AsmartWidget" );
  16                     // automatically generated class
  17 13:   iface.changeFront(QString("Now this is really smart!"));
  18 14: }
  19 

Line 12 in Listing 13.11 exemplifies the use of the automatically generated "stub" interface. This type of usage is visibly more convenient than the manual definitions of send(), call(), and process() on both developed clients. The advantages become especially evident with large programming projects. Using the dcopidl compiler proved to be compelling enough that most KDE applications—which initially used manual DCOP interface implementations—were recently rewritten to employ this easier and better programming technique.

Looking at how things are prepared for the use of the dcopidl tools might raise the question of how does the compiler realize the difference between a method to be treated as a send() and one that will be a call(). The specification of the dcopidl tools provides the developer with the ASYNC pseudotype. ASYNC is a precompiler macro that translates to the valid C++ type void. The developer writes ASYNC in the header file defining the DCOP interface, in front of the definition of methods that are expected to be treated as send() methods. The dcopidl tools will interpret this marker at precompilation and invest the marked method with proper non-blocking implementations.

13.5.4. Makefile Magic

In order for the automated DCOP support to be built in to an application, use of proper make rules is needed. A few specific additions will aid the compilation of the preceding examples when using the dcopidl tools:

  • A rule is needed to generate the .kidl file.

  • Another rule will help create the _skel.cpp, _stub.h, and _stub.cpp files.

  • The generated _skel.cpp (and eventually _stub.cpp) source needs to be compiled.

Of course, the usual details related to normal project management have to be taken care of. A Makefile example is shown in Listing 13.12.


Example 13.12. Specific Makefile Rules Needed for the DCOP Mechanism

   1 
   2  1:  QTDIR = /home/ctibirna/kde/2_0/qt-copy
   3  2:  CXXFLAGS = -I${QTDIR}/include -I${KDEDIR}/include -I.
   4  3:  LDFLAGS =  -L${QTDIR}/lib -L${KDEDIR}/lib -L/usr/X11R6/lib
   5  4:  LDADD = -ldl -lqt -lICE
   6  5:
   7  6:  all: autoclient myapp
   8  7:
   9  8:  autoclient : asmartwidget_stub.o aclient.o
  10  9:          g++ asmartwidget_stub.o aclient.o $(LDFLAGS) $(LDADD) -o  autoclient
  11 10:
  12 11:  myapp      : aswmartwidget.o asmartwidget_skel.o asmartwidget_moc.o  myapp.o
  13 12:         g++ myapp.o asmartwidget.o asmartwidget_skel.o asmartwidget_moc.o\
  14 13:           $(LDFLAGS) $(LDADD) -o myapp
  15 14:
  16 15:  .cpp.o:
  17 16:          g++ $(CXXFLAGS) -c $<
  18 17:
  19 18:  asmartwidget.kidl: asmartwidget.h
  20 19:        dcopidl asmartwidget.h > asmartwidget.kidl || rm -f  asmartwidget.kidl
  21 20:  asmartwidget_moc.cpp: asmartwidget.h
  22 21:          ${QTDIR}/bin/moc asmartwidget.h -o asmartwidget_moc.cpp
  23 22:  asmartwidget_skel.cpp: asmartwidget.kidl
  24 23:        dcopidl2cpp asmartwidget.kidl
  25 24:  asmartwidget_stub.cpp: asmartwidget.kidl
  26 25:
  27 26:  clean :
  28 27:          rm -f *.o *_moc.cpp *_skel.* *_stub.* *.kidl myapp autoclient
  29 

The use of the standard KDE development environment makes the issue of the Makefile rules much simpler thanks to the autodetection and autogeneration of makefiles used there. Simply adding the name of the _skel.cpp file to be generated and compiled to the list of the other compilable source files is enough.