1 DCOP: Desktop COmmunications Protocol
3 Preston Brown <pbrown@kde.org>
6 Revised and extended by Matthias Ettrich <ettrich@kde.org>
9 Extended with DCOP Signals by Waldo Bastian <bastian@kde.org>
13 Motivation and Background:
14 --------------------------
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.
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.
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.
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.
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.
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.
71 This HOWTO describes the manual method first and covers the dcopidl
74 Establishing the Connection:
75 ----------------------------
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
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.
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
98 * returns the appId that is actually registered, which _may_ be
99 * different from what you passed
101 appId = client->registerAs(kApp->name());
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.
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.
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().
117 Sending Data to a Remote Application:
118 -------------------------------------
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
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.
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
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
151 QDataStream arg(data, IO_WriteOnly);
153 if (!client->send("someAppId", "fooObject/barObject", "doIt(int)",
155 qDebug("there was some error using DCOP.");
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.
163 QByteArray data, replyData;
165 QDataStream arg(data, IO_WriteOnly);
167 if (!client->call("someAppId", "fooObject/barObject", "doIt(int)",
168 data, replyType, replyData))
169 qDebug("there was some error using DCOP.");
171 QDataStream reply(replyData, IO_ReadOnly);
172 if (replyType == "QString") {
175 print("the result is: %s",result.latin1());
177 qDebug("doIt returned an unexpected type of reply!");
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'.
188 Receiving Data via DCOP:
189 ------------------------
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.
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.
206 bool BarObject::process(const QCString &fun, const QByteArray &data,
207 QCString &replyType, QByteArray &replyData)
209 if (fun == "doIt(int)") {
210 QDataStream arg(data, IO_ReadOnly);
213 QString result = self->doIt (i);
214 QDataStream reply(replyData, IO_WriteOnly);
216 replyType = "QString";
219 qDebug("unknown function call to BarObject::process()");
224 Receiving Calls and processing them:
225 ------------------------------------
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.
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.
238 Such code could like this:
240 bool BarObject::process(const QCString &fun, const QByteArray &data,
241 QCString &, QByteArray &)
243 if (fun == "doIt(int)") {
244 QDataStream arg(data, IO_ReadOnly);
247 QString result = self->doIt(i);
249 DCOPClientTransaction *myTransaction;
250 myTransaction = kapp->dcopClient()->beginTransaction();
252 // start processing...
253 // Calls slotProcessingDone when finished.
254 startProcessing( myTransaction, i);
258 qDebug("unknown function call to BarObject::process()");
263 slotProcessingDone(DCOPClientTransaction *myTransaction, const QString &result)
265 QCString replyType = "QString";
266 QByteArray replyData;
267 QDataStream reply(replyData, IO_WriteOnly);
269 kapp->dcopClient()->endTransaction( myTransaction, replyType, replyData );
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.
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.
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).
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
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.
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.
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.
312 The following example shows how KLauncher emits a signal whenever it notices
313 that an application that was started via KLauncher terminates.
316 QDataStream stream(params, IO_WriteOnly);
318 kapp->dcopClient()->emitDCOPSignal("clientDied(pid_t)", params);
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:
324 connectDCOPSignal(0, 0, "clientDied(pid_t)", "clientDied(pid_t)", false);
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.
333 Using the dcopidl compiler
334 ---------------------
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.
340 This also allows you to describe the interface for your class in a
341 single, separate header file.
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
350 #ifndef MY_INTERFACE_H
351 #define MY_INTERFACE_H
353 #include <dcopobject.h>
355 class MyInterface : virtual public DCOPObject
361 virtual ASYNC myAsynchronousMethod(QString someParameter) = 0;
362 virtual QRect mySynchronousMethod() = 0;
367 As you can see, you're essentially declaring an abstract base class, which
368 virtually inherits from DCOPObject.
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.
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.
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.
388 class MyClass: public QObject, virtual public MyInterface
396 ASYNC myAsynchronousMethod(QString someParameter);
397 QRect mySynchronousMethod();
400 Note: (Qt issue) Remember that if you are inheriting from QObject, you must
401 place it first in the list of inherited classes.
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.
412 DCOPObject("MyInterface")
417 Now you can simply implement the methods you have declared in your interface,
418 exactly the same as you would normally.
422 void MyClass::myAsynchronousMethod(QString someParameter)
424 qDebug("myAsyncMethod called with param `" + someParameter + "'");
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:
432 class MyClass: public QObject, virtual public DCOPObject
442 ASYNC myAsynchronousMethod(QString someParameter);
443 QRect mySynchronousMethod();
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.
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>.
460 Inter-user communication
461 ------------------------
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.
467 To do this, two steps have to be taken:
469 a) both processes need to talk to the same DCOP server
470 b) the authentication must be ensured
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
485 ICEAUTHORITY=~user/.ICEauthority kdesu root -c kcmroot -dcopserver `cat ~user/.DCOPserver`
487 will, after kdesu got the root password, execute kcmroot as root, talking
488 to the user's dcop server.
491 NOTE: DCOP communication is not encrypted, so please do not
492 pass important information around this way.
497 A few back-of-the-napkin tests folks:
501 #include <kapplication.h>
503 int main(int argc, char **argv)
507 app = new KApplication(argc, argv, "testit");
513 g++ -O2 -o testit testit.cpp -I$QTDIR/include -L$QTDIR/lib -lkdecore
515 on Linux yields the following memory use statistics:
525 If I create the KApplication's DCOPClient, and call attach() and
526 registerAs(), it changes to this:
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
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:
544 0.28user 0.02system 0:00.32elapsed 92%CPU (0avgtext+0avgdata 0maxresident)k
545 0inputs+0outputs (1084major+62minor)pagefaults 0swaps
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:
550 0.27user 0.03system 0:00.34elapsed 87%CPU (0avgtext+0avgdata 0maxresident)k
551 0inputs+0outputs (1107major+65minor)pagefaults 0swaps
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.