[daemon-fix] fixed querying about name information
[platform/upstream/dbus.git] / doc / dcop-howto.txt
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
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@kde.org> and Matthias
457 Ettrich <ettrich@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