Dcop

Let DCOP Tell You the Signature

A DCOP call failed today even though the method name was correct. The method name, unfortunately, was only part of the name that mattered. I had sent an int; the exported function expected an unsigned value.

Before changing code, I ask the running object what it exposes:

dcop myapp MainIface

The output includes complete function signatures. In this case it shows void setPage(unsigned int). I then call it from the command line with a small test argument:

dcop myapp MainIface setPage 3

DCOP identifies methods by their signatures and marshals known Qt types through QDataStream. The sending and receiving sides must therefore agree on argument types, order, and return type. A visually similar C++ declaration is not necessarily the same wire contract.

For custom values, both programs need compatible streaming operators and type registration. I avoid custom structures in small public interfaces when a QString, QCString, or simple list expresses the operation clearly. Fewer private types also make the command-line client useful.

When writing the C++ caller, I keep the request and reply types visible next to each other:

QByteArray data;
QDataStream args(data, IO_WriteOnly);
unsigned int page = 3;
args << page;

QCString replyType;
QByteArray replyData;
if (!client->call(app, "MainIface", "setPage(unsigned int)",
                  data, replyType, replyData)) {
    kdWarning() << "DCOP setPage call failed" << endl;
}

For a function with a return value, I verify replyType before extracting from replyData. Deserializing a reply under the wrong assumption merely converts a useful interface error into strange application state.

The application and object names should not be copied from a single run if multiple instances are possible. I list registered clients and decide whether the program is meant to address one instance, every instance, or the instance owning a particular document. A suffix added to keep names unique is normal, not a DCOP server losing its arithmetic.

I also preserve failures in scripts. The command-line client returns useful status, and a shell script should check it rather than carrying on after the target application has exited. Interprocess calls cross a lifetime boundary; yesterday’s object name is not a reservation for today.

I keep the exported interface narrow and test it independently of the window. If the command works but a toolbar action does not, DCOP has been acquitted and I can inspect the connection instead.

I let dcop provide the authoritative spelling instead of copying it from memory. Memory is fast, local, and apparently not type-safe.

A Small DCOP Command Is a Good Test

I needed to tell a running application to refresh a document, and my first instinct was to add a socket and invent a tiny protocol. Then I remembered that a KDE session already has DCOP, including discovery, calls, and argument marshalling. It seemed wasteful to build a worse version before lunch.

The dcop command is the quickest way to see what is available:

dcop
dcop konqueror
dcop konqueror KonquerorIface

The first command lists registered applications. Supplying an application lists its objects and interfaces; adding an object shows callable functions. This is useful beyond scripting. It tells me whether the object registered under the name I expected and whether the signature exported by the program matches my mental version of it.

For a simple application object, I derive from DCOPObject and name the interface:

class RefreshIface : virtual public DCOPObject
{
    K_DCOP
    k_dcop:
        virtual void refresh(const QString &path) = 0;
};

The implementation registers through the application’s DCOPClient. Calls are sent through the session’s DCOP server, which locates the target client and carries the serialized arguments. The caller does not need the target’s process identifier, and the receiver can expose a small interface rather than its internal objects.

I keep DCOP methods coarse. Sending refresh(QString) is reasonable; reproducing every setter on a widget is not. Once the interface mirrors the user interface, outside programs depend on details I will want to change.

There is also a choice between a call that waits for a reply and one that merely sends a message. I use a synchronous call only when the result is genuinely needed. Blocking while another desktop process opens a file or asks a question can make both programs feel frozen.

Application names need care as well. A second instance may register with a numbered name rather than replacing the first one. If the operation concerns a particular document, guessing the first process in the list is unsafe. I either arrange single-instance behavior deliberately or discover the intended application and object from information I already have. DCOP removes the need for process identifiers; it does not remove the need to choose the right process.

Failures should remain visible. A call can fail because the application exited, the object disappeared, or the signature changed. I report that to the caller instead of treating a missing reply as an empty successful result.

My rule now is to prove the exchange with the command-line client first. If dcop cannot see or call the object, another page of C++ is unlikely to improve matters. This is one of those rare debugging techniques that removes code, which makes it suspiciously pleasant.