C-Scene Issues 1..9 Authors Algorithms Books Patterns Graphics Miscellaneous UNIX Web & XML Windows Feedback FAQs Changes Submissions |
by Jürgen Hermann
last updated 2001/08/02 (version
1.1.1.1)
also available as XML
Many operating systems and other subsystems (like GUI libraries) feature a special type of hook into those systems, named callbacks or callback functions. Upon initialization or by calling an API function you pass pointers to the callback into the subsystem, for later use. The problem with those functions is, since these subsystems are nowadays not yet OO, that they have no notion of what an object is. So if you want to have a callback object instead of a mere function, some OO magic is called for.
As an example, consider the BeginThread
API that many OSes have
in a quite similar form; we assume that it takes a pointer to the function that
provides the actual code for the newly created thread plus a data
pointer that is passed to that function as a startup parameter. Thus,
we end up with BeginThread (void (*thread_func) (void*), void*
startup_data)
. Now let's make a Thread
class of
that.
What we want to have is an ABC (abstract base class) that you can inherit
from, creating specialized thread classes and in turn thread objects (i.e.
actual threads). The code of the thread is located in a pure
virtual function code
that is provided by the inherited
class. code
is then similar to the thread_func
parameter of the BeginThread
call, but is a full-blown member
function, not just a C function. So, we get this interface for the
Thread
class:
class Thread { public: virtual ~Thread(); void run(); protected: Thread(); virtual void code() = 0; private: int running; static void dispatch(void* thread_obj); };
This might seem quite unusual to you (like having a protected constructor), but things will be explained in due course.
When we put the thread concept into a class, we have to consider lifetime. A
thread exists as long as the thread function does not return, thus the object
has to have the same lifetime. Because of this, an auto
thread
object does not make much sense; we insure that every thread object exists on
the heap by making the ctor protected and providing a static factory method
create
for thread objects in each derived class:
Thread::Thread() : running(0) { } DerivedThread& DerivedThread::create() { return *new DerivedThread; }
create
has to be added to every inherited class, returning an
object of that class.
Next, we need a run
method that actually starts the thread. This
can't be done in the ctor: when code
would be registered as a
thread of execution in the base class ctor, the superclass would
not yet be fully created and calling code
would be quite invalid
and dangerous. run
does its job by registering the
dispatch
function as a thread, giving that thread the object
pointer as a startup parameter; since dispatch
is static, it has a
prototype that matches the void(*)(void*)
parameter of
BeginThread
.
void Thread::run() { // Don't start two threads on the same object if (running) return; // Create an OS thread, using the static callback BeginThread(Thread::dispatch, this); running = 1; }
So finally, dispatch
is called and performs the step from a
procedural callback to the callback object:
void Thread::dispatch(void* thread_obj) { // Call the actual OO thread code ((Thread*)thread_obj)->code(); // After code() returns, kill the thread object delete (Thread*)thread_obj; }
A real-world thread class has to consider a few things we have ignored here. These things include:
Developed and tested on Windows NT, this is the source for a little example program that implements the above in the real world. If you run it, you get something similar to the following output:
[\cscene\callback]callback Started thread #80 for dice1 Started thread #84 for dice2 dice1 rolled 1 dice2 rolled 1 dice2 rolled 3 dice1 rolled 1 dice2 rolled 4 dice1 rolled 6 dice1 rolled 3 dice2 rolled 3 dice1 rolled 1 dice1 rolled 4 dice2 rolled 4 dice2 rolled 3 dice1 rolled 1 dice2 rolled 6 dice1 rolled 2 dice2 rolled 2 dice1 rolled 1 dice2 rolled 4 dice2 rolled 1 dice1 rolled 4 dice1 rolled 5 dice2 rolled 1 dice1 rolled 3 dice2 rolled 3 dice1 rolled 2 dice1 rolled 6 dice2 rolled 2 dice1 rolled 1 dice2 rolled 3 dice1 rolled 4 dice1 rolled 5 dice2 rolled 3 dice2 rolled 6 dice1 rolled 4
Have fun!
This article is Copyright © 1997-98 by Jürgen Hermann
and
Copyright © 1999 by C-Scene. All Rights Reserved.