You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
577 lines
21 KiB
577 lines
21 KiB
/*
|
|
A dummy source file for documenting the library.
|
|
Copied from HOWTO with small syntactic changes.
|
|
*/
|
|
|
|
/**
|
|
\mainpage The DCOP Desktop COmmunication Protocol library
|
|
|
|
DCOP is a simple IPC/RPC mechanism built to operate over sockets.
|
|
Either unix domain sockets or TCP/IP sockets are supported. DCOP is
|
|
built on top of the Inter Client Exchange (ICE) protocol, which comes
|
|
standard as a part of X11R6 and later. It also depends on Qt, but
|
|
beyond that it does not require any other libraries. Because of this,
|
|
it is extremely lightweight, enabling it to be linked into all Trinity
|
|
applications with low overhead.
|
|
|
|
\section model Model:
|
|
|
|
The model is simple. Each application using DCOP is a client. They
|
|
communicate to each other through a DCOP server, which functions like
|
|
a traffic director, dispatching messages/calls to the proper
|
|
destinations. All clients are peers of each other.
|
|
|
|
Two types of actions are possible with DCOP: "send and forget"
|
|
messages, which do not block, and "calls," which block waiting for
|
|
some data to be returned.
|
|
|
|
Any data that will be sent is serialized (also referred to as marshalling
|
|
in CORBA speak) using the built-in QDataStream operators available in all
|
|
of the Qt classes. This is fast and easy. In fact it's so little work
|
|
that you can easily write the marshalling code by hand. In addition,
|
|
there's a simple IDL-like compiler available (dcopidl and dcopidl2cpp)
|
|
that generates stubs and skeletons for you. Using the dcopidl compiler
|
|
has the additional benefit of type safety.
|
|
|
|
The manual method is covered first, followed by the automatic IDL method.
|
|
|
|
|
|
\section establish Establishing the Connection:
|
|
|
|
TDEApplication has gained a method called \p TDEApplication::dcopClient()
|
|
which returns a pointer to a DCOPClient instance. The first time this
|
|
method is called, the client class will be created. DCOPClients have
|
|
unique identifiers attached to them which are based on what
|
|
TDEApplication::name() returns. In fact, if there is only a single
|
|
instance of the program running, the appId will be equal to
|
|
TDEApplication::name().
|
|
|
|
To actually enable DCOP communication to begin, you must use
|
|
\p DCOPClient::attach(). This will attempt to attach to the DCOP server.
|
|
If no server is found or there is any other type of error,
|
|
DCOPClient::attach() will return false. TDEApplication will catch a dcop
|
|
signal and display an appropriate error message box in that case.
|
|
|
|
After connecting with the server via DCOPClient::attach(), you need to
|
|
register this appId with the server so it knows about you. Otherwise,
|
|
you are communicating anonymously. Use the
|
|
DCOPClient::registerAs(const QCString &name) to do so. In the simple
|
|
case:
|
|
|
|
\code
|
|
appId = client->registerAs(kapp->name());
|
|
\endcode
|
|
|
|
If you never retrieve the DCOPClient pointer from TDEApplication, the
|
|
object will not be created and thus there will be no memory overhead.
|
|
|
|
You may also detach from the server by calling DCOPClient::detach().
|
|
If you wish to attach again you will need to re-register as well. If
|
|
you only wish to change the ID under which you are registered, simply
|
|
call DCOPClient::registerAs() with the new name.
|
|
|
|
KUniqueApplication automatically registers itself to DCOP. If you
|
|
are using KUniqueApplication you should not attach or register
|
|
yourself, this is already done. The appId is by definition
|
|
equal to \p kapp->name(). You can retrieve the registered DCOP client
|
|
by calling \p kapp->dcopClient().
|
|
|
|
|
|
\section sending_data Sending Data to a Remote Application:
|
|
|
|
To actually communicate, you have one of two choices. You may either
|
|
call the "send" or the "call" method. Both methods require three
|
|
identification parameters: an application identifier, a remote object,
|
|
a remote function. Sending is asynchronous (i.e. it returns immediately)
|
|
and may or may not result in your own application being sent a message at
|
|
some point in the future. Then "send" requires one and "call" requires
|
|
two data parameters.
|
|
|
|
The remote object must be specified as an object hierarchy. That is,
|
|
if the toplevel object is called \p fooObject and has the child
|
|
\p barObject, you would reference this object as \p fooObject/barObject.
|
|
Functions must be described by a full function signature. If the
|
|
remote function is called \p doIt, and it takes an int, it would be
|
|
described as \p doIt(int). Please note that the return type is not
|
|
specified here, as it is not part of the function signature (or at
|
|
least the C++ understanding of a function signature). You will get
|
|
the return type of a function back as an extra parameter to
|
|
DCOPClient::call(). See the section on call() for more details.
|
|
|
|
In order to actually get the data to the remote client, it must be
|
|
"serialized" via a QDataStream operating on a QByteArray. This is how
|
|
the data parameter is "built". A few examples will make clear how this
|
|
works.
|
|
|
|
Say you want to call \p doIt as described above, and not block (or wait
|
|
for a response). You will not receive the return value of the remotely
|
|
called function, but you will not hang while the RPC is processed either.
|
|
The return value of DCOPClient::send() indicates whether DCOP communication
|
|
succeeded or not.
|
|
|
|
\code
|
|
QByteArray data;
|
|
QDataStream arg(data, IO_WriteOnly);
|
|
arg << 5;
|
|
if (!client->send("someAppId", "fooObject/barObject", "doIt(int)",
|
|
data))
|
|
tqDebug("there was some error using DCOP.");
|
|
\endcode
|
|
|
|
OK, now let's say we wanted to get the data back from the remotely
|
|
called function. You have to execute a DCOPClient::call() instead of a
|
|
DCOPClient::send(). The returned value will then be available in the
|
|
data parameter "reply". The actual return value of call() is still
|
|
whether or not DCOP communication was successful.
|
|
|
|
\code
|
|
QByteArray data, replyData;
|
|
QCString replyType;
|
|
QDataStream arg(data, IO_WriteOnly);
|
|
arg << 5;
|
|
if (!client->call("someAppId", "fooObject/barObject", "doIt(int)",
|
|
data, replyType, replyData))
|
|
tqDebug("there was some error using DCOP.");
|
|
else {
|
|
QDataStream reply(replyData, IO_ReadOnly);
|
|
if (replyType == "QString") {
|
|
QString result;
|
|
reply >> result;
|
|
print("the result is: %s",result.latin1());
|
|
} else
|
|
tqDebug("doIt returned an unexpected type of reply!");
|
|
}
|
|
\endcode
|
|
|
|
|
|
\section receiving_data Receiving Data via DCOP:
|
|
|
|
Currently the only real way to receive data from DCOP is to multiply
|
|
inherit from the normal class that you are inheriting (usually some
|
|
sort of QWidget subclass or QObject) as well as the DCOPObject class.
|
|
DCOPObject provides one very important method: DCOPObject::process().
|
|
This is a pure virtual method that you must implement in order to
|
|
process DCOP messages that you receive. It takes a function
|
|
signature, QByteArray of parameters, and a reference to a QByteArray
|
|
for the reply data that you must fill in.
|
|
|
|
Think of DCOPObject::process() as a sort of dispatch agent. In the
|
|
future, there will probably be a precompiler for your sources to write
|
|
this method for you. However, until that point you need to examine
|
|
the incoming function signature and take action accordingly. Here is
|
|
an example implementation.
|
|
|
|
\code
|
|
bool BarObject::process(const QCString &fun, const QByteArray &data,
|
|
QCString &replyType, QByteArray &replyData)
|
|
{
|
|
if (fun == "doIt(int)") {
|
|
QDataStream arg(data, IO_ReadOnly);
|
|
int i; // parameter
|
|
arg >> i;
|
|
QString result = self->doIt (i);
|
|
QDataStream reply(replyData, IO_WriteOnly);
|
|
reply << result;
|
|
replyType = "QString";
|
|
return true;
|
|
} else {
|
|
tqDebug("unknown function call to BarObject::process()");
|
|
return false;
|
|
}
|
|
}
|
|
\endcode
|
|
|
|
|
|
\section receiving_calls Receiving Calls and processing them:
|
|
|
|
If your applications is able to process incoming function calls
|
|
right away the above code is all you need. When your application
|
|
needs to do more complex tasks you might want to do the processing
|
|
out of 'process' function call and send the result back later when
|
|
it becomes available.
|
|
|
|
For this you can ask your DCOPClient for a transactionId. You can
|
|
then return from the 'process' function and when the result is
|
|
available finish the transaction. In the mean time your application
|
|
can receive incoming DCOP function calls from other clients.
|
|
|
|
Such code could like this:
|
|
|
|
\code
|
|
bool BarObject::process(const QCString &fun, const QByteArray &data,
|
|
QCString &, QByteArray &)
|
|
{
|
|
if (fun == "doIt(int)") {
|
|
QDataStream arg(data, IO_ReadOnly);
|
|
int i; // parameter
|
|
arg >> i;
|
|
QString result = self->doIt(i);
|
|
|
|
DCOPClientTransaction *myTransaction;
|
|
myTransaction = kapp->dcopClient()->beginTransaction();
|
|
|
|
// start processing...
|
|
// Calls slotProcessingDone when finished.
|
|
startProcessing( myTransaction, i);
|
|
|
|
return true;
|
|
} else {
|
|
tqDebug("unknown function call to BarObject::process()");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
slotProcessingDone(DCOPClientTransaction *myTransaction, const QString &result)
|
|
{
|
|
QCString replyType = "QString";
|
|
QByteArray replyData;
|
|
QDataStream reply(replyData, IO_WriteOnly);
|
|
reply << result;
|
|
kapp->dcopClient()->endTransaction( myTransaction, replyType, replyData );
|
|
}
|
|
\endcode
|
|
|
|
|
|
\section dcopidl Using the dcopidl compiler:
|
|
|
|
dcopidl makes setting up a DCOP server easy. Instead of having to implement
|
|
the process() method and unmarshalling (retrieving from QByteArray) parameters
|
|
manually, you can let dcopidl create the necessary code on your behalf.
|
|
|
|
This also allows you to describe the interface for your class in a
|
|
single, separate header file.
|
|
|
|
Writing an IDL file is very similar to writing a normal C++ header. An
|
|
exception is the keyword 'ASYNC'. It indicates that a call to this
|
|
function shall be processed asynchronously. For the C++ compiler, it
|
|
expands to 'void'.
|
|
|
|
Example:
|
|
|
|
\code
|
|
#ifndef MY_INTERFACE_H
|
|
#define MY_INTERFACE_H
|
|
|
|
#include <dcopobject.h>
|
|
|
|
class MyInterface : virtual public DCOPObject
|
|
{
|
|
K_DCOP
|
|
|
|
k_dcop:
|
|
|
|
virtual ASYNC myAsynchronousMethod(QString someParameter) = 0;
|
|
virtual QRect mySynchronousMethod() = 0;
|
|
};
|
|
|
|
#endif
|
|
\endcode
|
|
|
|
As you can see, you're essentially declaring an abstract base class, which
|
|
virtually inherits from DCOPObject.
|
|
|
|
If you're using the standard Trinity build scripts, then you can simply
|
|
add this file (which you would call MyInterface.h) to your sources
|
|
directory. Then you edit your Makefile.am, adding 'MyInterface.skel'
|
|
to your SOURCES list and MyInterface.h to include_HEADERS.
|
|
|
|
The build scripts will use dcopidl to parse MyInterface.h, converting
|
|
it to an XML description in MyInterface.kidl. Next, a file called
|
|
MyInterface_skel.cpp will automatically be created, compiled and
|
|
linked with your binary.
|
|
|
|
The next thing you have to do is to choose which of your classes will
|
|
implement the interface described in MyInterface.h. Alter the inheritance
|
|
of this class such that it virtually inherits from MyInterface. Then
|
|
add declarations to your class interface similar to those on MyInterface.h,
|
|
but virtual, not pure virtual.
|
|
|
|
Example:
|
|
|
|
\code
|
|
class MyClass: public QObject, virtual public MyInterface
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
MyClass();
|
|
~MyClass();
|
|
|
|
ASYNC myAsynchronousMethod(QString someParameter);
|
|
QRect mySynchronousMethod();
|
|
};
|
|
\endcode
|
|
\note (Qt issue) Remember that if you are inheriting from QObject, you must
|
|
place it first in the list of inherited classes.
|
|
|
|
In the implementation of your class' ctor, you must explicitly initialize
|
|
those classes from which you are inheriting from. This is, of course, good
|
|
practice, but it is essential here as you need to tell DCOPObject the name of
|
|
the interface which your are implementing.
|
|
|
|
Example:
|
|
|
|
\code
|
|
MyClass::MyClass()
|
|
: QObject(),
|
|
DCOPObject("MyInterface")
|
|
{
|
|
// whatever...
|
|
}
|
|
\endcode
|
|
|
|
|
|
Now you can simply implement the methods you have declared in your interface,
|
|
exactly the same as you would normally.
|
|
|
|
Example:
|
|
|
|
\code
|
|
void MyClass::myAsynchronousMethod(QString someParameter)
|
|
{
|
|
tqDebug("myAsyncMethod called with param `" + someParameter + "'");
|
|
}
|
|
\endcode
|
|
|
|
It is not necessary (though very clean) to define an interface as an
|
|
abstract class of its own, like we did in the example above. We could
|
|
just as well have defined a k_dcop section directly within MyClass:
|
|
|
|
\code
|
|
class MyClass: public QObject, virtual public DCOPObject
|
|
{
|
|
Q_OBJECT
|
|
K_DCOP
|
|
|
|
public:
|
|
MyClass();
|
|
~MyClass();
|
|
|
|
k_dcop:
|
|
ASYNC myAsynchronousMethod(QString someParameter);
|
|
QRect mySynchronousMethod();
|
|
};
|
|
\endcode
|
|
|
|
In addition to skeletons, dcopidl2cpp also generate stubs. Those make
|
|
it easy to call a DCOP interface without doing the marshalling
|
|
manually. To use a stub, add MyInterface.stub to the SOURCES list of
|
|
your Makefile.am. The stub class will then be called MyInterface_stub.
|
|
|
|
|
|
\section iuc Inter-user communication:
|
|
|
|
Sometimes it might be interesting to use DCOP between processes
|
|
belonging to different users, e.g. a frontend process running
|
|
with the user's id, and a backend process running as root.
|
|
|
|
To do this, two steps have to be taken:
|
|
|
|
a) both processes need to talk to the same DCOP server
|
|
b) the authentication must be ensured
|
|
|
|
For the first step, you simply pass the server address (as
|
|
found in .DCOPserver) to the second process. For the authentication,
|
|
you can use the ICEAUTHORITY environment variable to tell the
|
|
second process where to find the authentication information.
|
|
(Note that this implies that the second process is able to
|
|
read the authentication file, so it will probably only work
|
|
if the second process runs as root. If it should run as another
|
|
user, a similar approach to what tdesu does with xauth must
|
|
be taken. In fact, it would be a very good idea to add DCOP
|
|
support to tdesu!)
|
|
|
|
For example
|
|
|
|
ICEAUTHORITY=~user/.ICEauthority tdesu root -c kcmroot -dcopserver `cat ~user/.DCOPserver`
|
|
|
|
will, after tdesu got the root password, execute kcmroot as root, talking
|
|
to the user's dcop server.
|
|
|
|
|
|
NOTE: DCOP communication is not encrypted, so please do not
|
|
pass important information around this way.
|
|
|
|
\section protocol DCOP Protocol description:
|
|
|
|
A DCOPSend message does not expect any reply.
|
|
\code
|
|
data: << fromId << toId << objId << fun << dataSize + data[dataSize]
|
|
\endcode
|
|
|
|
A DCOPCall message can get a DCOPReply, a DCOPReplyFailed
|
|
or a DCOPReplyWait message in response.
|
|
\code
|
|
data: << fromId << toId << objId << fun << dataSize + data[dataSize]
|
|
\endcode
|
|
|
|
DCOPReply is the successful reply to a DCOPCall message
|
|
\code
|
|
data: << fromId << toId << replyType << replyDataSize + replyData[replyDataSize]
|
|
\endcode
|
|
|
|
DCOPReplyFailed indicates failure of a DCOPCall message
|
|
\code
|
|
data: << fromId << toId
|
|
\endcode
|
|
|
|
DCOPReplyWait indicates that a DCOPCall message is successfully
|
|
being processed but that response will come later.
|
|
\code
|
|
data: << fromId << toId << transactionId
|
|
\endcode
|
|
|
|
DCOPReplyDelayed is the successful reply to a DCOPCall message
|
|
after a DCOPReplyWait message.
|
|
\code
|
|
data: << fromId << toId << transactionId << replyType << replyData
|
|
\endcode
|
|
|
|
DCOPFind is a message much like a "call" message. It can however
|
|
be send to multiple objects within a client. If a function in a
|
|
object that is being called returns a boolean with the value "true",
|
|
a DCOPReply will be send back containing the DCOPRef of the object
|
|
who returned "true".
|
|
|
|
All c-strings (fromId, toId, objId, fun and replyType), are marshalled with
|
|
their respective length as 32 bit unsigned integer first:
|
|
\code
|
|
data: length + string[length]
|
|
\endcode
|
|
\note This happens automatically when using QCString on a QDataStream.
|
|
|
|
\section Deadlock protection and reentrancy
|
|
|
|
When a DCOP call is made, the dcop client will be monitoring the
|
|
dcop connection for the reply on the call. When an incoming call is
|
|
received in this period, it will normally not be processed but queued
|
|
until the outgoing call has been fully handled.
|
|
|
|
However, the above scenario would cause deadlock if the incoming call
|
|
was directly or indirectly a result of the outgoing call and the reply
|
|
on the outgoing call is waiting for the result of the incoming call.
|
|
(E.g. a circular call such as client A calling client B, with client B
|
|
calling client A)
|
|
|
|
To prevent deadlock in this case, DCOP has a call tracing mechanism that
|
|
detects circular calls. When it detects an incoming circular call that
|
|
would otherwise be queued and as a result cause deadlock, it will handle
|
|
the incoming call immediately instead of queueing it. This means that the
|
|
incoming call may be processed at a point in the code where an outgoing
|
|
DCOP call is made. An application should be aware of this kind of
|
|
reentrancy. A special case of this is when a DCOP client makes a call
|
|
to itself, such calls are always handled directly.
|
|
|
|
Call tracing works by appending a key to each outgoing call. When a client
|
|
receives an incoming call while waiting for a response on an outgoing call,
|
|
it will check if the key of the incoming call is equal to the key used for
|
|
the last outgoing call. If the keys are equal a circular call has been
|
|
detected.
|
|
|
|
The key used by clients is 0 if they have not yet received any key. In this
|
|
case the server will send them back a unique key that they should use in
|
|
further calls. If a client makes an outgoing call in response to an incoming
|
|
call it will use the key of the incoming call for the outgoing call instead
|
|
of the key that was received from the server.
|
|
|
|
A key value of 1 has a special meaning and is used for non-call messages
|
|
such as DCOPSend, DCOPReplyFailed and DCOP signals.
|
|
|
|
A key value of 2 has a special meaning and is used for priority calls.
|
|
When a dcop clien is in priority call mode, it will only handle incoming
|
|
calls that have a key value of 2.
|
|
|
|
NOTE: If client A and client B would call each other simultaneously there
|
|
is still a risk of deadlock because both calls would have unique keys and
|
|
both clients would decide to queue the incoming call until they receive
|
|
a response on their outgoing call.
|
|
|
|
\section dcop_signals DCOP Signals:
|
|
|
|
Sometimes a component wants to send notifications via DCOP to other
|
|
components but does not know which components will be interested in these
|
|
notifications. One could use a broadcast in such a case but this is a very
|
|
crude method. For a more sophisticated method DCOP signals have been invented.
|
|
|
|
DCOP signals are very similair to Qt signals, there are some differences
|
|
though. A DCOP signal can be connected to a DCOP function. Whenever the DCOP
|
|
signal gets emitted, the DCOP functions to which the signal is connected are
|
|
being called. DCOP signals are, just like Qt signals, one way. They do not
|
|
provide a return value. For declaration of dcop signals, the keyword
|
|
\p k_dcop_signals is provided. A declaration looks like this:
|
|
|
|
\code
|
|
class Example : virtual public DCOPClient
|
|
{
|
|
K_DCOP
|
|
|
|
k_dcop:
|
|
// some ordinary dcop methods here
|
|
...
|
|
k_dcop_signals:
|
|
// our dcop signal
|
|
void clientDied(pid_t pid);
|
|
...
|
|
}
|
|
\endcode
|
|
|
|
A DCOP signal originates from a DCOP Object/DCOP Client combination (sender).
|
|
It can be connected to a function of another DCOP Object/DCOP Client
|
|
combination (receiver).
|
|
|
|
\note There are two major differences between connections of Qt signals and
|
|
connections of DCOP signals. In DCOP, unlike Qt, a signal connections can
|
|
have an anonymous sender and, unlike Qt, a DCOP signal connection can be
|
|
non-volatile.
|
|
|
|
With DCOP one can connect a signal without specifying the sending DCOP Object
|
|
or DCOP Client. In that case signals from any DCOP Object and/or DCOP Client
|
|
will be delivered. This allows the specification of certain events without
|
|
tying oneself to a certain object that implementes the events.
|
|
|
|
Another DCOP feature are so called non-volatile connections. With Qt signal
|
|
connections, the connection gets deleted when either sender or receiver of
|
|
the signal gets deleted. A volatile DCOP signal connection will behave the
|
|
same. However, a non-volatile DCOP signal connection will not get deleted
|
|
when the sending object gets deleted. Once a new object gets created with
|
|
the same name as the original sending object, the connection will be restored.
|
|
There is no difference between the two when the receiving object gets deleted,
|
|
in that case the signal connection will always be deleted.
|
|
|
|
A receiver can create a non-volatile connection while the sender doesn't (yet)
|
|
exist. An anonymous DCOP connection should always be non-volatile.
|
|
|
|
The following example shows how TDELauncher emits a signal whenever it notices
|
|
that an application that was started via TDELauncher terminates:
|
|
|
|
\code
|
|
QByteArray params;
|
|
QDataStream stream(params, IO_WriteOnly);
|
|
stream << pid;
|
|
kapp->dcopClient()->emitDCOPSignal("clientDied(pid_t)", params);
|
|
\endcode
|
|
|
|
The task manager of the Trinity panel connects to this signal. It uses an
|
|
anonymous connection (it doesn't require that the signal is being emitted
|
|
by TDELauncher) that is non-volatile:
|
|
|
|
\code
|
|
connectDCOPSignal(0, 0, "clientDied(pid_t)", "clientDied(pid_t)", false);
|
|
\endcode
|
|
|
|
It connects the clientDied(pid_t) signal to its own clientDied(pid_t) DCOP
|
|
function. In this case the signal and the function to call have the same name.
|
|
This isn't needed as long as the arguments of both signal and receiving function
|
|
match. The receiving function may ignore one or more of the trailing arguments
|
|
of the signal. E.g. it is allowed to connect the clientDied(pid_t) signal to
|
|
a clientDied(void) DCOP function.
|
|
|
|
|
|
\section conclusion Conclusion:
|
|
|
|
Hopefully this document will get you well on your way into the world of
|
|
inter-process communication with Trinity! Please direct all comments and/or
|
|
suggestions to the Trinity Core Developers List \<kde-core-devel@kde.org\>.
|
|
|
|
*/
|