Services Products Downloads About Us Contact
 
My Account
 
Services
Consulting
Business Solutions
Skills
Training
Support
 
Professional Products
molib™
the sandbox™
sswf™
odbcpp™
Trinity℠
Order Made!®
PHP eFax
PHP Pay Junction
Instant Cart™
DLF™
Documents & White Papers
 
Consumer Products
Turn Watcher™
Download Our Products
CafePress
Forums
 
About Us
History
News Room
Blogs
FAQ
Terms and Conditions
Your Privacy
 
Contact Us
Contact
Press Kit
Jobs
Service Request
 
 
 
Languages Available:  français   español 
 

CLASS

moThread

NAME

Constructor - initialize a thread object
Destructor - suspends the thread if still running and cleans up

VERSION

Version: 1.2.0

SYNOPSIS

moThread(const moName& name, const moRunner *runner);
virtual ~moThread();
static bool ThreadingAvailable(void);

PARAMETERS

name - the name of this thread
runner - the task to run by this thread

DESCRIPTION

The moThread and moThread::moRunner classes are used by the mo library users to create applications using multiple threads.

The constructor initializes the moThread object but it doesn't start the thread. You need to call Start() in order to get the thread running. The code run by the thread will be the Run() function of the specified runner. The runner cannot be changed for the lifetime of the moThread object.

The name passed on the moThread object is used to broadcast the death event of the thread. It is thus important to properly choose this name.

The pointer to the runner can be set to null in which case the new thread is considered empty and the Start() function will always fail.

The destructor calls the Stop() function which requests the moRunner to stop. Note that we have a separate object because when the destructor of the moThread is called, the moThread object is in an invalid state (virtual tables will have been changed to match the moThread virtual functions) and thus it cannot be the object including the code for the thread. This means you cannot derive your class from moThread and moRunner (it is actually strongly suggested that you never derive from moThread.)

The ThreadingAvailable() function can be used to know whether threads are available in the running application. It returns true if threads can be created (this doesn't guarantee that threads will successfully be created though.)

This is a static function and thus you don't have to create an moThread object to call it, i.e.:

if(!moThread::ThreadingAvailable()) { fprintf(stderr, "ERROR: my program requires threads\n"); exit(1); }

To implement a thread correctly you should do the following in your class derived from moThread::moRunner:

. Override the Init() function; do initialization you need to do for the new thread to function properly; if your initialization succeeds, return true; if you want the parent thread to wait for this initialization to be complete then also implement the IsWaitStartedRequired() and return true

. Override the Run() function; DO NOT IMPLEMENT A LOOP! The loop is outside and you may not use it by leaving the run count to the default value (i.e. run once); if Run() can fail, return false, it will stop the thread; otherwise always return true

. Override the Cleanup() function; do all the necessary cleanup of resources allocated by the thread.

Example:

bool Init() { m_state = 0;

return true; }

bool Run() { switch(m_state) { case 0: // do this m_state = 1; break;

case 1: // do that m_state = 2; break;

case 2: // do more and then repeat m_state = 0; break;

}

return true; }

void Cleanup() { if(m_state == 1) { // do some necessary cleanup } }

RETURN VALUE

The ThreadingAvailable() function returns true if threads
capability is available, false otherwise.

SEE ALSO

GetName(), IsRunning(), Start(), Stop(), CallDeathEventPipes()

BUGS

The runner objects can be owned by at most one moThread at a time. However they can be handled by some temporary moThread and then another and another... This rule is being enforced at run time by a throw in the constructor whenever an moRunner is being attached to a second moThread.

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moThread

NAME

GetName - get the name of this thread

VERSION

Version: 1.2.0

SYNOPSIS

mo_name_t GetName(void) const;

DESCRIPTION

Each moThread object needs to be given a name when created.

This name is constant and can be read with the GetName() function.

RETURN VALUE

the name of the thread as defined by the moNamePool

SEE ALSO

Constructor

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moThread

NAME

IsRunning - check whether this thread is currently running
Start - start the thread
Stop - stop the thread and eventually wait for it to be stopped
Continue - whether the Stop() function was called

VERSION

Version: 1.2.0

SYNOPSIS

virtual bool IsRunning(void) const;
bool Start(run_count_t run_count = RUN_COUNT_DEFAULT);
void Stop(bool wait = false);
bool Continue(void) const;

PARAMETERS

run_count - the number of time the Run() function will be called
wait - whether the Stop() command waits for the thread to end

DESCRIPTION

Once a thread was initialized, you can start it (in effect, call its Init()/Run()/Cleanup() functions) by calling the Start() function.

The Start() command works if:

. run_count is not RUN_COUNT_STOP

. a valid runner was attached to the moThread (i.e. not null)

. the thread is not already running

. the runner is ready

. the system can create one more thread

Once started, the system thread runs until one of the following events occur:

. the Init() function returns false (in which case the Run() function will not even be called)

. the run_count parameter reaches RUN_COUNT_STOP (zero)

. the Run() function returns false

. the Stop() function is called, forcing the run_count counter to the value RUN_COUNT_STOP (within the thread, you can test this value with a call to Continue())

When the thread dies, the following happens:

. call the Cleanup() function

. set the IsRunning() flag to false

. post a message to all the death event handlers

. signal the thread which called Stop(), if any, with the wait flag set to true

You can thus stop a thread by calling its Stop() function. Note that the Stop() function is likely to return before the thread dies unless you set the wait flag to true. A thread can call its own Stop() function in which case it will not block (i.e. ignore the wait flag.)

The IsRunning() function returns true if the thread is currently running. Note however that the running flag is set to false before the death events are posted. This gives a chance to other threads to try restarting this thread before it really exits. This will block the new parent thread until the child is finished posting events.

The Continue() function returns true until the Stop() function is called or the run_count counter reaches zero (i.e. RUN_COUNT_STOP). This function is usually called from the Continue() defined in the moRunner class.

NOTES

All the functions are supposed to be thread safe.

RETURN VALUE

IsRunning() returns true when the thread is currently working

Start() returns false if a new system thread cannot be created
either because of a lack of resources or because there is
already a system thread attached to this moThread object or
you set the run_count parameter to RUN_COUNT_STOP or the
runner is not ready

Continue() returns false until the thread starts; then it
returns true until the Stop() function is called or the run_count
counter reaches zero; this flag is used to break the thread loop

SEE ALSO

CallDeathEventPipes(), AddDeathEventPipe()

BUGS

Multiple threads can call the Stop() function with the 'wait' parameter set to 'false'.

When the parameter 'wait' is to be set to 'true' you need to make sure only one single thread calls the Stop() function. This is because only one thread can be waiting for the other until it dies. If you need to know when the thread dies, use the death event facility instead (see the AddDeathEventPipe() function).

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moThread

NAME

AddDeathEventPipe - event pipe to receive death events
RemoveDeathEventPipe - remove a death event pipe from a thread
PostToDeathEventPipes - send a death event to all registered pipes

VERSION

Version: 1.2.0

SYNOPSIS

void AddDeathEventPipe(const moEventPipe& event_pipe);
void RemoveDeathEventPipe(const moEventPipe& event_pipe);
void PostToDeathEventPipes(moBase *result) const;

PARAMETERS

event_handler - an event pipe to sent death events

DESCRIPTION

The AddDeathEventPipe() function is used to add an event pipe to which the death of a thread is to be reported. You can add the same handler multiple times if you want. It will work, thought, it probably isn't very useful since it will duplicate the message sent to your pipe.

Note that the death is reported by the thread right before it dies and thus it may still be running, system speaking, whenever this event arrives to your handler.

If after a while you are not interested in receiving death events, you can use the RemoveDeathEventPipe() function to remove your event pipe. Note that if you added the same handler multiple times, you will only remove one instance per call to RemoveDeathEventPipe().

The PostToDeathEventPipes() is currently public. It may become private or protected. At this time, it isn't known whether anyone will even need to call that function directly, but by doing so you can simulate the death of a thread.

The result parameter passed to PostToDeathEventPipes() is the result set in your moRunner before it returns. Do not forget that this result is cleared (set to null) each time you restart the thread.

NOTES

These functions are protected with a mutex when handling the list of pipes. Thus it is safe to add and remove pipes even when a handler is called. Note however that removing a handler which has not yet been called will not prevent that handler from being called if it was in the list before the call to the PostToDeathEventPipes() function.

SEE ALSO

IsRunning(), Start(), Stop()

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moThread::moRunner

NAME

Constructor - initialize the runner
Destructor - clean up the runner
GetClassName - returns the name of the moRunner class

VERSION

Version: 1.2.0

SYNOPSIS

moThread(void);
~moThread();
GetClassName(void);

DESCRIPTION

The constructor ensures that the default f_thread pointer is null.

The destructor does nothing in release. In debug it checks that the f_thread pointer is null. If not it is a bug since the runner should not be deleted if still attached to an moThread object.

The GetClassName() function returns the name of the class.

SEE ALSO

moThread

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moThread::moRunner

NAME

GetThread - returns a smart pointer to the thread
attached to this runner

private:
SetThread - attach a thread to this runner

VERSION

Version: 1.2.0

SYNOPSIS

moThreadSPtr GetThread(void) const;
bool SetThread(const moThread *thread);

DESCRIPTION

The GetThread() function returns a smart pointer of the thread attached to this runner. You must save that thread in a smart pointer as well. It will return a null pointer whenever the runner is not attached.

SetThread() is a private function called by the moThread to set itself as the owner of the runner. Note that there can be only one owner. Trying to attach more than one thread to a runner fails with a throw of an moError in the moThread constructor.

SEE ALSO

moThread

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moThread::moRunner

NAME

IsWaitStartedRequired - check whether this thread
requires the parent to wait until the child
initialization is complete
IsReady - check whether the runner is ready to start
Init - initialize the new thread
Run - run the new thread once (see below)
Cleanup - cleans the thread before exiting
Continue - test whether the thread should stop

VERSION

Version: 1.2.0

SYNOPSIS

virtual bool IsWaitStartedRequired(void) const;
virtual bool IsReady(void) const;
virtual bool Init(void);
virtual bool Run(void);
virtual void Cleanup(void);
bool Continue(void) const;

DESCRIPTION

The IsWaitStartedRequired() function is used to know whether the Start() function needs to wait on the Init() function to complete before to continue. The IsWaitStartedRequired() function is called only once when the runner is passed to the constructor. This is important to ensure consistency. Yet, since this should always return true when overridden, it should not be a problem.

The IsReady() function is called from the parent thread before the child is created to make sure that the moRunner is ready to start. If that call returns false, then the child is not started and the Start() function returns false. The default function always returns true.

The Init() function is called once when the thread starts. It can be used to initialize the new thread. When your IsWaitStartedRequired() function returned true in the moThread constructor, the parent thread blocks until this Init() function returns.

The default Init() function just returns true.

The Run() function is called repeatitively by the new thread until the thread is stopped. You can stop the thread by: a) returning false from the Run() function; b) calling the Stop() function from any thread; c) reaching the maximum number of times the Run() function is supposed to be called (number which is specified with the Start() function.)

You can prevent (a) by always returning true from the Run() function. You can prevent (c) by calling the Start() function with RUN_COUNT_FOREVER. You can prevent (a), (b) and (c) by never returning from the Run() function.

If your Run() function is very long, you may want to break it up in smaller tasks which you perform one after another. You can also call the Continue() function within the Run() function to know whether another thread requested you to Stop() [i.e. if(!Continue()) return false;]

The Cleanup() function is called once the thread was asked to stop. This function is expected to release any resource used by the thread.

The default Cleanup() function does nothing.

After the Cleanup() the child thread will post its death event. Finally, if Stop() was called by another thread and that thread requested to wait on the child death (i.e. a blocking Stop() call), the child signals the waiting thread.

The Continue() function can be called from within your functions in the running thread to know whether the user requested the thread to stop (i.e. called the Stop() function.) This can be useful if you have a very long process which can cleanly be stopped mid-way.

Note that the Continue() function is public so other threads can check whether this thread is stopped or about to stop. Yet, to make sure of that, you should call the corresponding moThread::IsRunning() function instead.

SEE ALSO

IsRunning(), Start(), Stop()

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moThread::moRunner

NAME

GetData - retrieve the pointer to the data passed to the thread
SetData - set the data to be used by the thread
GetResult - the current result of the thread
SetResult - set a new result for the thread

VERSION

Version: 1.2.0

SYNOPSIS

moBaseSPtr GetData(void) const;
void SetData(moBase *data);
moBaseSPtr GetResult(void) const;
void SetResult(moBase *result);

PARAMETERS

data - a pointer to some moBase object
result - a pointer to some moBase object

DESCRIPTION

These functions manage the data (input) and result (output) of a thread. The functions are mutually thread safe meaning that only one thread at a time can change or read the data or result pointer.

Note that the GetData() and GetResult() functions return a smart pointer. This means the object will not be deleted between a call to that function and the time the caller uses the pointer. It is your responsibility to save that pointer in a smart pointer.

SEE ALSO

IsRunning(), Start(), Stop()

BUGS

These functions by themselves are safe, however changing the data pointer or the result objects may not be thread safe. Refer to that object for more information.

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moTaskManager::moTask

NAME

Constructor - initialize a task object
Destructor - uninitialize a task object

VERSION

Version: 1.2.0

SYNOPSIS

DESCRIPTION

The moTask object will be used in an moTaskManager object in order to run tasks in a seperate thread.

The idea behind having a task is to do some work asynchroneously without overloading the processor. Thus, instead of having one thread per task, you have many tasks which will be run in a single thread. Once a task if finished, you need to kill it. It will then automatically be removed from the task manager.

The moTaskManager will call the Run() function of every task it holds, one after another. It then starts again. The Run() function is expected to return after a relatively small amount of time.

A task can either be repetitive, so each time the Run() function is called it does the same thing over and over again, or it can be written using a state. Thus, it can do some specialized work each time the Run() function is called.

SEE ALSO

Run(), SetState(), SetOrder(), SetPriority()

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moTaskManager::moTask

NAME

SetState - change the current state of a task
GetState - get the current state of a task

VERSION

Version: 1.2.0

SYNOPSIS

void SetState(state_t state);
state_t GetState(void) const;

PARAMETERS

state - the new state

DESCRIPTION

An moTask object can be in one of the following states:

MO_TASK_STATE_STARTING MO_TASK_STATE_RUNNING MO_TASK_STATE_SLEEPING MO_TASK_STATE_ZOMBIE

An moTask is marked as STARTING until it is added to an moTaskManager, at which time its state becomes RUNNING meaning its Run() function will be called when allowed.

You can temporarilly stop a task by setting its state to SLEEPING. Another task or thread can later revive the task by changing its state back to RUNNING. Note that setting the state to SLEEPING doesn't put a task to sleep if it is currently running. It will really be asleep once its Run() function returns. A task can put itself in SLEEPING mode.

At any time, you can set the state to ZOMBIE to kill a task. If you try to add a ZOMBIE task to an moTaskManager, then nothing happens. When a task is not part of a task manager, then it's state can be set to STARTING again.

NOTES

The fact that a task can't be changed from ZOMBIE to RUNNING or SLEEPING seems to make sense to me at this time. You can detach the task from the task manager and then reattach it after you changed the state back to STARTING. This ensures you know what you're doing.

RETURN VALUE

The GetState() function returns the current state of
a task.

SEE ALSO

Run(), GetOrder(), GetPriority()

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moTaskManager::moTask

NAME

SetOrder - change the order of this task
GetOrder - get the order of this task
SetPriority - change the priority of this task
GetPriority - get the priority of this task
NeedSorting - whether the priority or order changed
Compare - compare two tasks together for sorting purposes

VERSION

Version: 1.2.0

SYNOPSIS

void SetOrder(int order);
int GetOrder(void) const;
void SetPriority(int priority);
int GetPriority(void) const;

bool NeedSorting(void) const;

virtual compare_t Compare(const moBase& object) const;

PARAMETERS

order - the new order
priority - the new priority
object - another moTask object

DESCRIPTION

Tasks are sorted first by priority and second by order. The priority and order can be changed at any time, but only when a task returns does it have an effect on the list of tasks.

The tasks with a higher priority (i.e. 10 is higher than 5) will always be executed until they fall asleep. If they don't fall asleep, then tasks with a lower priority won't get any CPU time for themselves.

The order can be used to execute certain tasks before others. Contrary to the priority, the lower order is executed first. Thus, a task with an order of 10 will be executed after a task with an order of -10.

By default, the order and priority of a task are set to 0.

RETURN VALUE

The GetOrder() function returns the order value.

The GetPriority() function returns the priority value.

SEE ALSO

Run(), GetState()

BUGS

At this time this is somewhat bogus since it is not multithreaded aware. However, it should still work in most cases. The main problem is that we would need a global mutex to make sure everything is properly synchronized. Thus the sorting would be done while the mutex is locked by a task manager.

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moTaskManager::moTask

NAME

GetName - get the name of this task

VERSION

Version: 1.2.0

SYNOPSIS

mo_name_t GetName(void) const;

DESCRIPTION

Get the name of the task. Any task is required to have a name.

RETURN VALUE

The name of the task as defined in an moNamePool.

SEE ALSO

Run(), Constructor

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moTaskManager::moTask

NAME

Attach - attach this task to a task manager
Detach - detach this task from a task manager

VERSION

Version: 1.2.0

SYNOPSIS

void Attach(void);
void Detach(void);

DESCRIPTION

Each time a task is added to a task manager, that task manager calls the Attach() function. Each time a task gets in a Zombie state, the Detach() function is called. This ensures we know whether the task is currently part of a task manager or not.

Note that a task can be added to different task managers. It is the responsability of the creator of such task to make sure they will run properly in multiple threads in parallel.

SEE ALSO

Run(), Constructor

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moTaskManager

NAME

Constructor - initialize a task manager
Destructor - cleans up a task manager
GetClassName - retrieve the name of the class

VERSION

Version: 1.2.0

SYNOPSIS

moTaskManager(void);
~moTaskManager();
virtual const char *GetClassName(void) const;

DESCRIPTION

One will create a task manager whenever multiple tasks need to run in the background one after another (opposed to separate threads which would run in parallel.)

SEE ALSO

Run(), Constructor

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moTaskManager

NAME

IsRunning - check whether the task manager is currently running

VERSION

Version: 1.2.0

SYNOPSIS

virtual bool IsRunning(void) const;

private:
virtual bool Init(void);
virtual bool Run(void);
virtual void Cleanup(void);

DESCRIPTION

The task manager thread can be running continuously. However, it can be waiting for tasks to be assigned to it or have only sleeping tasks and in that case it will be considered as not running.

The IsRunning() function returns true if the moTaskManager thread was started and some of the tasks assigned to it are running. Note that if you need to know whether the corresponding thread is running, then call IsRunning() on the thread and not the task manager.

The Run() function is that one function which will handle the list of tasks to know which one needs to be run next and which ones need to be removed from the list of tasks. This is an internal function.

The Init() function initializes the task index. It will be re-initialized each time you restart the Task Manager thread.

The Cleanup() function ensures that the internal lock isn't in effect when the thread ends.

RETURN VALUE

IsRunning() returns true when the task manager is
considered running

SEE ALSO

Start(), AddTask()

BUGS

To stop the Task Manager thread you can call Stop() this will stop all the tasks. If there was no task or all tasks were sleeping then the Stop() may need up to 2 seconds to really stop the Task Manager thread.

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


CLASS

moTaskManager

NAME

AddTask - add a task to the task manager
RemoveTask - remove a task from the task manager

VERSION

Version: 1.2.0

SYNOPSIS

void AddTask(moTask& task);
void RemoveTask(moTask& task);

DESCRIPTION

Once you created a task manager, you can add and remove tasks from it. A task is added with AddTask() and removed with RemoveTask(). The same task will be passed to either function.

Note that the RemoveTask() function is relatively slow and is not usually necessary. That is, if you set the state of a task to MO_TASK_STATE_ZOMBIE, then the task will automatically be removed from the list of tasks of the manager as soon as possible.

A task you add can't be in the MO_TASK_STATE_ZOMBIE state or it will be ignored. It can however, already be running or asleeping as handled by another task manager.

NOTES

At this time, the fact that a task could be shared between multiple task managers is not well supported. It will have to be heavily debugged before to be used and this note can then be scrapped out.

RETURN VALUE

true when the task manager is considered running

SEE ALSO

Start(), AddTask()

BUGS

Note that you can currently add the same task multiple times. This feature is likely to be removed in the future.

The same task can be defined in multiple task managers, however, a task has only one state shared among all of the task managers. This means the same task will fall asleep in all task managers or be running in all task managers. Similarly, if its state becomes ZOMBIE, then it will die and be removed from the all task managers.

COPYRIGHTS

This documentation and the code being documented is proprietary and cannot be duplicated without the express and written consent of Made to Order Software Coporation.

Copyright (c) 1999-2007 by Made to Order Software Corporation
All rights reserved.

AUTHORS

Alexis Wilke, Doug Barbieri


 

Links:
  molib
  the sandbox