diff options
Diffstat (limited to 'doc/artsbuilder/mcop.docbook')
-rw-r--r-- | doc/artsbuilder/mcop.docbook | 2274 |
1 files changed, 2274 insertions, 0 deletions
diff --git a/doc/artsbuilder/mcop.docbook b/doc/artsbuilder/mcop.docbook new file mode 100644 index 00000000..86aa03b5 --- /dev/null +++ b/doc/artsbuilder/mcop.docbook @@ -0,0 +1,2274 @@ +<!-- <?xml version="1.0" ?> +<!DOCTYPE chapter PUBLIC "-//KDE//DTD DocBook XML V4.2-Based Variant V1.1//EN" "dtd/kdex.dtd"> +To validate or process this file as a standalone document, uncomment +this prolog. Be sure to comment it out again when you are done --> + +<chapter id="mcop"> +<title>&MCOP;: Object Model and Streaming</title> + +<sect1 id="mcop-overview"> + +<title>Overview</title> + +<para> +&MCOP; is the standard &arts; uses for: +</para> + +<itemizedlist> +<listitem> +<para> +Communication between objects. +</para> +</listitem> + +<listitem> +<para> +Network transparency. +</para> +</listitem> + +<listitem> +<para> +Describing object interfaces. +</para> +</listitem> + +<listitem> +<para> +Language independancy. +</para> +</listitem> +</itemizedlist> + +<para> +One major aspect of &MCOP; is the <emphasis>interface description +language</emphasis>, &IDL;, in which many of the &arts; interfaces and +<acronym>API</acronym>s are defined in a language independent way. +</para> + +<para> +To use &IDL; interfaces from C++, is compiled by the &IDL; +compiler into C++ code. When you implement an interface, you derive from +the skeleton class the &IDL; compiler has generated. When you use an +interface, you do so using a wrapper. This way, &MCOP; can use a +protocol if the object you are talking to is not local - you get network +transparency. +</para> + +<para> +This chapter is supposed to describe the basic features of the object +model that results from the use of &MCOP;, the protocol, how do use +&MCOP; in C++ (language binding), and so on. +</para> + +</sect1> + +<sect1 id="interfaces"> + +<title>Interfaces and &IDL;</title> + +<para> +Many of the services provided by &arts;, such as modules and the sound +server, are defined in terms of <acronym>interfaces</acronym>. +Interfaces are specified in a programming language independent format: +&IDL;. +</para> + +<para> +This allows many of the implementation details such as the format of +multimedia data streams, network transparency, and programming language +dependencies, to be hidden from the specification for the interface. A +tool, &mcopidl;, translates the interface +definition into a specific programming language (currently only C++ is +supported). +</para> + +<para> +The tool generates a skeleton class with all of the boilerplate code and +base functionality. You derive from that class to implement the features +you want. +</para> + +<para> +The &IDL; used by &arts; is similar to that used by +<acronym>CORBA</acronym> and <acronym>DCOM</acronym>. +</para> + +<para> +&IDL; files can contain: +</para> + +<itemizedlist> +<listitem> +<para> +C-style #include directives for other &IDL; files. +</para> +</listitem> + +<listitem> +<para> +Definitions of enumerated and struct types, as in C/C++. +</para> +</listitem> + +<listitem> +<para> +Definitions of interfaces. +</para> +</listitem> +</itemizedlist> + +<para> +In &IDL;, interfaces are defined much like a C++ class or C struct, +albeit with some restrictions. Like C++, interfaces can subclass other +interfaces using inheritance. Interface definitions can include three +things: streams, attributes, and methods. +</para> + +<sect2 id="streams"> + +<title>Streams</title> + +<para> +Streams define multimedia data, one of the most important components of +a module. Streams are defined in the following format: +</para> + +<para> +[ async ] in|out [ multi ] <replaceable>type</replaceable> stream <replaceable>name</replaceable> [ , <replaceable>name</replaceable> ] ; +</para> + +<para> +Streams have a defined direction in reference to the module, as +indicated by the required qualifiers in or out. The type argument +defines the type of data, which can be any of the types described later +for attributes (not all are currently supported). Many modules use the +stream type audio, which is an alias for float since that is the +internal data format used for audio stream. Multiple streams of the same +type can defined in the same definition uisng comma separated names. +</para> + +<para> +Streams are by default synchronous, which means they are continuous +flows of data at a constant rate, such as <acronym>PCM</acronym> +audio. The async qualifier specifies an asynchronous stream, which is +used for non-continuous data flows. The most common example of an async +stream is &MIDI; messages. +</para> + +<para> +The multi keyword, only valid for input streams, indicates that the +interface supports a variable number of inputs. This is useful for +implementing devices such as mixers that can accept any number of input +streams. +</para> + +</sect2> +<sect2 id="attributes"> + +<title>Attributes</title> + +<para> +Attributes are data associated with an instance of an interface. They +are declared like member variables in C++, and can can use any of the +primitive types boolean, byte, long, string, or float. You can also use +user-defined struct or enum types as well as variable sized sequences +using the syntax sequence<type>. Attributes can optionally be +marked readonly. +</para> + +</sect2> +<sect2 id="methods"> + +<title>Methods</title> + +<para> +As in C++, methods can be defined in interfaces. The method parameters +are restricted to the same types as attributes. The keyword oneway +indicates a method which returns immediately and is executed +asynchronously. +</para> + +</sect2> + +<sect2 id="standardinterfaces"> + +<title>Standard Interfaces</title> + +<para> +Several standard module interfaces are already defined for you in +&arts;, such as <interfacename>StereoEffect</interfacename>, and +<interfacename>SimpleSoundServer</interfacename>. +</para> + +</sect2> + +<sect2 id="example"> +<title>Example</title> + +<para> +A simple example of a module taken from &arts; is the constant delay +module, found in the file +<filename>kdemultimedia/arts/modules/artsmodules.idl</filename>. The +interface definition is listed below. +</para> + +<programlisting> +interface Synth_CDELAY : SynthModule { + attribute float time; + in audio stream invalue; + out audio stream outvalue; +}; +</programlisting> + +<para> +This modules inherits from +<interfacename>SynthModule</interfacename>. That interface, defined in +<filename>artsflow.idl</filename>, defines the standard methods +implemented in all music synthesizer modules. +</para> + +<para> +The CDELAY effect delays a stereo audio stream by the time value +specified as a floating point parameter. The interface definition has an +attribute of type float to store the delay value. It defines two input +audio streams and two output audio streams (typical of stereo +effects). No methods are required other than those it inherits. +</para> + +</sect2> + +</sect1> + +<sect1 id="more-about-streams"> +<title>More About Streams</title> + +<para> +This section covers some additional topics related to streams. +</para> + +<sect2 id="stream-types"> +<title>Stream Types</title> + +<para> +There are various requirements for how a module can do streaming. To +illustrate this, consider these examples: +</para> + +<itemizedlist> +<listitem> +<para> +Scaling a signal by a factor of two. +</para> +</listitem> + +<listitem> +<para> +Performing sample frequency conversion. +</para> +</listitem> + +<listitem> +<para> +Decompressing a run-length encoded signal. +</para> +</listitem> + +<listitem> +<para> +Reading &MIDI; events from <filename +class="devicefile">/dev/midi00</filename> and inserting them into a +stream. +</para> +</listitem> +</itemizedlist> + +<para> +The first case is the simplest: upon receiving 200 samples of input the +module produces 200 samples of output. It only produces output when it +gets input. +</para> + +<para> +The second case produces different numbers of output samples when given +200 input samples. It depends what conversion is performed, but the +number is known in advance. +</para> + +<para> +The third case is even worse. From the outset you cannot even guess how +much data 200 input bytes will generate (probably a lot more than 200 +bytes, but...). +</para> + +<para> +The last case is a module which becomes active by itself, and sometimes +produces data. +</para> + +<para> +In &arts;s-0.3.4, only streams of the first type were handled, and most +things worked nicely. This is probably what you need most when writing +modules that process audio. The problem with the other, more complex +types of streaming, is that they are hard to program, and that you don't +need the features most of the time. That is why we do this with two +different stream types: synchronous and asynchronous. +</para> + +<para> +Synchronous streams have these characteristics: +</para> + +<itemizedlist> +<listitem> +<para> +Modules must be able to calculate data of any length, given enough +input. +</para> +</listitem> + +<listitem> +<para> +All streams have the same sampling rate. +</para> +</listitem> + +<listitem> +<para> +The <function>calculateBlock()</function> function will be called when +enough data is available, and the module can rely on the pointers +pointing to data. +</para> +</listitem> + +<listitem> +<para> +There is no allocation and deallocation to be done. +</para> +</listitem> +</itemizedlist> + +<para> +Asynchronous streams, on the other hand, have this behavior: +</para> + +<itemizedlist> +<listitem> +<para> +Modules may produce data sometimes, or with varying sampling rate, or +only if they have input from some filed descriptor. They are not bound by +the rule <quote>must be able to satisfy requests of any size</quote>. +</para> +</listitem> + +<listitem> +<para> +Asynchronous streams of a module may have entirely different sampling +rates. +</para> +</listitem> + +<listitem> +<para> +Outgoing streams: there are explicit functions to allocate packets, to +send packets - and an optional polling mechanism that will tell you when +you should create some more data. +</para> +</listitem> + +<listitem> +<para> +Incoming streams: you get a call when you receive a new packet - you +have to say when you are through with processing all data of that +packet, which must not happen at once (you can say that anytime later, +and if everybody has processed a packet, it will be freed/reused) +</para> +</listitem> +</itemizedlist> + +<para> +When you declare streams, you use the keyword <quote>async</quote> to +indicate you want to make an asynchronous stream. So, for instance, +assume you want to convert an asynchronous stream of bytes into a +synchronous stream of samples. Your interface could look like this: +</para> + +<programlisting> +interface ByteStreamToAudio : SynthModule { + async in byte stream indata; // the asynchronous input sample stream + + out audio stream left,right; // the synchronous output sample streams +}; +</programlisting> + +</sect2> + +<sect2 id="async-streams"> +<title>Using Asynchronous Streams</title> + +<para> +Suppose you decided to write a module to produce sound +asynchronously. Its interface could look like this: +</para> + +<programlisting> +interface SomeModule : SynthModule +{ + async out byte stream outdata; +}; +</programlisting> + +<para> +How do you send the data? The first method is called <quote>push +delivery</quote>. With asynchronous streams you send the data as +packets. That means you send individual packets with bytes as in the +above example. The actual process is: allocate a packet, fill it, send +it. +</para> + +<para> +Here it is in terms of code. First we allocate a packet: +</para> + +<programlisting> +DataPacket<mcopbyte> *packet = outdata.allocPacket(100); +</programlisting> + +<para> +The we fill it: +</para> + +<programlisting> +// cast so that fgets is happy that it has a (char *) pointer +char *data = (char *)packet->contents; + +// as you can see, you can shrink the packet size after allocation +// if you like +if(fgets(data,100,stdin)) + packet->size = strlen(data); +else + packet->size = 0; +</programlisting> + +<para> +Now we send it: +</para> + +<programlisting> +packet->send(); +</programlisting> + +<para> +This is quite simple, but if we want to send packets exactly as fast as +the receiver can process them, we need another approach, the <quote>pull +delivery</quote> method. You ask to send packets as fast as the receiver +is ready to process them. You start with a certain amount of packets you +send. As the receiver processes one packet after another, you start +refilling them with fresh data, and send them again. +</para> + +<para> +You start that by calling setPull. For example: +</para> + +<programlisting> +outdata.setPull(8, 1024); +</programlisting> + +<para> +This means that you want to send packets over outdata. You want to start +sending 8 packets at once, and as the receiver processes some of them, +you want to refill them. +</para> + +<para> +Then, you need to implement a method which fills the packets, which could +look like this: +</para> + +<programlisting> +void request_outdata(DataPacket<mcopbyte> *packet) +{ + packet->size = 1024; // shouldn't be more than 1024 + for(int i = 0;i < 1024; i++) + packet->contents[i] = (mcopbyte)'A'; + packet->send(); +} +</programlisting> + +<para> +Thats it. When you don't have any data any more, you can start sending +packets with zero size, which will stop the pulling. +</para> + +<para> +Note that it is essential to give the method the exact name +<methodname>request_<replaceable>streamname</replaceable></methodname>. +</para> + +<para> +We just discussed sending data. Receiving data is much much +simpler. Suppose you have a simple ToLower filter, which simply converts +all letters in lowercase: +</para> + +<programlisting> +interface ToLower { + async in byte stream indata; + async out byte stream outdata; +}; +</programlisting> + +<para> +This is really simple to implement; here is the whole implementation: +</para> + +<programlisting> +class ToLower_impl : public ToLower_skel { +public: + void process_indata(DataPacket<mcopbyte> *inpacket) + { + DataPacket<mcopbyte> *outpacket = outdata.allocPacket(inpacket->size); + + // convert to lowercase letters + char *instring = (char *)inpacket->contents; + char *outstring = (char *)outpacket->contents; + + for(int i=0;i<inpacket->size;i++) + outstring[i] = tolower(instring[i]); + + inpacket->processed(); + outpacket->send(); + } +}; + +REGISTER_IMPLEMENTATION(ToLower_impl); +</programlisting> + +<para> +Again, it is essential to name the method +<methodname>process_<replaceable>streamname</replaceable></methodname>. +</para> + +<para> +As you see, for each arriving packet you get a call for a function (the +<function>process_indata</function> call in our case). You need to call +the <methodname>processed()</methodname> method of a packet to indicate +you have processed it. +</para> + +<para> +Here is an implementation tip: if processing takes longer (&ie; if you +need to wait for soundcard output or something like that), don't call +processed immediately, but store the whole data packet and call +processed only as soon as you really processed that packet. That way, +senders have a chance to know how long it really takes to do your work. +</para> + +<para> +As synchronization isn't so nice with asynchronous streams, you should +use synchronous streams wherever possible, and asynchronous streams only +when necessary. +</para> + +</sect2> + +<sect2 id="default-streams"> +<title>Default Streams</title> + +<para> +Suppose you have 2 objects, for example an AudioProducer and an +AudioConsumer. The AudioProducer has an output stream and AudioConsumer +has an input one. Each time you want to connect them, you will use those +2 streams. The first use of defaulting is to enable you to make the +connection without specifying the ports in that case. +</para> + +<para> +Now suppose the teo objects above can handle stereo, and each have a +<quote>left</quote> and <quote>right</quote> port. You'd still like to +connect them as easily as before. But how can the connecting system +know which output port to connect to which input port? It has no way to +correctly map the streams. Defaulting is then used to specify several +streams, with an order. Thus, when you connect an object with 2 default +output streams to another one with 2 default input streams, you don't +have to specify the ports, and the mapping will be done correctly. +</para> + +<para> +Of course, this is not limited to stereo. Any number of streams can be +made default if needed, and the connect function will check that the +number of defaults for 2 object match (in the required direction) if you +don't specify the ports to use. +</para> + +<para> +The syntax is as follows: in the &IDL;, you can use the default keyword +in the stream declaration, or on a single line. For example: +</para> + +<programlisting> +interface TwoToOneMixer { + default in audio stream input1, input2; + out audio stream output; +}; +</programlisting> + +<para> +In this example, the object will expect its two input ports to be +connected by default. The order is the one specified on the default +line, so an object like this one: +</para> + +<programlisting> +interface DualNoiseGenerator { + out audio stream bzzt, couic; + default couic, bzzt; +}; +</programlisting> + +<para> +Will make connections from <quote>couic</quote> to +<quote>input1</quote>, and <quote>bzzt</quote> to <quote>input2</quote> +automatically. Note that since there is only one output for the mixer, +it will be made default in this case (see below). The syntax used in the +noise generator is useful to declare a different order than the +declaration, or selecting only a few ports as default. The directions of +the ports on this line will be looked up by &mcopidl;, so don't specify +them. You can even mix input and output ports in such a line, only the +order is important. +</para> + +<para> +There are some rules that are followed when using inheritance: +</para> + +<itemizedlist> +<listitem> +<para> +If a default list is specified in the &IDL;, then use +it. Parent ports can be put in this list as well, whether they were +default in the parent or not. +</para> +</listitem> + +<listitem> +<para> +Otherwise, inherit parent's defaults. Ordering is parent1 default1, +parent1 default2..., parent2 default1... If there is a common ancestor +using 2 parent branches, a <quote>virtual public</quote>-like merging is +done at that default's first occurrence in the list. +</para> +</listitem> + +<listitem> +<para> +If there is still no default and a single stream in a +direction, use it as default for that direction. +</para> +</listitem> +</itemizedlist> + +</sect2> + +</sect1> +<sect1 id="attribute-change-notify"> +<title>Attribute change notifications</title> + +<!-- TODO: This should be embedded better into the context - I mean: the + context should be written ;-). --> + +<para> +Attribute change notifications are a way to know when an attribute +changed. They are a bit comparable with &Qt;'s or Gtk's signals and +slots. For instance, if you have a &GUI; element, a slider, which +configures a number between 0 and 100, you will usually have an object +that does something with that number (for instance, it might be +controlling the volume of some audio signal). So you would like that +whenever the slider is moved, the object which scales the volume gets +notified. A connection between a sender and a receiver. +</para> + +<para> +&MCOP; deals with that by being able to providing notifications when +attributes change. Whatever is declared as <quote>attribute</quote> in +the &IDL;, can emit such change notifications, and should do so, +whenever it is modified. Whatever is declared as +<quote>attribute</quote> can also receive such change notifications. So +for instance if you had two &IDL; interfaces, like these: +</para> + +<programlisting> + interface Slider { + attribute long min,max; + attribute long position; + }; + interface VolumeControl : Arts::StereoEffect { + attribute long volume; // 0..100 + }; +</programlisting> + +<para> +You can connect them using change notifications. It works using the +normal flowsystem connect operation. In this case, the C++ code to +connect two objects would look like this: +</para> + +<programlisting> +#include <connect.h> +using namespace Arts; +[...] +connect(slider,"position_changed",volumeControl,"volume"); +</programlisting> + +<para> +As you see, each attribute offers two different streams, one for sending +the change notifications, called +<function><replaceable>attributename</replaceable>_changed</function>, + +<!-- TODO - how do I markup code that is an example - you wouldn't write + attributename in the source, but the name of some attribute + + LW: I'm guessing + here, because I know how to markup QT code, but your stuff is different. + Hopefully this will give you inspiration, and we can work out later the fine + tuning if I have it wrong. The line above in the code sample, if it were qt + stuff, I would mark up this way (linebreaks for clarity of markup only, yes I + know it's incorrect!): + + <function>connect(<classname>slider</classname>, + <function><replaceable>position</replaceable>_changed</function>, + <classname>volumeControl</classname>, + <function>volume</function>);</function> + + You can use <function><replaceable>attributename</function> and even + <function><replaceable>attributename</replaceable>_changed</function>. + + If I have the above totally wrong (which is entirely possible!) Some other + elements you might find handy: + + <varname>, <type>, <returnvalue>, <constant>, <methodname> + There's also a markup guide at http://madmax.atconnex.net/kde/ that might + help, although unfortunately the programming section is still incomplete. --> + + and one for receiving change notifications, called +<function>attributename</function>. +</para> + +<para> +It is important to know that change notifications and asynchronous +streams are compatible. They are also network transparent. So you can +connect a change notification of a float attribute of a &GUI; widget has +to an asynchronous stream of a synthesis module running on another +computer. This of course also implies that change notifications are +<emphasis>not synchronous</emphasis>, this means, that after you have +sent the change notification, it may take some time until it really gets +received. +</para> + +<sect2 id="sending-change-notifications"> + +<title>Sending change notifications</title> + +<para> +When implementing objects that have attributes, you need to send change +notifications whereever an attribute changes. The code for doing this +looks like this: +</para> + +<programlisting> + void KPoti_impl::value(float newValue) + { + if(newValue != _value) + { + _value = newValue; + value_changed(newValue); // <- send change notification + } + } +</programlisting> + +<para> +It is strongly recommended to use code like this for all objects you +implement, so that change notifications can be used by other people. You +should however void sending notifications too often, so if you are doing +signal processing, it is probably the best if you keep track when you +sent your last notification, so that you don't send one with every +sample you process. +</para> + +</sect2> + +<sect2 id="change-notifications-apps"> +<title>Applications for change notifications</title> + +<para> +It will be especially useful to use change notifications in conjunction +with scopes (things that visualize audio data for instance), gui +elements, control widgets, and monitoring. Code using this is in +<filename class="directory">kdelibs/arts/tests</filename>, and in the +experimental artsgui implementation, which you can find under <filename +class="directory">kdemultimedia/arts/gui</filename>. +</para> + +<!-- TODO: can I markup links into the source code - if yes, how? --> + +<!-- LW: Linking into the source is problematic - we can't assume people are +reading this on a machine with the sources available, or that they aren't +reading it from a website. We're working on it! --> + +</sect2> +</sect1> + +<sect1 id="the-mcoprc-file"> + +<title>The <literal role="extension">.mcoprc</literal> file</title> + +<para> +The <literal role="extension">.mcoprc</literal> file (in each user's +home folder) can be used to configure &MCOP; in some ways. Currently, +the following is possible: +</para> + +<variablelist> + +<varlistentry> +<term>GlobalComm</term> +<listitem> +<para> +The name of an interface to be used for global communication. Global +communication is used to find other objects and obtain the secret +cookie. Multiple &MCOP; clients/servers that should be able to talk to +each other need to have a GlobalComm object which is able to share +information between them. Currently, the possible values are +<quote>Arts::TmpGlobalComm</quote> to communicate via <filename +class="directory">/tmp/mcop-<replaceable>username</replaceable></filename> +folder (which will only work on the local computer) and +<quote>Arts::X11GlobalComm</quote> to communicate via the root window +properties on the X11 server. +</para> +</listitem> +</varlistentry> + +<varlistentry> +<term>TraderPath</term> + +<listitem> +<para> +Specifies where to look for trader information. You can list more than +one folder here, and separate them with commas, like +</para> +</listitem> + +</varlistentry> + +<varlistentry> +<term>ExtensionPath</term> + +<listitem> +<para> +Specifies from which folders extensions (in the form of shared +libraries) are loaded. Multiple values can be specified comma separated. +</para> +</listitem> + +</varlistentry> +</variablelist> + +<para> +An example which uses all of the above is: +</para> + +<programlisting> +# $HOME/.mcoprc file +GlobalComm=Arts::X11GlobalComm + +# if you are a developer, it might be handy to add a folder in your home +# to the trader/extension path to be able to add components without +# installing them +TraderPath="/opt/kde2/lib/mcop","/home/joe/mcopdevel/mcop" +ExtensionPath="/opt/kde2/lib","/home/joe/mcopdevel/lib" +</programlisting> + +</sect1> + +<sect1 id="mcop-for-corba-users"> +<title>&MCOP; for <acronym>CORBA</acronym> Users</title> + +<para> +If you have used <acronym>CORBA</acronym> before, you will see that +&MCOP; is much the same thing. In fact, &arts; prior to version 0.4 used +<acronym>CORBA</acronym>. +</para> + +<para> +The basic idea of <acronym>CORBA</acronym> is the same: you implement +objects (components). By using the &MCOP; features, your objects are not +only available as normal classes from the same process (via standard C++ +techniques) - they also are available to remote servers +transparently. For this to work, the first thing you need to do is to +specify the interface of your objects in an &IDL; file - just like +<acronym>CORBA</acronym> &IDL;. There are only a few differences. +</para> + +<sect2 id="corba-missing"> +<title><acronym>CORBA</acronym> Features That Are Missing In +&MCOP;</title> + +<para> +In &MCOP; there are no <quote>in</quote> and <quote>out</quote> +parameters on method invocations. Parameters are always incoming, the +return code is always outgoing, which means that the interface: +</para> + +<programlisting> +// CORBA idl +interface Account { + void deposit( in long amount ); + void withdraw( in long amount ); + long balance(); +}; +</programlisting> + +<para> +is written as +</para> + +<programlisting> +// MCOP idl +interface Account { + void deposit( long amount ); + void withdraw( long amount ); + long balance(); +}; +</programlisting> + +<para> +in &MCOP;. +</para> + +<para> +There is no exception support. &MCOP; doesn't have exceptions - it uses +something else for error handling. +</para> + +<para> +There are no union types and no typedefs. I don't know if that is a real +weakness, something one would desperately need to survive. +</para> + +<para> +There is no support for passing interfaces or object references +</para> + +</sect2> + +<sect2 id="corba-different"> +<title><acronym>CORBA</acronym> Features That Are Different In +&MCOP;</title> + +<para> +You declare sequences as +<quote>sequence<replaceable>type</replaceable></quote> in &MCOP;. There +is no need for a typedef. For example, instead of: +</para> + +<programlisting> +// CORBA idl +struct Line { + long x1,y1,x2,y2; +}; +typedef sequence<Line> LineSeq; +interface Plotter { + void draw(in LineSeq lines); +}; +</programlisting> + +<para> +you would write +</para> + +<programlisting> +// MCOP idl +struct Line { + long x1,y1,x2,y2; +}; +interface Plotter { + void draw(sequence<Line> lines); +}; +</programlisting> + +</sect2> + +<sect2 id="no-in-corba"> +<title>&MCOP; Features That Are Not In <acronym>CORBA</acronym></title> + +<para> +You can declare streams, which will then be evaluated by the &arts; +framework. Streams are declared in a similar manner to attributes. For +example: +</para> + +<programlisting> +// MCOP idl +interface Synth_ADD : SynthModule { + in audio stream signal1,signal2; + out audio stream outvalue; +}; +</programlisting> + +<para> +This says that your object will accept two incoming synchronous audio +streams called signal1 and signal2. Synchronous means that these are +streams that deliver x samples per second (or other time), so that the +scheduler will guarantee to always provide you a balanced amount of +input data (⪚ 200 samples of signal1 are there and 200 samples +signal2 are there). You guarantee that if your object is called with +those 200 samples signal1 + signal2, it is able to produce exactly 200 +samples to outvalue. +</para> + +</sect2> + +<sect2 id="mcop-binding"> +<title>The &MCOP; C++ Language Binding</title> + +<para> +This differs from <acronym>CORBA</acronym> mostly: +</para> + +<itemizedlist> +<listitem> +<para> +Strings use the C++ <acronym>STL</acronym> <classname>string</classname> +class. When stored in sequences, they are stored <quote>plain</quote>, +that means they are considered to be a primitive type. Thus, they need +copying. +</para> +</listitem> + +<listitem> +<para> +longs are plain long's (expected to be 32 bit). +</para> +</listitem> + +<listitem> +<para> +Sequences use the C++ <acronym>STL</acronym> +<classname>vector</classname> class. +</para> +</listitem> + +<listitem> +<para> +Structures are all derived from the &MCOP; class +<classname>Type</classname>, and generated by the &MCOP; &IDL; +compiler. When stored in sequences, they are not stored +<quote>plain</quote> , but as pointers, as otherwise, too much copying +would occur. +</para> +</listitem> +</itemizedlist> +</sect2> + +<sect2 id="implementing-objects"> +<title>Implementing &MCOP; Objects</title> + +<para> +After having them passed through the &IDL; compiler, you need to derive +from the <classname>_skel</classname> class. For instance, consider you +have defined your interface like this: +</para> + +<programlisting> +// MCOP idl: hello.idl +interface Hello { + void hello(string s); + string concat(string s1, string s2); + long sum2(long a, long b); +}; +</programlisting> + +<para> +You pass that through the &IDL; compiler by calling +<userinput><command>mcopidl</command> +<parameter>hello.idl</parameter></userinput>, which will in turn generate +<filename>hello.cc</filename> and <filename>hello.h</filename>. To +implement it, you need to define a C++-class that inherits the skeleton: +</para> + +<programlisting> +// C++ header file - include hello.h somewhere +class Hello_impl : virtual public Hello_skel { +public: + void hello(const string& s); + string concat(const string& s1, const string& s2); + long sum2(long a, long b); +}; +</programlisting> + +<para> +Finally, you need to implement the methods as normal C++ +</para> + +<programlisting> +// C++ implementation file + +// as you see string's are passed as const string references +void Hello_impl::hello(const string& s) +{ + printf("Hello '%s'!\n",s.c_str()); +} + +// when they are a returncode they are passed as "normal" strings +string Hello_impl::concat(const string& s1, const string& s2) +{ + return s1+s2; +} + +long Hello_impl::sum2(long a, long b) +{ + return a+b; +} +</programlisting> + +<para> +Once you do that, you have an object which can communicate using &MCOP;. +Just create one (using the normal C++ facilities to create an object): +</para> + +<programlisting> + Hello_impl server; +</programlisting> + +<para> +And as soon as you give somebody the reference +</para> + +<programlisting> + string reference = server._toString(); + printf("%s\n",reference.c_str()); +</programlisting> + +<para> +and go to the &MCOP; idle loop +</para> + +<programlisting> +Dispatcher::the()->run(); +</programlisting> + +<para> +People can access the thing using +</para> + +<programlisting> +// this code can run anywhere - not necessarily in the same process +// (it may also run on a different computer/architecture) + + Hello *h = Hello::_fromString([the object reference printed above]); +</programlisting> + +<para> +and invoke methods: +</para> + +<programlisting> + if(h) + h->hello("test"); + else + printf("Access failed?\n"); +</programlisting> + +</sect2> +</sect1> + +<sect1 id="mcop-security"> +<title>&MCOP; Security Considerations</title> + +<para> +Since &MCOP; servers will listen on a <acronym>TCP</acronym> port, +potentially everybody (if you are on the Internet) may try to connect +&MCOP; services. Thus, it is important to authenticate clients. &MCOP; +uses the md5-auth protocol. +</para> + +<para> +The md5-auth protocol does the following to ensure that only selected +(trusted) clients may connect to a server: +</para> + +<itemizedlist> +<listitem> +<para> +It assumes you can give every client a secret cookie. +</para> +</listitem> + +<listitem> +<para> +Every time a client connects, it verifies that this client knows that +secret cookie, without actually transferring it (not even in a form that +somebody listening to the network traffic could find it out). +</para> +</listitem> + +</itemizedlist> + +<para> +To give each client the secret cookie, &MCOP; will (normally) put it in +the <filename class="directory">mcop</filename> folder (under +<filename +class="directory">/tmp/mcop-<envar>USER</envar>/secret-cookie</filename>). Of +course, you can copy it to other computers. However, if you do so, use a +secure transfer mechanism, such as <command>scp</command> (from +<application>ssh</application>). +</para> + +<para> +The authentication of clients uses the following steps: +</para> + +<procedure> +<step> +<para> +[SERVER] generate a new (random) cookie R +</para> +</step> + +<step> +<para> +[SERVER] send it to the client +</para> +</step> + +<step> +<para> +[CLIENT] read the "secret cookie" S from a file +</para> +</step> + +<step> +<para> +[CLIENT] mangle the cookies R and S to a mangled cookie M using the MD5 +algorithm +</para> +</step> + +<step> +<para> +[CLIENT] send M to the server +</para> +</step> + +<step> +<para> +[SERVER] verify that mangling R and S gives just the +same thing as the cookie M received from the client. If yes, +authentication is successful. +</para> +</step> + +</procedure> + +<para> +This algorithm should be secure, given that +</para> + +<orderedlist> +<listitem> +<para> +The secret cookies and random cookies are <quote>random enough</quote> + and +</para> +</listitem> + +<listitem> +<para> +The MD5 hashing algorithm doesn't allow to find out the +<quote>original text</quote>, that is the secret cookie S and the random +cookie R (which is known, anyway), from the mangled cookie M. +</para> +</listitem> +</orderedlist> + +<para> +The &MCOP; protocol will start every new connection with an +authentication process. Basically, it looks like this: +</para> + +<procedure> + +<step> +<para> +Server sends a ServerHello message, which describes +the known authentication protocols. +</para> +</step> + +<step> +<para> +Client sends a ClientHello message, which includes authentication info. +</para> +</step> + +<step> +<para> +Server sends an AuthAccept message. +</para> +</step> +</procedure> + +<para> +To see that the security actually works, we should look at how messages +are processed on unauthenticated connections: +</para> + +<itemizedlist> +<listitem> +<para> +Before the authentication succeeds, the server will not receive other +messages from the connection. Instead, if the server for instance +expects a <quote>ClientHello</quote> message, and gets an mcopInvocation +message, it will drop the connection. +</para> +</listitem> + +<listitem> +<para> +If the client doesn't send a valid &MCOP; message at all (no &MCOP; +magic in the message header) in the authentication phase, but something +else, the connection is dropped. +</para> +</listitem> + +<listitem> +<para> +If the client tries to send a very very large message (> 4096 bytes +in the authentication phase, the message size is truncated to 0 bytes, +which will cause that it isn't accepted for authentication) This is to +prevent unauthenticated clients from sending ⪚ 100 megabytes of +message, which would be received and could cause the server to run out +of memory. +</para> +</listitem> + +<listitem> +<para> +If the client sends a corrupt ClientHello message (one, for which +demarshalling fails), the connection is dropped. +</para> +</listitem> + +<listitem> +<para> +If the client send nothing at all, then a timeout should occur (to be +implemented). +</para> +</listitem> +</itemizedlist> + +</sect1> + +<sect1 id="mcop-protocol"> +<title>&MCOP; Protocol Specification</title> + +<sect2 id="mcop-protocol-intro"> +<title>Introduction</title> + +<para> +It has conceptual similarities to <acronym>CORBA</acronym>, but it is +intended to extend it in all ways that are required for real time +multimedia operations. +</para> + +<para> +It provides a multimedia object model, which can be used for both: +communication between components in one address space (one process), and +between components that are in different threads, processes or on +different hosts. +</para> + +<para> +All in all, it will be designed for extremely high performance (so +everything shall be optimized to be blazingly fast), suitable for very +communicative multimedia applications. For instance streaming videos +around is one of the applications of &MCOP;, where most +<acronym>CORBA</acronym> implementations would go down to their knees. +</para> + +<para> +The interface definitions can handle the following natively: +</para> + +<itemizedlist> +<listitem> +<para> +Continuous streams of data (such as audio data). +</para> +</listitem> + +<listitem> +<para> +Event streams of data (such as &MIDI; events). +</para> +</listitem> + +<listitem> +<para> +Real reference counting. +</para> +</listitem> +</itemizedlist> + +<para> +and the most important <acronym>CORBA</acronym> gimmicks, like +</para> + +<itemizedlist> +<listitem> +<para> +Synchronous method invocations. +</para> +</listitem> + +<listitem> +<para> +Asynchronous method invocations. +</para> +</listitem> + +<listitem> +<para> +Constructing user defined data types. +</para> +</listitem> + +<listitem> +<para> +Multiple inheritance. +</para> +</listitem> + +<listitem> +<para> +Passing object references. +</para> +</listitem> +</itemizedlist> + +</sect2> + +<sect2 id="mcop-protocol-marshalling"> +<title>The &MCOP; Message Marshalling</title> + +<para> +Design goals/ideas: +</para> + +<itemizedlist> + +<listitem> +<para> +Marshalling should be easy to implement. +</para> +</listitem> + +<listitem> +<para> +Demarshalling requires the receiver to know what type he wants to +demarshall. +</para> +</listitem> + +<listitem> +<para> +The receiver is expected to use every information - so skipping is only +in the protocol to a degree that: +</para> + +<itemizedlist> +<listitem> +<para> +If you know you are going to receive a block of bytes, you don't need to +look at each byte for an end marker. +</para> +</listitem> + +<listitem> +<para> +If you know you are going to receive a string, you don't need to read it +until the zero byte to find out it's length while demarshalling, however, +</para> +</listitem> + +<listitem> +<para> +If you know you are going to receive a sequence of strings, you need to +look at the length of each of them to find the end of the sequence, as +strings have variable length. But if you use the strings for something +useful, you'll need to do that anyway, so this is no loss. +</para> +</listitem> +</itemizedlist> + +</listitem> + +<listitem> +<para> +As little overhead as possible. +</para> +</listitem> +</itemizedlist> + +<!-- TODO: Make this a table --> + +<para> +Marshalling of the different types is show in the table below: +</para> + +<informaltable> +<tgroup cols="3"> +<thead> +<row> +<entry>Type</entry> +<entry>Marshalling Process</entry> +<entry>Result</entry> +</row> +</thead> + +<tbody> +<row> +<entry><type>void</type></entry> +<entry><type>void</type> types are marshalled by omitting them, so +nothing is written to the stream for them.</entry> +<entry></entry> +</row> + +<row> +<entry><type>long</type></entry> +<entry>is marshalled as four bytes, the most significant byte first, +so the number 10001025 (which is 0x989a81) would be marshalled +as:</entry> +<entry><literal>0x00 0x98 0x9a 0x81</literal></entry> +</row> + +<row> +<entry><type>enums</type></entry> +<entry><para>are marshalled like <type>long</type>s</para></entry> +<entry></entry> +</row> + +<row> +<entry><type>byte</type></entry> +<entry><para>is marshalled as a single byte, so the byte 0x42 would be +marshalled as:</para></entry> +<entry><literal>0x42</literal></entry> +</row> + +<row> +<entry><type>string</type></entry> +<entry><para>is marshalled as a <type>long</type>, containing the length +of the following string, and then the sequence of characters strings +must end with one zero byte (which is included in the length +counting).</para> +<important> +<para>include the trailing 0 byte in length counting!</para> +</important> +<para><quote>hello</quote> would be marshalled as:</para></entry> +<entry><literal>0x00 0x00 0x00 0x06 0x68 0x65 0x6c 0x6c 0x6f 0x00</literal></entry> +</row> + +<row> +<entry><type>boolean</type></entry> +<entry><para>is marshalled as a byte, containing 0 if +<returnvalue>false</returnvalue> or 1 if +<returnvalue>true</returnvalue>, so the boolean value +<returnvalue>true</returnvalue> is marshalled as:</para></entry> +<entry><literal>0x01</literal></entry> +</row> + +<row> +<entry><type>float</type></entry> +<entry><para>is marshalled after the four byte IEEE754 representation - +detailed docs how IEEE works are here: <ulink +url="http://twister.ou.edu/workshop.docs/common-tools/numerical_comp_guide/ncg_math.doc.html">http://twister.ou.edu/workshop.docs/common-tools/numerical_comp_guide/ncg_math.doc.html</ulink> +and here: <ulink +url="http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html">http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html</ulink>. +So, the value 2.15 would be marshalled as:</para></entry> +<entry><literal>0x9a 0x99 0x09 0x40</literal></entry> +</row> + +<row> +<entry><type>struct</type></entry> +<entry><para>A structure is marshalled by marshalling it's +contents. There are no additional prefixes or suffixes required, so the +structure +</para> +<programlisting> +struct test { + string name; // which is "hello" + long value; // which is 10001025 (0x989a81) +}; +</programlisting> +<para>would be marshalled as</para></entry> +<entry> +<literallayout> +0x00 0x00 0x00 0x06 0x68 0x65 0x6c 0x6c +0x6f 0x00 0x00 0x98 0x9a 0x81 +</literallayout></entry> +</row> + +<row> +<entry><type>sequence</type></entry> +<entry><para>a sequence is marshalled by listing the number of elements +that follow, and then marshalling the elements one by one.</para> +<para>So a sequence of 3 longs a, with a[0] = 0x12345678, a[1] = 0x01 +and a[2] = 0x42 would be marshalled as:</para></entry> +<entry> +<literallayout> +0x00 0x00 0x00 0x03 0x12 0x34 0x56 0x78 +0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x42 +</literallayout> +</entry> +</row> +</tbody> +</tgroup> +</informaltable> + +<para> +If you need to refer to a type, all primitive types are referred by the +names given above. Structures and enums get own names (like +Header). Sequences are referred as *<replaceable>normal +type</replaceable>, so that a sequence of longs is <quote>*long</quote> +and a sequence of Header struct's is <quote>*Header</quote>. +</para> + +</sect2> + +<sect2 id="mcop-protocol-messages"> +<title>Messages</title> + +<para> +The &MCOP; message header format is defined as defined by this +structure: +</para> + +<programlisting> +struct Header { + long magic; // the value 0x4d434f50, which is marshalled as MCOP + long messageLength; + long messageType; +}; +</programlisting> + +<para> +The possible messageTypes are currently +</para> + +<programlisting> + mcopServerHello = 1 + mcopClientHello = 2 + mcopAuthAccept = 3 + mcopInvocation = 4 + mcopReturn = 5 + mcopOnewayInvocation = 6 +</programlisting> + +<para> +A few notes about the &MCOP; messaging: +</para> + + +<itemizedlist> +<listitem> +<para> +Every message starts with a Header. +</para> +</listitem> + +<listitem> +<para> +Some messages types should be dropped by the server, as long as the +authentication is not complete. +</para> +</listitem> + +<listitem> +<para> +After receiving the header, the protocol (connection) handling can +receive the message completely, without looking at the contents. +</para> +</listitem> +</itemizedlist> + +<para> +The messageLength in the header is of course in some cases redundant, +which means that this approach is not minimal regarding the number of +bytes. +</para> + +<para> +However, it leads to an easy (and fast) implementation of non-blocking +messaging processing. With the help of the header, the messages can be +received by protocol handling classes in the background (non-blocking), +if there are many connections to the server, all of them can be served +parallel. You don't need to look at the message content, to receive the +message (and to determine when you are done), just at the header, so the +code for that is pretty easy. +</para> + +<para> +Once a message is there, it can be demarshalled and processed in one +single pass, without caring about cases where not all data may have been +received (because the messageLength guarantees that everything is +there). +</para> + +</sect2> + +<sect2 id="mcop-protocol-invocations"> +<title>Invocations</title> + +<para> +To call a remote method, you need to send the following structure in the +body of an &MCOP; message with the messageType = 1 (mcopInvocation): +</para> + +<programlisting> +struct Invocation { + long objectID; + long methodID; + long requestID; +}; +</programlisting> + +<para> +after that, you send the parameters as structure, ⪚ if you invoke the +method string concat(string s1, string s2), you send a structure like +</para> + +<programlisting> +struct InvocationBody { + string s1; + string s2; +}; +</programlisting> + + +<para> +if the method was declared to be oneway - that means asynchronous +without return code - then that was it. Otherwise, you'll receive as +answer the message with messageType = 2 (mcopReturn) +</para> + +<programlisting> +struct ReturnCode { + long requestID; + <resulttype> result; +}; +</programlisting> + + +<para> +where <resulttype> is the type of the result. As void types are +omitted in marshalling, you can also only write the requestID if you +return from a void method. +</para> + +<para> +So our string concat(string s1, string s2) would lead to a returncode +like +</para> + +<programlisting> +struct ReturnCode { + long requestID; + string result; +}; +</programlisting> + +</sect2> + +<sect2 id="mcop-protocol-inspecting"> +<title>Inspecting Interfaces</title> + +<para> +To do invocations, you need to know the methods an object supports. To +do so, the methodID 0, 1, 2 and 3 are hardwired to certain +functionalities. That is +</para> + +<programlisting> +long _lookupMethod(MethodDef methodDef); // methodID always 0 +string _interfaceName(); // methodID always 1 +InterfaceDef _queryInterface(string name); // methodID always 2 +TypeDef _queryType(string name); // methodID always 3 +</programlisting> + +<para> +to read that, you of course need also +</para> + +<programlisting> +struct MethodDef { + string methodName; + string type; + long flags; // set to 0 for now (will be required for streaming) + sequence<ParamDef> signature; +}; + +struct ParamDef { + string name; + long typeCode; +}; +</programlisting> + +<para> +the parameters field contains type components which specify the types of +the parameters. The type of the returncode is specified in the +MethodDef's type field. +</para> + +<para> +Strictly speaking, only the methods +<methodname>_lookupMethod()</methodname> and +<methodname>_interfaceName()</methodname> differ from object to object, +while the <methodname>_queryInterface()</methodname> and +<methodname>_queryType()</methodname> are always the same. +</para> + +<para> +What are those methodIDs? If you do an &MCOP; invocation, you are +expected to pass a number for the method you are calling. The reason for +that is, that numbers can be processed much faster than strings when +executing an &MCOP; request. +</para> + +<para> +So how do you get those numbers? If you know the signature of the +method, that is a MethodDef that describes the method, (which contains +name, type, parameter names, parameter types and such), you can pass +that to _lookupMethod of the object where you wish to call a method. As +_lookupMethod is hardwired to methodID 0, you should encounter no +problems doing so. +</para> + +<para> +On the other hand, if you don't know the method signature, you can find +which methods are supported by using _interfaceName, _queryInterface and +_queryType. +</para> +</sect2> + +<sect2 id="mcop-protocol-typedefs"> +<title>Type Definitions</title> + +<para> +User defined datatypes are described using the +<structname>TypeDef</structname> structure: +</para> + +<programlisting> +struct TypeComponent { + string type; + string name; +}; + +struct TypeDef { + string name; + + sequence<TypeComponent> contents; +}; +</programlisting> + +</sect2> +</sect1> + +<sect1 id="why-not-dcop"> +<title>Why &arts; Doesn't Use &DCOP;</title> + +<para> +Since &kde; dropped <acronym>CORBA</acronym> completely, and is using +&DCOP; everywhere instead, naturally the question arises why &arts; +isn't doing so. After all, &DCOP; support is in +<classname>KApplication</classname>, is well-maintained, supposed to +integrate greatly with libICE, and whatever else. +</para> + +<para> +Since there will be (potentially) a lot of people asking whether having +&MCOP; besides &DCOP; is really necessary, here is the answer. Please +don't get me wrong, I am not trying to say <quote>&DCOP; is +bad</quote>. I am just trying to say <quote>&DCOP; isn't the right +solution for &arts;</quote> (while it is a nice solution for other +things). +</para> + +<para> +First, you need to understand what exactly &DCOP; was written +for. Created in two days during the &kde;-TWO meeting, it was intended +to be as simple as possible, a really <quote>lightweight</quote> +communication protocol. Especially the implementation left away +everything that could involve complexity, for instance a full blown +concept how data types shall be marshalled. +</para> + +<para> +Even although &DCOP; doesn't care about certain things (like: how do I +send a string in a network-transparent manner?) - this needs to be +done. So, everything that &DCOP; doesn't do, is left to &Qt; in the +&kde; apps that use &DCOP; today. This is mostly type management (using +the &Qt; serialization operator). +</para> + +<para> +So &DCOP; is a minimal protocol which perfectly enables &kde; +applications to send simple messages like <quote>open a window pointing +to http://www.kde.org</quote> or <quote>your configuration data has +changed</quote>. However, inside &arts; the focus lies on other things. +</para> + +<para> +The idea is, that little plugins in &arts; will talk involving such data +structures as <quote>midi events</quote> and <quote>songposition +pointers</quote> and <quote>flow graphs</quote>. +</para> + +<para> +These are complex data types, which must be sent between different +objects, and be passed as streams, or parameters. &MCOP; supplies a type +concept, to define complex data types out of simpler ones (similar to +structs or arrays in C++). &DCOP; doesn't care about types at all, so +this problem would be left to the programmer - like: writing C++ classes +for the types, and make sure they can serialize properly (for instance: +support the &Qt; streaming operator). +</para> + +<para> +But that way, they would be inaccessible to everything but direct C++ +coding. Specifically, you could not design a scripting language, that +would know all types plugins may ever expose, as they are not self +describing. +</para> + +<para> +Much the same argument is valid for interfaces as well. &DCOP; objects +don't expose their relationships, inheritance hierarchies, etc. - if you +were to write an object browser which shows you <quote>what attributes +has this object got</quote>, you'd fail. +</para> + + +<para> +While Matthias told me that you have a special function +<quote>functions</quote> on each object that tells you about the methods +that an object supports, this leaves out things like attributes +(properties), streams and inheritance relations. +</para> + +<para> +This seriously breaks applications like &arts-builder;. But remember: +&DCOP; was not so much intended to be an object model (as &Qt; already +has one with <application>moc</application> and similar), nor to be +something like <acronym>CORBA</acronym>, but to supply inter-application +communication. +</para> + +<para> +Why &MCOP; even exists is: it should work fine with streams between +objects. &arts; makes heavily use of small plugins, which interconnect +themselves with streams. The <acronym>CORBA</acronym> version of &arts; +had to introduce a very annoying split between <quote>the SynthModule +objects</quote>, which were the internal work modules that did do the +streaming, and <quote>the <acronym>CORBA</acronym> interface</quote>, +which was something external. +</para> + +<para> +Much code cared about making interaction between <quote>the SynthModule +objects</quote> and <quote>the <acronym>CORBA</acronym> +interface</quote> look natural, but it didn't, because +<acronym>CORBA</acronym> knew nothing at all about streams. &MCOP; +does. Look at the code (something like +<filename>simplesoundserver_impl.cc</filename>). Way better! Streams +can be declared in the interface of modules, and implemented in a +natural looking way. +</para> + +<para> +One can't deny it. One of the reasons why I wrote &MCOP; was speed. Here +are some arguments why &MCOP; will definitely be faster than &DCOP; +(even without giving figures). +</para> + + +<para> +An invocation in &MCOP; will have a six-<quote>long</quote>-header. That +is: +</para> + +<itemizedlist> +<listitem><para>magic <quote>MCOP</quote></para></listitem> +<listitem><para>message type (invocation)</para></listitem> +<listitem><para>size of the request in bytes</para></listitem> +<listitem><para>request ID</para></listitem> +<listitem><para>target object ID</para></listitem> +<listitem><para>target method ID</para></listitem> +</itemizedlist> + +<para> +After that, the parameters follow. Note that the demarshalling of this +is extremely fast. You can use table lookups to find the object and the +method demarshalling function, which means that complexity is O(1) [ it +will take the same amount of time, no matter how many objects are alive, +or how many functions are there ]. +</para> + +<para> +Comparing this to &DCOP;, you'll see, that there are at least +</para> + +<itemizedlist> +<listitem><para>a string for the target object - something like +<quote>myCalculator</quote></para></listitem> +<listitem><para>a string like <quote>addNumber(int,int)</quote> to +specify the method</para></listitem> +<listitem><para>several more protocol info added by libICE, and other +DCOP specifics I don't know</para></listitem> +</itemizedlist> + +<para> +These are much more painful to demarshall, as you'll need to parse the +string, search for the function, &etc;. +</para> + +<para> +In &DCOP;, all requests are running through a server +(<application>DCOPServer</application>). That means, the process of a +synchronous invocation looks like this: +</para> + +<itemizedlist> +<listitem> +<para> +Client process sends invocation. +</para> +</listitem> + +<listitem> +<para> +<application>DCOPserver</application> (man-in-the-middle) receives +invocation and looks where it needs to go, and sends it to the +<quote>real</quote> server. +</para> +</listitem> + +<listitem> +<para> +Server process receives invocation, performs request and sends result. +</para> +</listitem> + +<listitem> +<para> +<application>DCOPserver</application> (man-in-the-middle) receives +result and ... sends it to the client. +</para> +</listitem> + +<listitem> +<para> +Client decodes reply. +</para> +</listitem> +</itemizedlist> + +<para> +In &MCOP;, the same invocation looks like this: +</para> + +<itemizedlist> +<listitem> +<para> +Client process sends invocation. +</para> +</listitem> + +<listitem> +<para> +Server process receives invocation, performs request and sends result. +</para> +</listitem> + +<listitem> +<para> +Client decodes reply. +</para> +</listitem> +</itemizedlist> + +<para> +Say both were implemented correctly, &MCOP;s peer-to-peer strategy +should be faster by a factor of two, than &DCOP;s man-in-the-middle +strategy. Note however that there were of course reasons to choose the +&DCOP; strategy, which is namely: if you have 20 applications running, +and each app is talking to each app, you need 20 connections in &DCOP;, +and 200 with &MCOP;. However in the multimedia case, this is not +supposed to be the usual setting. +</para> + +<para> +I tried to compare &MCOP; and &DCOP;, doing an invocation like adding +two numbers. I modified testdcop to achieve this. However, the test may +not have been precise on the &DCOP; side. I invoked the method in the +same process that did the call for &DCOP;, and I didn't know how to get +rid of one debugging message, so I used output redirection. +</para> + +<para> +The test only used one object and one function, expect &DCOP;s results +to decrease with more objects and functions, while &MCOP;s results +should stay the same. Also, the <application>dcopserver</application> +process wasn't connected to other applications, it might be that if many +applications are connected, the routing performance decreases. +</para> + +<para> +The result I got was that while &DCOP; got slightly more than 2000 +invocations per second, &MCOP; got slightly more than 8000 invocations +per second. That makes a factor of 4. I know that &MCOP; isn't tuned to +the maximum possible, yet. (Comparison: <acronym>CORBA</acronym>, as +implemented with mico, does something between 1000 and 1500 invocations +per second). +</para> + +<para> +If you want <quote>harder</quote> data, consider writing some small +benchmark app for &DCOP; and send it to me. +</para> + +<para> +<acronym>CORBA</acronym> had the nice feature that you could use objects +you implemented once, as <quote>separate server process</quote>, or as +<quote>library</quote>. You could use the same code to do so, and +<acronym>CORBA</acronym> would transparently decide what to do. With +&DCOP;, that is not really intended, and as far as I know not really +possible. +</para> + +<para> +&MCOP; on the other hand should support that from the beginning. So you +can run an effect inside &artsd;. But if you are a wave editor, you can +choose to run the same effect inside your process space as well. +</para> + +<para> +While &DCOP; is mostly a way to communicate between apps, &MCOP; is also +a way to communicate inside apps. Especially for multimedia streaming, +this is important (as you can run multiple &MCOP; objects parallely, to +solve a multimedia task in your application). +</para> + +<para> +Although &MCOP; does not currently do so, the possibilities are open to +implement quality of service features. Something like <quote>that &MIDI; event +is really really important, compared to this invocation</quote>. Or something +like <quote>needs to be there in time</quote>. +</para> + +<para> +On the other hand, stream transfer can be integrated in the &MCOP; +protocol nicely, and combined with <acronym>QoS</acronym> stuff. Given +that the protocol may be changed, &MCOP; stream transfer should not +really get slower than conventional <acronym>TCP</acronym> streaming, +but: it will be easier and more consistent to use. +</para> + +<para> +There is no need to base a middleware for multimedia on &Qt;. Deciding +so, and using all that nice &Qt;-streaming and stuff, will easily lead +to the middleware becoming a &Qt;-only (or rather &kde;-only) thing. I +mean: as soon as I'll see the GNOMEs using &DCOP;, too, or something like +that, I am certainly proven wrong. +</para> + +<para> +While I do know that &DCOP; basically doesn't know about the data types +it sends, so that you could use &DCOP; without using &Qt;, look at how +it is used in daily &kde; usage: people send types like +<classname>QString</classname>, <classname>QRect</classname>, +<classname>QPixmap</classname>, <classname>QCString</classname>, ..., +around. These use &Qt;-serialization. So if somebody choose to support +&DCOP; in a GNOME program, he would either have to claim to use +<classname>QString</classname>,... types (although he doesn't do so), +and emulate the way &Qt; does the streaming, or he would send other +string, pixmap and rect types around, and thus not be interoperable. +</para> + +<para> +Well, whatever. &arts; was always intended to work with or without +&kde;, with or without &Qt;, with or without X11, and maybe even with or +without &Linux; (and I have even no problems with people who port it to +a popular non-free operating systems). +</para> + +<para> +It is my position that non-&GUI;-components should be written +non-&GUI;-dependant, to make sharing those among wider amounts of +developers (and users) possible. +</para> + +<para> +I see that using two <acronym>IPC</acronym> protocols may cause +inconveniences. Even more, if they are both non-standard. However, for +the reasons given above, switching to &DCOP; is no option. If there is +significant interest to find a way to unite the two, okay, we can +try. We could even try to make &MCOP; speak <acronym>IIOP</acronym>, +then we'd have a <acronym>CORBA</acronym> <acronym>ORB</acronym> ;). +</para> + +<para> +I talked with Matthias Ettrich a bit about the future of the two +protocols, and we found lots of ways how things could go on. For +instance, &MCOP; could handle the message communication in &DCOP;, thus +bringing the protocols a bit closer together. +</para> + +<para> +So some possible solutions would be: +</para> + +<itemizedlist> +<listitem> +<para> +Write an &MCOP; - &DCOP; gateway (which should be possible, and would +make interoperation possible) - note: there is an experimental +prototype, if you like to work on that. +</para> +</listitem> + +<listitem> +<para> +Integrate everything &DCOP; users expect into &MCOP;, and try to only do +&MCOP; - one could add an <quote>man-in-the-middle-option</quote> to +&MCOP;, too ;) +</para> +</listitem> + +<listitem> +<para> +Base &DCOP; on &MCOP; instead of libICE, and slowly start integrating +things closer together. +</para> +</listitem> +</itemizedlist> + +<para> +However, it may not be the worst possibility to use each protocol for +everything it was intended for (there are some big differences in the +design goals), and don't try to merge them into one. +</para> + +</sect1> +</chapter> |