From: bill@amber.csd.harris.com (Bill Leonard)
Newsgroups: comp.lang.c++,comp.std.c++
Subject: Re: Callbacks -- suggested approaches??
Date: 28 Jan 1993 22:41:49 GMT
Organization: Harris CSD, Ft. Lauderdale, FL
Distribution: ssd
NNTP-Posting-Host: amber.ssd.csd.harris.com

In article <rmartin.728090477@stripe>, rmartin@stripe.Rational.COM (Bob Martin) writes:
> Passing messages between processes is always a challenge, especially
> when what you really want to do is pass messages between two objects
> which happen to live in different processes.  It would be very nice if
> the sending and receiving objects did not know that the message was
> crossing a process boundary.  Below is described a technique for
> achieving this.
> 
> Unfortunately, theB lives in another process.  No problem.  Create an
> abstract class called AbstractB.  It contains a set of pure virtual
> functions defining all the messages that B can receive.  B will
> inherit from AbstractB.  So will another class called BSurrogate.
> BSurrogate knows which process the real B object lives in and knows
> how to convert the message into a packet and ship it to that process.
> The packet will contain all the arguments to X, an identifier denoting
> that it represents an X message and the identifier of the B object to
> which the message should be sent.
> 
> When the process receives the it looks up the name of the object in a
> dictionary.  The dictionary associates the name with a derivative of
> the class Reciever.  BReceiver is derived from Receiver, and knows how
> to unpack the X packet.  It also knows where the B object is (Probably
> it contains a pointer to it), so it unpacks all the arguments and then
> sends the message to its B object.  

We have implemented a scheme very much like this in a project here, with
some minor differences.  In so doing we have learned some lessons that
I will pass on for what they're worth.  First, a brief description of
our scheme:

For each class of object that needs to implement inter-process
communication, we define 3 classes: There's the AbstractB as Bob says
above, there's the BSurrogate derived from AbstractB, and there's also the
BReal derived from AbstractB.  AbstractB just defines the interface to the
B class of objects.  BSurrogate implements the interface by passing
messages to another process.  BReal is the actual implementation of the
functionality of B.

We also define a class called Router (similar to Bob's Receiver class).
For each class B, there is a BRouter class, derived from Router, that
receives the messages that BSurrogate sent and calls the appropriate
function using a pointer to a BReal object.

By the way, we don't "look up the object in a dictionary".  We just pass
the address of the BRouter object as part of the message; the generic
message handler casts that value to a Router * and calls a virtual function
using the resulting pointer.  (More on this below...)

Now, the lessons learned:

  1. Adding a function to the interface to B is a real pain!  You must add
     the function signature in 3 places: AbstractB, BSurrogate, and BReal.
     You generally also have to invent a new type of message, which entails
     adding code to BRouter to know how to interpret that new message.

  2. Getting these objects created is often non-trivial.  Someone has to
     create the BReal object in the process where he is to reside.  That
     someone must typically also create the BRouter object, who needs to
     keep a pointer to the BReal object around so he can call member
     functions of it.  Then the BSurrogate object must be created in the
     other process, and he needs to know some ID for the BRouter object so
     that messages he sends get to the right place.

     We've had a few cases where the BSurrogate and BReal objects each
     needed to talk to the other.  That further complicates the creation,
     usually entailing having one of the objects send a message to the
     other one telling him the address he should use if he wants to
     initiate messages in the other direction.

  3. Destroying these objects is likewise tricky.

  4. If, for some reason, you need to make copies of a B object, then that
     entails having BSurrogate implement a copy constructor that can send a
     message over to BReal telling him to make a copy of himself and his
     BRouter.

  5. If I had to do this over, I would make the BReal object use multiple
     inheritance, deriving it from both the AbstractB class and the Router
     class.  That way, the BReal object can be its *own* Router, thus
     eliminating the need to create an extra object and coordinating its
     creation and destruction.

  6. If you ever screw up a message and pass the wrong Router pointer, you
     will get very bizarre and hard-to-track-down bugs.  This method has
     the advantage of being fast, but we still talk about adding some kind
     of dictionary so that we can at least verify that the Router pointer
     we have is valid.

We made a small attempt at automating the creation of the BSurrogate and
BRouter classes, with no success, but that doesn't mean it couldn't be
done.  If I had to do this for a very large number of classes and
functions, I'd probably develop a very-high-level language of some kind to
describe the interface, so that I could automate at least parts of this.

Well, hope this helps somebody...

-- 
Bill Leonard
Harris Computer Systems Division
2101 W. Cypress Creek Road
Fort Lauderdale, FL  33309
bill@ssd.csd.harris.com

These opinions and statements are my own and do not reflect the opinions or
positions of Harris Corporation.
---------------------------------------------------------------------------
Prism: A place for light waves that commit minor refractions.
---------------------------------------------------------------------------
Newsgroups: comp.lang.c++