Home | History | Annotate | Download | only in doc
      1 		DCOP: Desktop COmmunications Protocol
      2 
      3 		    Preston Brown <pbrown (a] kde.org>
      4 			   October 14, 1999
      5 
      6 	 Revised and extended by Matthias Ettrich <ettrich (a] kde.org>
      7 			   Mar 29, 2000
      8 
      9         Extended with DCOP Signals by Waldo Bastian <bastian (a] kde.org>
     10                            Feb 19, 2001
     11 
     12 
     13 Motivation and Background:
     14 --------------------------
     15 
     16 The motivation behind building a protocol like DCOP is simple.  For
     17 the past year, we have been attempting to enable interprocess
     18 communication between KDE applications. KDE already has an extremely
     19 simple IPC mechanism called KWMcom, which is (was!) used for communicating
     20 between the panel and the window manager for instance.  It is about as
     21 simple as it gets, passing messages via X Atoms.  For this reason it
     22 is limited in the size and complexity of the data that can be passed
     23 (X atoms must be small to remain efficient) and it also makes it so
     24 that X is required.  CORBA was thought to be a more effective IPC/RPC
     25 solution.  However, after a year of attempting to make heavy use of
     26 CORBA in KDE, we have realized that it is a bit slow and memory
     27 intensive for simple use.  It also has no authentication available.
     28 
     29 What we really needed was an extremely simple protocol with basic
     30 authorization, along the lines of MIT-MAGIC-COOKIE, as used by X.  It
     31 would not be able to do NEARLY what CORBA was able to do, but for the
     32 simple tasks required it would be sufficient. Some examples of such
     33 tasks might be an application sending a message to the panel saying,
     34 "I have started, stop displaying the 'application starting' wait
     35 state," or having a new application that starts query to see if any
     36 other applications of the same name are running.  If they are, simply
     37 call a function on the remote application to create a new window,
     38 rather than starting a new process.
     39 
     40 Implementation:
     41 ---------------
     42 
     43 DCOP is a simple IPC/RPC mechanism built to operate over sockets.
     44 Either unix domain sockets or tcp/ip sockets are supported. DCOP is
     45 built on top of the Inter Client Exchange (ICE) protocol, which comes
     46 standard as a part of X11R6 and later. It also depends on Qt, but
     47 beyond that it does not require any other libraries. Because of this,
     48 it is extremely lightweight, enabling it to be linked into all KDE
     49 applications with low overhead.
     50 
     51 Model:
     52 ------
     53 
     54 The model is simple.  Each application using DCOP is a client.  They
     55 communicate to each other through a DCOP server, which functions like
     56 a traffic director, dispatching messages/calls to the proper
     57 destinations.  All clients are peers of each other.
     58 
     59 Two types of actions are possible with DCOP: "send and forget"
     60 messages, which do not block, and "calls," which block waiting for
     61 some data to be returned.
     62 
     63 Any data that will be sent is serialized (marshalled, for you CORBA
     64 types) using the built-in QDataStream operators available in all of
     65 the Qt classes.  This is fast and easy.  In fact it's so little work
     66 that you can easily write the marshalling code by hand. In addition,
     67 there's a simple IDL-like compiler available (dcopidl and dcopidl2cpp)
     68 that generates stubs and skeletons for you. Using the dcopidl compiler
     69 has the additional benefit of type safety.
     70 
     71 This HOWTO describes the manual method first and covers the dcopidl
     72 compiler later.
     73 
     74 Establishing the Connection:
     75 ----------------------------
     76 
     77 KApplication has gained a method called "KApplication::dcopClient()"
     78 which returns a pointer to a DCOPClient instance.  The first time this
     79 method is called, the client class will be created.  DCOPClients have
     80 unique identifiers attached to them which are based on what
     81 KApplication::name() returns.  In fact, if there is only a single
     82 instance of the program running, the appId will be equal to
     83 KApplication::name().
     84 
     85 To actually enable DCOP communication to begin, you must use
     86 DCOPClient::attach().  This will attempt to attach to the DCOP server.
     87 If no server is found or there is any other type of error, attach()
     88 will return false. KApplication will catch a dcop signal and display an
     89 appropriate error message box in that case.
     90 
     91 After connecting with the server via DCOPClient::attach(), you need to
     92 register this appId with the server so it knows about you.  Otherwise,
     93 you are communicating anonymously.  Use the
     94 DCOPClient::registerAs(const QCString &name) to do so.  In the simple
     95 case:
     96 
     97 /*
     98  * returns the appId that is actually registered, which _may_ be
     99  * different from what you passed
    100  */
    101 appId = client->registerAs(kApp->name());
    102 
    103 If you never retrieve the DCOPClient pointer from KApplication, the
    104 object will not be created and thus there will be no memory overhead.
    105 
    106 You may also detach from the server by calling DCOPClient::detach().
    107 If you wish to attach again you will need to re-register as well.  If
    108 you only wish to change the ID under which you are registered, simply
    109 call DCOPClient::registerAs() with the new name.
    110 
    111 KUniqueApplication automatically registers itself to DCOP. If you
    112 are using KUniqueApplication you should not attach or register
    113 yourself, this is already done. The appId is by definition
    114 equal to kapp->name(). You can retrieve the registered DCOP client
    115 by calling kapp->dcopClient().
    116 
    117 Sending Data to a Remote Application:
    118 -------------------------------------
    119 
    120 To actually communicate, you have one of two choices.  You may either
    121 call the "send" or the "call" method.  Both methods require three
    122 identification parameters: an application identifier, a remote object,
    123 a remote function. Sending is asynchronous (i.e. it returns immediately)
    124 and may or may not result in your own application being sent a message at
    125 some point in the future. Then "send" requires one and "call" requires
    126 two data parameters.
    127 
    128 The remote object must be specified as an object hierarchy.  That is,
    129 if the toplevel object is called "fooObject" and has the child
    130 "barObject", you would reference this object as "fooObject/barObject".
    131 Functions must be described by a full function signature.  If the
    132 remote function is called "doIt", and it takes an int, it would be
    133 described as "doIt(int)".  Please note that the return type is not
    134 specified here, as it is not part of the function signature (or at
    135 least the C++ understanding of a function signature).  You will get
    136 the return type of a function back as an extra parameter to
    137 DCOPClient::call().  See the section on call() for more details.
    138 
    139 In order to actually get the data to the remote client, it must be
    140 "serialized" via a QDataStream operating on a QByteArray. This is how
    141 the data parameter is "built". A few examples will make clear how this
    142 works.
    143 
    144 Say you want to call "doIt" as described above, and not block (or wait
    145 for a response).  You will not receive the return value of the remotely
    146 called function, but you will not hang while the RPC is processed either.
    147 The return value of send() indicates whether DCOP communication succeeded
    148 or not.
    149 
    150 QByteArray data;
    151 QDataStream arg(data, IO_WriteOnly);
    152 arg << 5;
    153 if (!client->send("someAppId", "fooObject/barObject", "doIt(int)",
    154 	          data))
    155   qDebug("there was some error using DCOP.");
    156 
    157 OK, now let's say we wanted to get the data back from the remotely
    158 called function.  You have to execute a call() instead of a send().
    159 The returned value will then be available in the data parameter "reply".
    160 The actual return value of call() is still whether or not DCOP
    161 communication was successful.
    162 
    163 QByteArray data, replyData;
    164 QCString replyType;
    165 QDataStream arg(data, IO_WriteOnly);
    166 arg << 5;
    167 if (!client->call("someAppId", "fooObject/barObject", "doIt(int)",
    168                   data, replyType, replyData))
    169   qDebug("there was some error using DCOP.");
    170 else {
    171   QDataStream reply(replyData, IO_ReadOnly);
    172   if (replyType == "QString") {
    173     QString result;
    174     reply >> result;
    175     print("the result is: %s",result.latin1());
    176   } else
    177     qDebug("doIt returned an unexpected type of reply!");
    178 }
    179 
    180 N.B.: You cannot call() a method belonging to an application which has
    181 registered with an unique numeric id appended to its textual name (see
    182 dcopclient.h for more info). In this case, DCOP would not know which
    183 application it should connect with to call the method. This is not an issue
    184 with send(), as you can broadcast to all applications that have registered
    185 with appname-<numeric_id> by using a wildcard (e.g. 'konsole-*'), which
    186 will send your signal to all applications called 'konsole'.
    187 
    188 Receiving Data via DCOP:
    189 ------------------------
    190 
    191 Currently the only real way to receive data from DCOP is to multiply
    192 inherit from the normal class that you are inheriting (usually some
    193 sort of QWidget subclass or QObject) as well as the DCOPObject class.
    194 DCOPObject provides one very important method: DCOPObject::process().
    195 This is a pure virtual method that you must implement in order to
    196 process DCOP messages that you receive.  It takes a function
    197 signature, QByteArray of parameters, and a reference to a QByteArray
    198 for the reply data that you must fill in.
    199 
    200 Think of DCOPObject::process() as a sort of dispatch agent.  In the
    201 future, there will probably be a precompiler for your sources to write
    202 this method for you.  However, until that point you need to examine
    203 the incoming function signature and take action accordingly.  Here is
    204 an example implementation.
    205 
    206 bool BarObject::process(const QCString &fun, const QByteArray &data,
    207 		        QCString &replyType, QByteArray &replyData)
    208 {
    209   if (fun == "doIt(int)") {
    210     QDataStream arg(data, IO_ReadOnly);
    211     int i; // parameter
    212     arg >> i;
    213     QString result = self->doIt (i);
    214     QDataStream reply(replyData, IO_WriteOnly);
    215     reply << result;
    216     replyType = "QString";
    217     return true;
    218   } else {
    219     qDebug("unknown function call to BarObject::process()");
    220     return false;
    221   }
    222 }
    223 
    224 Receiving Calls and processing them:
    225 ------------------------------------
    226 
    227 If your applications is able to process incoming function calls
    228 right away the above code is all you need. When your application
    229 needs to do more complex tasks you might want to do the processing
    230 out of 'process' function call and send the result back later when
    231 it becomes available.
    232 
    233 For this you can ask your DCOPClient for a transactionId. You can
    234 then return from the 'process' function and when the result is
    235 available finish the transaction. In the mean time your application
    236 can receive incoming DCOP function calls from other clients.
    237 
    238 Such code could like this:
    239 
    240 bool BarObject::process(const QCString &fun, const QByteArray &data,
    241 		        QCString &, QByteArray &)
    242 {
    243   if (fun == "doIt(int)") {
    244     QDataStream arg(data, IO_ReadOnly);
    245     int i; // parameter
    246     arg >> i;
    247     QString result = self->doIt(i);
    248 
    249     DCOPClientTransaction *myTransaction;
    250     myTransaction = kapp->dcopClient()->beginTransaction();
    251 
    252     // start processing...
    253     // Calls slotProcessingDone when finished.
    254     startProcessing( myTransaction, i);
    255 
    256     return true;
    257   } else {
    258     qDebug("unknown function call to BarObject::process()");
    259     return false;
    260   }
    261 }
    262 
    263 slotProcessingDone(DCOPClientTransaction *myTransaction, const QString &result)
    264 {
    265     QCString replyType = "QString";
    266     QByteArray replyData;
    267     QDataStream reply(replyData, IO_WriteOnly);
    268     reply << result;
    269     kapp->dcopClient()->endTransaction( myTransaction, replyType, replyData );
    270 }
    271 
    272 DCOP Signals
    273 ------------
    274 
    275 Sometimes a component wants to send notifications via DCOP to other
    276 components but does not know which components will be interested in these
    277 notifications. One could use a broadcast in such a case but this is a very
    278 crude method. For a more sophisticated method DCOP signals have been invented.
    279 
    280 DCOP signals are very similair to Qt signals, there are some differences 
    281 though. A DCOP signal can be connected to a DCOP function. Whenever the DCOP
    282 signal gets emitted, the DCOP functions to which the signal is connected are
    283 being called. DCOP signals are, just like Qt signals, one way. They do not
    284 provide a return value. 
    285 
    286 A DCOP signal originates from a DCOP Object/DCOP Client combination (sender). 
    287 It can be connected to a function of another DCOP Object/DCOP Client 
    288 combination (receiver).
    289 
    290 There are two major differences between connections of Qt signals and 
    291 connections of DCOP signals. In DCOP, unlike Qt, a signal connections can
    292 have an anonymous sender and, unlike Qt, a DCOP signal connection can be
    293 non-volatile.
    294 
    295 With DCOP one can connect a signal without specifying the sending DCOP Object 
    296 or DCOP Client. In that case signals from any DCOP Object and/or DCOP Client
    297 will be delivered. This allows the specification of certain events without
    298 tying oneself to a certain object that implementes the events.
    299 
    300 Another DCOP feature are so called non-volatile connections. With Qt signal
    301 connections, the connection gets deleted when either sender or receiver of
    302 the signal gets deleted. A volatile DCOP signal connection will behave the
    303 same. However, a non-volatile DCOP signal connection will not get deleted 
    304 when the sending object gets deleted. Once a new object gets created with 
    305 the same name as the original sending object, the connection will be restored.
    306 There is no difference between the two when the receiving object gets deleted,
    307 in that case the signal connection will always be deleted.
    308 
    309 A receiver can create a non-volatile connection while the sender doesn't (yet)
    310 exist. An anonymous DCOP connection should always be non-volatile.
    311 
    312 The following example shows how KLauncher emits a signal whenever it notices
    313 that an application that was started via KLauncher terminates.
    314 
    315    QByteArray params;
    316    QDataStream stream(params, IO_WriteOnly);
    317    stream << pid;
    318    kapp->dcopClient()->emitDCOPSignal("clientDied(pid_t)", params);
    319 
    320 The task manager of the KDE panel connects to this signal. It uses an 
    321 anonymous connection (it doesn't require that the signal is being emitted
    322 by KLauncher) that is non-volatile:
    323 
    324    connectDCOPSignal(0, 0, "clientDied(pid_t)", "clientDied(pid_t)", false);
    325 
    326 It connects the clientDied(pid_t) signal to its own clientDied(pid_t) DCOP
    327 function. In this case the signal and the function to call have the same name.
    328 This isn't needed as long as the arguments of both signal and receiving function
    329 match. The receiving function may ignore one or more of the trailing arguments
    330 of the signal. E.g. it is allowed to connect the clientDied(pid_t) signal to
    331 a clientDied(void) DCOP function.
    332 
    333 Using the dcopidl compiler
    334 ---------------------
    335 
    336 dcopidl makes setting up a DCOP server easy. Instead of having to implement
    337 the process() method and unmarshalling (retrieving from QByteArray) parameters
    338 manually, you can let dcopidl create the necessary code on your behalf.
    339 
    340 This also allows you to describe the interface for your class in a
    341 single, separate header file.
    342 
    343 Writing an IDL file is very similar to writing a normal C++ header. An
    344 exception is the keyword 'ASYNC'. It indicates that a call to this
    345 function shall be processed asynchronously. For the C++ compiler, it
    346 expands to 'void'.
    347 
    348 Example:
    349 
    350 #ifndef MY_INTERFACE_H
    351 #define MY_INTERFACE_H
    352 
    353 #include <dcopobject.h>
    354 
    355 class MyInterface : virtual public DCOPObject
    356 {
    357   K_DCOP
    358 
    359   k_dcop:
    360 
    361     virtual ASYNC myAsynchronousMethod(QString someParameter) = 0;
    362     virtual QRect mySynchronousMethod() = 0;
    363 };
    364 
    365 #endif
    366 
    367 As you can see, you're essentially declaring an abstract base class, which
    368 virtually inherits from DCOPObject.
    369 
    370 If you're using the standard KDE build scripts, then you can simply
    371 add this file (which you would call MyInterface.h) to your sources
    372 directory. Then you edit your Makefile.am, adding 'MyInterface.skel'
    373 to your SOURCES list and MyInterface.h to include_HEADERS.
    374 
    375 The build scripts will use dcopidl to parse MyInterface.h, converting
    376 it to an XML description in MyInterface.kidl. Next, a file called
    377 MyInterface_skel.cpp will automatically be created, compiled and
    378 linked with your binary.
    379 
    380 The next thing you have to do is to choose which of your classes will
    381 implement the interface described in MyInterface.h. Alter the inheritance
    382 of this class such that it virtually inherits from MyInterface. Then
    383 add declarations to your class interface similar to those on MyInterface.h,
    384 but virtual, not pure virtual.
    385 
    386 Example:
    387 
    388 class MyClass: public QObject, virtual public MyInterface
    389 {
    390   Q_OBJECT
    391   
    392   public:
    393     MyClass(); 
    394     ~MyClass();
    395 
    396     ASYNC myAsynchronousMethod(QString someParameter);
    397     QRect mySynchronousMethod();
    398 };
    399 
    400 Note: (Qt issue) Remember that if you are inheriting from QObject, you must
    401 place it first in the list of inherited classes.
    402 
    403 In the implementation of your class' ctor, you must explicitly initialize
    404 those classes from which you are inheriting from. This is, of course, good
    405 practise, but it is essential here as you need to tell DCOPObject the name of
    406 the interface which your are implementing.
    407 
    408 Example:
    409 
    410 MyClass::MyClass()
    411   : QObject(),
    412     DCOPObject("MyInterface")
    413 {
    414   // whatever...
    415 }
    416 
    417 Now you can simply implement the methods you have declared in your interface,
    418 exactly the same as you would normally.
    419 
    420 Example:
    421 
    422 void MyClass::myAsynchronousMethod(QString someParameter)
    423 {
    424   qDebug("myAsyncMethod called with param `" + someParameter + "'");
    425 }
    426 
    427 
    428 It is not necessary (though very clean) to define an interface as an
    429 abstract class of its own, like we did in the example above. We could
    430 just as well have defined a k_dcop section directly within MyClass:
    431 
    432 class MyClass: public QObject, virtual public DCOPObject
    433 {
    434   Q_OBJECT
    435   K_DCOP
    436 
    437   public:
    438     MyClass(); 
    439     ~MyClass();
    440 
    441   k_dcop:
    442     ASYNC myAsynchronousMethod(QString someParameter);
    443     QRect mySynchronousMethod();
    444 };
    445 
    446 In addition to skeletons, dcopidl2cpp also generate stubs. Those make
    447 it easy to call a DCOP interface without doing the marshalling
    448 manually. To use a stub, add MyInterface.stub to the SOURCES list of
    449 your Makefile.am. The stub class will then be called MyInterface_stub.
    450 
    451 Conclusion:
    452 -----------
    453 
    454 Hopefully this document will get you well on your way into the world
    455 of inter-process communication with KDE!  Please direct all comments
    456 and/or suggestions to Preston Brown <pbrown (a] kde.org> and Matthias
    457 Ettrich <ettrich (a] kde.org>.
    458 
    459 
    460 Inter-user communication
    461 ------------------------
    462 
    463 Sometimes it might be interesting to use DCOP between processes
    464 belonging to different users, e.g. a frontend process running
    465 with the user's id, and a backend process running as root.
    466 
    467 To do this, two steps have to be taken:
    468 
    469 a) both processes need to talk to the same DCOP server
    470 b) the authentication must be ensured
    471 
    472 For the first step, you simply pass the server address (as
    473 found in .DCOPserver) to the second process. For the authentication,
    474 you can use the ICEAUTHORITY environment variable to tell the
    475 second process where to find the authentication information.
    476 (Note that this implies that the second process is able to
    477 read the authentication file, so it will probably only work
    478 if the second process runs as root. If it should run as another
    479 user, a similar approach to what kdesu does with xauth must
    480 be taken. In fact, it would be a very good idea to add DCOP
    481 support to kdesu!)
    482 
    483 For example
    484 
    485 ICEAUTHORITY=~user/.ICEauthority kdesu root -c kcmroot -dcopserver `cat ~user/.DCOPserver`
    486 
    487 will, after kdesu got the root password, execute kcmroot as root, talking
    488 to the user's dcop server.
    489 
    490 
    491 NOTE: DCOP communication is not encrypted, so please do not
    492 pass important information around this way.
    493 
    494 
    495 Performance Tests:
    496 ------------------
    497 A few back-of-the-napkin tests folks:
    498 
    499 Code:
    500 
    501 #include <kapplication.h>
    502 
    503 int main(int argc, char **argv)
    504 {
    505   KApplication *app;
    506 
    507   app = new KApplication(argc, argv, "testit");
    508   return app->exec();
    509 }
    510 
    511 Compiled with:
    512 
    513 g++ -O2 -o testit testit.cpp -I$QTDIR/include -L$QTDIR/lib -lkdecore
    514 
    515 on Linux yields the following memory use statistics:
    516 
    517 VmSize:     8076 kB
    518 VmLck:         0 kB
    519 VmRSS:      4532 kB
    520 VmData:      208 kB
    521 VmStk:        20 kB
    522 VmExe:         4 kB
    523 VmLib:      6588 kB
    524 
    525 If I create the KApplication's DCOPClient, and call attach() and
    526 registerAs(), it changes to this:
    527 
    528 VmSize:     8080 kB
    529 VmLck:         0 kB
    530 VmRSS:      4624 kB
    531 VmData:      208 kB
    532 VmStk:        20 kB
    533 VmExe:         4 kB
    534 VmLib:      6588 kB
    535 
    536 Basically it appears that using DCOP causes 100k more memory to be
    537 resident, but no more data or stack.  So this will be shared between all
    538 processes, right?  100k to enable DCOP in all apps doesn't seem bad at
    539 all. :)
    540 
    541 OK now for some timings.  Just creating a KApplication and then exiting
    542 (i.e. removing the call to KApplication::exec) takes this much time:
    543 
    544 0.28user 0.02system 0:00.32elapsed 92%CPU (0avgtext+0avgdata 0maxresident)k
    545 0inputs+0outputs (1084major+62minor)pagefaults 0swaps
    546 
    547 I.e. about 1/3 of a second on my PII-233.  Now, if we create our DCOP
    548 object and attach to the server, it takes this long:
    549 
    550 0.27user 0.03system 0:00.34elapsed 87%CPU (0avgtext+0avgdata 0maxresident)k
    551 0inputs+0outputs (1107major+65minor)pagefaults 0swaps
    552 
    553 I.e. about 1/3 of a second.  Basically DCOPClient creation and attaching
    554 gets lost in the statistical variation ("noise").  I was getting times
    555 between .32 and .48 over several runs for both of the example programs, so
    556 obviously system load is more relevant than the extra two calls to
    557 DCOPClient::attach and DCOPClient::registerAs, as well as the actual
    558 DCOPClient constructor time.
    559 
    560