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