[comp.lang.c++] Combining event driven and object orient programming

tmh@well.UUCP (Todd M. Hoff) (10/04/89)

With the question of C++ having boolean types out of our blood,
let's move on to something really challenging.

QUESTION:

In an event driven environment, how are events associated with 
the correct method within the correct object instance? How is
argument parsing handled?

SITUATION:

For our project each process is completely event driven. A set of
libraries implements event handling through a set of callback routines
and work/response queues. There are a number of clients and servers
using and providing various services. Applications process events from
multiple sources including a user interface manager, several database
managers, processes on remote machines asnchronously emitting events, 
and a few others. In each application are multiple objects handling
these events and controlling remote objects.

The problem is how to associate an event with a particular method within
a particular object instance. Events arrive and they must be processed.
Use which object? Which method? How is the message translated into
method arguments? The situation is complicated even more by the heterogenaity 
of the event message formats. Some messages are basically just serialized C 
structures and others are CMIP messages encoded in ASN.1, needing A LOT of 
decoding. So there is no standard message format or standard set of arguments 
to pass.

SOLUTION:

The problem seems to stem around the inability to send a message to
an object, something like: objID selector data. There's function
overloading, but function overloading assumes you're in a context 
such that you know what method to call. In an event driven environment 
the former is rarely the case.

I have a couple of possible solutions, but I am hoping someone has
faced this problem before and has an elegant painfully-obvious-on-
further-reflection solution. I don't consider endless switch statements 
a real solution.

Thanks in advance

tmh@well

nagle@well.UUCP (John Nagle) (10/04/89)

       What you really need is serious concurrency support.  Here's why.

       With a language like C++, there is the potential to have a lot of
bookkeeping taken care of automatically as objects are created and then 
destroyed as scopes are exited.  Unfortunately, in event-driven single-thread
programs, you can't do this, because essentially all context associated
with where control is in the program (basically, the information on the
stack) is lost every time you go back to the top of the event loop.

       In principle, if one has cheap multitasking (called, variously,
"threads", "lightweight processes", or "weak fork"), an elegant
solution is for events which will require processing beyond the
current event to fork off a new task to handle them, requesting that
related events be forwarded to that task.  The task can have lots of
context, and lots of "auto" storage-class (in the C sense) objects.

       For example, if you have a program with lots of menus and
windows, each time the user makes a menu selection that causes a
window to be opened, it should result in a task being forked off to
handle that window.  User-generated events in that window are then
sent to the task associated with that window.  It should be possible
for this process to be repeated recursively as necessary.  

       Each of these little tasks looks a lot like an old-fashioned
sequential program.  Rather than always going back to the event
loop, these tasks do something and wait for the user to respond.
If the user wants to do something else, a higher level task will
fork off a task to handle it.  

       It helps if Ada-like exception handling is available.  This
provides a clean way to bail out with proper unwinding when necessary.
The combination of multitasking, destructors, and exception processing
makes cleanup almost painless.

       Also essential is automatic multitasking interlocks, or "monitors".
The idea is that if you want to access data shared with another task,
you must call an access function of a class that is declared as allowing
object sharing across task boundaries.  When one task has control inside
a function of such a class, other tasks are blocked if they call a 
function of the class.  This protects shared data automatically.

       When you think about this, it makes sense.  An event loop is
really a dumb sort of CPU dispatcher, after all.  With a smarter dispatcher,
we could have much more elegant programs.

       Now how do we get multithread C++?

					John Nagle

rcb@ccpv1.ncsu.edu (Randy Buckland) (10/04/89)

The best way I have found to handle things like callbacks on events is to
create an object "action". I then have subclasses "action_method" and
"action_function" which provide method and procedure callback actions.
When setting up an event callback, I pass in a pointer to a proper action
structure. When the event occurs, the event code calls the "do_it" method of
the action object and the specific type of action handles how to perform the
function.
Randy Buckland
rcb@ncsuvx.ncsu.edu

davidm@uunet.UU.NET (David S. Masterson) (10/06/89)

In article <13929@well.UUCP> nagle@well.UUCP (John Nagle) writes:
>	  With a language like C++, there is the potential to have a lot of
>   bookkeeping taken care of automatically as objects are created and then
>   destroyed as scopes are exited.  Unfortunately, in event-driven
>   single-thread programs, you can't do this, because essentially all context
>   associated with where control is in the program (basically, the information
>   on the stack) is lost every time you go back to the top of the event loop.
>
Isn't one possibility to not lose the context by calling the event loop
instead of returning to it?  This may require a little rearranging of callback
routines because the new context (the sub-event loop) shouldn't handle all the
normal callbacks, just those that make sense in the new context.  If the event
loop (or event handler) is treated like an object (with static memory areas),
there should be no problem in constructing a new event loop.  Right?

>	  In principle, if one has cheap multitasking (called, variously,
>   "threads", "lightweight processes", or "weak fork"), an elegant solution is
>   for events which will require processing beyond the current event to fork
>   off a new task to handle them, requesting that related events be forwarded
>   to that task.  The task can have lots of context, and lots of "auto"
>   storage-class (in the C sense) objects.
>
The question I have hear is not so much "buying" into the cheap multitasking
(or even sub-event processing), but rather the manner of getting out of it
when the subtask is done.  Is there a return value from this other task or
does the task signal its parent with a new event?  Assume that the reason for
invoking the other task was to get information from another "process" (user,
database, whatever) and construct an object from that information that the
current task can work with.  What should be the method of giving this object
back to the parent task?

>	  Now how do we get multithread C++?
>
One naive question, though.  Define "smart" dispatcher?

David Masterson
uunet!cimshop!davidm

prc@erbe.se (Robert Claeson) (10/06/89)

In article <13929@well.UUCP> nagle@well.UUCP (John Nagle) writes:

>       What you really need is serious concurrency support.  Here's why.

[---]

>       When you think about this, it makes sense.  An event loop is
>really a dumb sort of CPU dispatcher, after all.  With a smarter dispatcher,
>we could have much more elegant programs.
>
>       Now how do we get multithread C++?

Check out Concurrent C++ (from AT&T Bell Labs., I believe).

-- 
          Robert Claeson      E-mail: rclaeson@erbe.se
	  ERBE DATA AB

rfg@ics.uci.edu (Ronald Guilmette) (10/08/89)

In article <848@maxim.erbe.se> prc@erbe.se (Robert Claeson) writes:
>
>Check out Concurrent C++ (from AT&T Bell Labs., I believe).

There have been papers published on "Concurrent C", but this is the first
mention I have seen to "Concurrent C++".  Can you provide a reference or
something so that people who *are* actually interested in this can get
more information.

Thanks.

// rfg

schmidt@zola.ics.uci.edu (Doug Schmidt) (10/08/89)

In article <1989Oct7.194345.1384@paris.ics.uci.edu>, rfg@ics (Ronald Guilmette) writes:
>In article <848@maxim.erbe.se> prc@erbe.se (Robert Claeson) writes:
>>
>>Check out Concurrent C++ (from AT&T Bell Labs., I believe).
>
>There have been papers published on "Concurrent C", but this is the first
>mention I have seen to "Concurrent C++".  Can you provide a reference or
>something so that people who *are* actually interested in this can get
>more information.

Check out the book `The Concurrent C Programming Language' by Narain
Gehani and William D. Roome, published 1989 by Silcon Press, ISBN
0-929306-00-7.  Chapter 5 discusses Concurrent C++ (this looks similar
to an article published recently in Software Practice and Experience).

The concurrency model in Concurrent C is based on the `rendezvous
concept,' and people familiar with Ada's tasking model should feel
comfortable with many of its constructs (see appendix C for a
comparison between Ada and Concurrent C).

I'd be interested in hearing whether anyone else has actually
programmed in Concurrent C/C++, and also whether it is available for
general use.  It certainly sounds like an interesting extension to
C/C++.

Doug
--
Master Swordsman speak of humility;             | schmidt@ics.uci.edu (ARPA)
Philosophers speak of truth;                    | office: (714) 856-4034
Saints and wisemen speak of the Tao of no doubt;
The moon, sun, and sea speaks for itself. -- Hiroshi Hamada

schwartz@psuvax1.cs.psu.edu (Scott Schwartz) (10/08/89)

Doug Schmidt writes:
| I'd be interested in hearing whether anyone else has actually
| programmed in Concurrent C/C++, and also whether it is available for
| general use.  

Here is one data point: CCC is used in some of the operating systems
courses here at psu.  People seem happier with this than with
Concurrent Euclid, which we used to use.

--
Scott Schwartz		<schwartz@shire.cs.psu.edu>
Now back to our regularly scheduled programming....

prc@erbe.se (Robert Claeson) (10/08/89)

In article <1989Oct7.194345.1384@paris.ics.uci.edu> Ronald Guilmette <rfg@ics.uci.edu> writes:

>There have been papers published on "Concurrent C", but this is the first
>mention I have seen to "Concurrent C++".  Can you provide a reference or
>something so that people who *are* actually interested in this can get
>more information.

Concurrent C++ comes from the same folks that makes Concurrent C.
Concurrent C++ is a compiler option. There were an article on C++
in one of the recent issues of UNIX Review. Interesting stuff.

-- 
          Robert Claeson      E-mail: rclaeson@erbe.se
	  ERBE DATA AB

nagle@well.UUCP (John Nagle) (10/09/89)

In article <CIMSHOP!DAVIDM.89Oct5101459@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
>In article <13929@well.UUCP> nagle@well.UUCP (John Nagle) writes:
>Isn't one possibility to not lose the context by calling the event loop
>instead of returning to it?  This may require a little rearranging of callback
>routines because the new context (the sub-event loop) shouldn't handle all the
>normal callbacks, just those that make sense in the new context.  If the event
>loop (or event handler) is treated like an object (with static memory areas),
>there should be no problem in constructing a new event loop.  Right?

       That approach results in a mode.  If you want a modeless event
processing model, as the Apple Human Interface Guidelines suggest, such
an approach is inappropriate.  You should be able to do anything at
any time unless there is some pressing reason to prohibit inherently
incompatible actions.

      If you like a calling-type model, something like Common LISP
closures might be helpful, as an alternative to tasking.

>The question I have hear (sic) is not so much "buying" into the cheap multitasking
>(or even sub-event processing), but rather the manner of getting out of it
>when the subtask is done.  Is there a return value from this other task or
>does the task signal its parent with a new event?  Assume that the reason for
>invoking the other task was to get information from another "process" (user,
>database, whatever) and construct an object from that information that the
>current task can work with.  What should be the method of giving this object
>back to the parent task?

      You fork off a task when there's no need to wait for the result.  
Suppose the application can open various kinds of documents, but opening
may be time-consuming.  Having initiated opening of a document, the user
should be free to make further selections and open more documents while
the first document opens.  

      It's OK to call a subroutine that takes time, but not from the main
event loop.  The idea is not to take control away from the user.

>One naive question, though.  Define "smart" dispatcher?

      One which supports preemption, priorities, time-slicing, and
wait/send (or P/V) type operations.  A dumb dispatcher can only transfer
control at predefined points.

					John Nagle

davidm@uunet.UU.NET (David S. Masterson) (10/10/89)

In article <14004@well.UUCP> nagle@well.UUCP (John Nagle) writes:
	  That approach results in a mode.  If you want a modeless event
   processing model, as the Apple Human Interface Guidelines suggest, such an
   approach is inappropriate.  You should be able to do anything at any time
   unless there is some pressing reason to prohibit inherently incompatible
   actions.

How can I get hold of the Apple Human Interface Guidelines?  Does it make
sense in an object-oriented world (particularly C++)?  How much analysis of
the proposed application is required for this model (it doesn't sound like
much, just a particular mindset that I haven't assimilated yet with C++ in
mind).  It seems to me that having a "pressing reason" would be more the norm
in most situations (again, probably a mindset problem).

	 You fork off a task when there's no need to wait for the result.
   Suppose the application can open various kinds of documents, but opening
   may be time-consuming.  Having initiated opening of a document, the user
   should be free to make further selections and open more documents while the
   first document opens.

Would not these subtasks also have event loops of their own?  There's
something recursive in this definition that I'm having trouble following. In
the C++ paradigm, would opening a document be considered constructing an
object or more of a message to an object that was already constructed (the
document opener task)?

David Masterson
uunet!cimshop!davidm

nagle@well.UUCP (John Nagle) (10/11/89)

In article <CIMSHOP!DAVIDM.89Oct9120329@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
>How can I get hold of the Apple Human Interface Guidelines?  

     Buy the book, "Apple Human Interface Guidelines", (no author named),
Addison-Wesley, 1986, ISBN 0-201-17753-6.

>Does it make sense in an object-oriented world (particularly C++)?  

     Modern thinking on user interfaces is that the interface design drives
the internals, not the other way round.  

>How much analysis of
>the proposed application is required for this model (it doesn't sound like
>much, just a particular mindset that I haven't assimilated yet with C++ in
>mind).  

     A lot.  This isn't easy.

>It seems to me that having a "pressing reason" would be more the norm
>in most situations (again, probably a mindset problem).

     Finding "pressing reasons" to lock the user into a mode is a sign of
a bad design.  The user ought to be able to do anything at any time that is
meaningful to do, even though this may be inconvenient for the application
implementor.

>Would not these subtasks also have event loops of their own?  

      They might, but their events would be messages from a higher level
event handler.  Normally, a subtask shouldn't capture the main event 
processing, since this would lock out the user from doing all the things
normally possible at the top level.

					John Nagle

alan@oz.nm.paradyne.com (Alan Lovejoy) (10/14/89)

In article <13929@well.UUCP< nagle@well.UUCP (John Nagle) writes:
<
<       What you really need is serious concurrency support.  Here's why.
<
<       In principle, if one has cheap multitasking (called, variously,
<"threads", "lightweight processes", or "weak fork"), an elegant
<solution is for events which will require processing beyond the
<current event to fork off a new task to handle them, requesting that
<related events be forwarded to that task.  The task can have lots of
<context, and lots of "auto" storage-class (in the C sense) objects.
<
<       It helps if Ada-like exception handling is available.  This
<provides a clean way to bail out with proper unwinding when necessary.
<The combination of multitasking, destructors, and exception processing
<makes cleanup almost painless.
<
<       Also essential is automatic multitasking interlocks, or "monitors".
<The idea is that if you want to access data shared with another task,
<you must call an access function of a class that is declared as allowing
<object sharing across task boundaries.  When one task has control inside
<a function of such a class, other tasks are blocked if they call a 
<function of the class.  This protects shared data automatically.
<
<       Now how do we get multithread C++?

Gee, this reminds me of something...  Perhaps some other programming language
I used at one time... Oh! I know, it's like Modula-2!  Or is it?  Let's see:
Lightweight processes, check.  Monitors, check.  Exception handling...well,
Modula-2 doesn't have exception handling.  Oh well.  It doesn't have classes,
constructors or destructors either.  I guess you can't have everything.

My impression is that Modula-2's threads are seldom used, for some reason.
Whether this is due to a problem with Modula-2, with the M2 implementation 
of threads/monitors, with the Modula-2 user community, or simply indicative
of the usefulness of threads, I couldn't really say.

Of course, exception handling can be kludged-up using setjmp/longjmp.  Monitors
and threads require compiler help, or at least extensive non-portable work
with asm().  P&V operations can be used to establish critical sections, 
instead of the more elegant "monitor" abstraction.  

All things considered, it would seem best to put support for threads/monitors 
in the compiler--provided threads/monitors would actually be useful to a 
significant community of programmers.  Whether this is the case is not clear, 
at least to me.  



____"Congress shall have the power to prohibit speech offensive to Congress"____
Alan Lovejoy; alan@pdn; 813-530-2211; AT&T Paradyne: 8550 Ulmerton, Largo, FL.
Disclaimer: I do not speak for AT&T Paradyne.  They do not speak for me. 
Motto: If nanomachines will be able to reconstruct you, YOU AREN'T DEAD YET.