DCOPRef and dynamic stubs ( 3.1? )

Matthias Ettrich ettrich at trolltech.com
Sun Sep 8 15:50:57 BST 2002


recently when chatting with Danimo we found out that calling functions via 
DCOP isn't quite as easy it should be. Originally we thought programmers 
would make more use of dcopidl2cpp and use the generated stubs. 

Stubs, however, are a bit of a hassle if all you want to do is call one or two 
functions on a remote object. In those case most KDE developer prefer to 
simply write the datastream-code themselves and use DCOPClient::call() or 
DCOPClient::send() directly.

Interestingly enough we already have an encapsulation for a remote DCOP 
object, the class DCOPRef. But until now, DCOPRef is only used to pass 
(especially to return) remote objects.

I was playing with the idea to extend DCOPRef to allow using if for calls and 

Here's some example code from keyecandypage.cpp in kpersonalizer:

	QByteArray replydata;
	QByteArray data;
	QCString replytype;
	kapp->dcopClient()->call( "kicker", "Panel", "panelSize()",data, 
              replytype, replydata);
	QDataStream stream( replydata, IO_ReadOnly );
	stream >> panelsize;

That much code just to get the panel size from kicker?

Or the code in ksmserver that sets the launch environment on the launcher:

       QByteArray params;
       QDataStream stream(params, IO_WriteOnly);
       stream << name << value;
       kapp->dcopClient()->send(launcher, launcher, 
           "setLaunchEnv(QCString,QCString)", params);

That much code to just pass two cstrings?

With my extended DCOPRef, we could write both things like this:

   DCOPRef panel( "kicker, "Panel" );
   panelsize = panel.call( "panelSize" );

   DCOPRef( launcher, launcher ).send( "setLaunchEnv", name, value );
Isn't that cool? And as opposed to the longer original versions, the new ones 
actually _do_ error checking. It's the wonders of modern template programming 
that make it possible :)

To handle the reply, DCOPRef returns a DCOPReply object which stores both the 
returned type and the bytearray data. It has a template cast operator like 

    template<class T>
    operator T() {
	T t;
	if ( typeCheck( dcopTypeName(t) ) ) {
	    QDataStream reply( data, IO_ReadOnly );
	    reply >> t;
	} else {
	return t;

Thanks to the global overloaded functions dcopTypeName() and dcopTypeInit, 
that both also provide dummy template implementations, not only can I check 
the conversion at runtime, but also initilize return values of type int, 
float, doube, etc. to 0.

Below you'll find the updated DCOPRef documentation.

My question is: to commit or not to commit? In case I commit, I'll also update 
the DCOP overview documentation.



 * A DCOPRef(erence) encapsulates a remote DCOP object as a triple
 * <app,obj,type> where type is optional. It allows for calling and
 * passing DCOP objects.
 * A DCOP reference makes it possible to return references to other DCOP
 * objects in a DCOP interface, for example in the method
 * giveMeAnotherObject() in an interface like this:
 * <pre>
 *	class Example : public DCOPObject
 *	{
 *	   K_DCOP
 *	...
 *	k_dcop:
 *	   DCOPRef giveMeAnotherObject();
 *	   int doSomething( QString, float, bool );
 *	   ASYNC pingMe( QCString message );
 *	   UserType userFunction( UserType );
 *	};
 * </pre>
 * In addition, the reference can operate as a comfortable generic
 * stub to call remote DCOP objects in cases where no DCOPStub is
 * available. The advantage of using DCOPRef instead of the low-level
 * functions DCOPClient::call() and DCOPClient::send() are the nicer
 * syntax and the implicit runtime error checking.
 * Say you want to call the method "doSomething" from the above
 * interface on an object called "example" that lives in application
 * "foo". Using DCOPRef, you would write
 * <pre>
 *	DCOPRef example( "foo", example" );
 *	int result = example.call( "doSomething", "Hello World", (float)2.5, true 
 * </pre>
 * If it is important for you to know whether the call succeeded or
 * not, you can use the slightly more elaborate pattern:
 * <pre>
 *	DCOPRef example( "foo", example" );
 *	DCOPReply reply = example.call( "doSomething", "Hello World", (float)2.5, 
true );
 *	if ( reply.isValid() ) {
 *	    int result = reply;
 *	    // ...
 *	}
 * </pre>
 * For curiosity, here is how you would achieve the exactly same
 * functionality by using DCOPClient::call() directly:
 * <pre>
 *    QByteArray data, replyData;
 *    QCString replyType;
 *    QDataStream arg( data, IO_WriteOnly );
 *    arg << QString("hello world" ), (float) 2.5 << true;
 *    if ( DCOPClient::mainClient()->call( app, obj,
 *			     "doSomething(QString,float,bool)",
 *			     data, replyType, replyData ) ) {
 *	if ( replyType == "int" ) {
 *	    int result;
 *	    QDataStream reply( replyData, IO_ReadOnly );
 *	    reply >> result;
 *	    // ...
 *	}
 *    }
 * </pre>
 * As you might see from the code snit bit, the DCOPRef has to "guess"
 * the names of the datatypes of the arguments to construct a dcop
 * call. This is done through global inline overloads of the
 * dcopTypeName function, for example
 * <pre>
 *	inline const char* dcopTypeName( const QString& ) { return "QString"; }
 * </pre>
 * If you use custom data types that do support QDataStream but have
 * no corrsponding dcopTypeName overload, you can either provide such
 * an overload or specifify the type name explicitely in both your
 * call and when you retrieve the reply. Example:
 * <pre>
 *	UserType userType;
 *	DCOPReply reply = example.call( "userFunction(UserType)", userType );
 *	reply.cast( "UserType", userType );
 * </pre>
 * The function send() works very similar to call(), only that it
 * returns a simple bool on whether the signal could be sent or not:
 * <pre>
 *	if ( example.pingMe( "message" ) == false )
 *	   qWarning("could not ping example" );
 * </pre>
 * A DCOP reference operates on DCOPClient::mainClient(), unless you
 * explicitely specify another client with setDCOPClient().

More information about the kde-core-devel mailing list