

                                                Logical Operators
                                Professional Software Development
                                          & Computer Consultation
                                                                 
                                              707-A Dunbar Avenue
                                                     P.O. Box 815
                                            Dunbar WV  25064-0815
                                         Telephone: (304)768-5079

                                THE ALERTDRIVER C++ CLASS LIBRARY
                                       EXCERPTS FROM USER'S GUIDE
                                                     Version 1.00
                                                                 
  References to businesses, organizations, and individuals
 within this publication are supplied only for informational
    purposes and in no way imply or infer endorsement or
  recommendation of the products and/or services of Logical
 Operators or its associates or affiliates unless explicitly
                           stated.
   
   All brand names, trademarks, and registered trademarks
 appearing throughout this document are properties of their
   respective owners. The AlertDriver Class Library source
   code, object code, and all associated documentation are
 copyrighted 1994 by Logical Operators. All Rights Reserved.
                   Part number ADUG940125.



Preface


At Logical Operators, we encountered many problems, portability
issues, and maintenance issues when writing error-reporting
code for the reusable libraries used in our applications.  Nested
if-then-else code detracted from the clarity of algorithms,
hard-coded error messages caused reusability problems, and
re-routing messages from the screen to other devices (such as
printers) was difficult to say the least.

Our solution to these problems, the AlertDriver Class Library,
avoids all of these problems while providing a new software
library with modularity, encapsulation, a consistent call
interface, and efficient code size and speed. The AlertDriver
Class Library is reusable and extensible to multiple application
environments and hardware configurations. And perhaps most
importantly, this easy-to-use class library provides the
flexibility of multiple error-reporting methods within the same
program while including the capability to easily enable, disable,
or change reporting methods at both compile-time and run-time!



Chapter 2           AlertDriver Class Library Concepts
                    

More Than Just Errors

The   AlertDriver  Class  Library  does  more  than  just  simple
error-reporting,  it  alerts the user to  virtually  any  program
condition   during   execution  (hence  the  name).   There   are
several  types  of  program  conditions,  or  alerts,  which  you
may  wish  to  present  to  the user  during  program  execution:
errors, information, messages, and warnings.

Errors,  the  most  serious  types  of  alerts,  occur   when   a
program  detects  a  state  which  makes  continuation   of   the
current   branch   of   execution  impossible.   In   interactive
applications,  an  error  usually stops program  execution  until
acknowledged  by  the  user. A typical error  might  be:  "Cannot
find file C:\YOURFILE.DAT."

Information  alerts  occur whenever an  application  informs  the
user  of  a  program  condition or the occurrence  of  an  event.
Information   alerts  do  not  reflect  execution  problems   and
exist  only  for  the  convenience of the  user.  In  interactive
applications,  an  information  alert  stops  program   execution
until  acknowledged  by  the  user. A typical  information  alert
might be:  "File C:\YOURFILE.DAT was successfully loaded."

Messages  also  occur when the application informs  the  user  of
a   program  condition.  Like  information  alerts,  messages  do
not   reflect   execution  problems  and  exist  only   for   the
convenience  of  the  user. Unlike information  alerts,  messages
do  not  stop  program  execution  in  interactive  applications.
Once   posted,  a  message  remains  active  until  replaced   by
another   message  or  cleared  by  the  application.  A   sample
message might be:  "Loading file C:\YOURFILE.DAT..."

Warnings   (sometimes   referred   to   as   confirmations)   are
reported   when   the  application  prompts  the   user   for   a
decision.  Warnings  are  typically  posed  as  a  question  with
three  possible  answers:   YES, NO, or  CANCEL.  Warnings  pause
program   execution  until  the  user  makes  a  decision,   then
continue   program   execution  based  upon  that   decision.   A
common   warning   is   "Do   you   want   to   overwrite    file
C:\YOURFILE.DAT?"

Although  the  AlertDriver Class Library provides  the  means  to
deal  with  all  of  these  types of alerts,  this  chapter  will
concentrate   only   on   error   handling   to   simplify    the
discussion.  Please  keep  in mind that  the  concepts  of  error
handling also apply to each of the other types of alerts.


The AlertableObject Concept

In   most   class   libraries,  the  majority  of   classes   are
ultimately  descendants  of a single root  class  (usually  named
something    like    Object    and    having    little     actual
functionality).  Most  new  classes  created  by  users  of   the
library  inherit  the properties of an existing  class,  even  if
the  class  is  as  basic  as  Object.  Thus,  most  objects  (or
class   instantiations)  in  an  application  can  be  referenced
and treated as a lowest common denominator - an Object.

Now  let's  suppose  that  the root  class  has  the  ability  to
report   errors  it  encounters  during  method  executions.   If
such   a   class   exists,  it  becomes  easy  to   derive   more
specialized  classes  while  simply  inheriting  the  ability  to
report  errors.  Our  programs can invoke  the  member  functions
of  an  object,  knowing  that if an  error  occurs,  the  object
itself  will  report  the  problem  to  the  user!  Our  programs
only  need  to  be  aware  of  the  success  or  failure  of  the
member function's execution.

If  such  a  capability could be added to the  root  class  of  a
commercial  application  framework,  then  the  majority  of  the
framework's  classes  (and their descendants)  would  be  capable
of   reporting  their  errors  to  the  user  in  a  standardized
manner   right   out   of   the  box   without   any   additional
programming!   Of  course,  not  every  programmer   would   have
access  to  the  framework's source code to implement  this,  and
we   may   not  want  to  add  additional  overhead  to  such   a
rudimentary  class  anyway.  So in the  true  spirit  of  object-
oriented   programming,  we  would  design  a  new  class   which
directly  inherits  the  capabilities of the  framework's  Object
counterpart  and  adds  some  basic  error-handling  capabilities
(see Example 4).

Example  4  - A sample class declaration which could  add  error-
handling   capabilities  to  the  base  class  (Object)   of   an
application  framework.  The AlertDriver  class  library  creates
an error-handling class in a similar manner.

class AlertableObject : public Object
{
   /*add member functions below to
     enable error handling*/
   ...
};   //class AlertableObject

The  AlertDriver  Class  Library  includes  a  class  similar  to
that  in  Example  4  and implements the  class  in  such  a  way
that  it  can  either  be  used  without  a  class  hierarchy  or
integrated  into  practically any  C++  class  library:   new  or
existing,  commercial  or  private.  We  have  named  this  class
AlertableObject:   it  provides the functionality  to  alert  the
user  of  errors.  By  serving as a base  class,  AlertableObject
provides   a  common  calling  interface  to  our  error-handling
member    functions.   In   addition,   because   AlertableObject
contains   a   public  member  function  interface,  our   error-
handling    routines   are   accessible   from    program    code
throughout the application.


Benefiting From AlertableObject

By   making   AlertableObject   a   direct   descendant   of    a
framework's   root   class   in  the  class   hierarchy,   error-
handling   capabilities  can  easily   be   inherited   by   your
classes  and  their  descendants. Of course,  classes  which  are
derived   from   the  root  class  (and  bypass  AlertableObject,
like  the  existing classes in the framework)  will  not  inherit
the error-handling capabilities.

So  what  other  benefits  exist from using  the  AlertableObject
class  in  an  application? Suppose we have a code  module  which
has   just   invoked  a  member  function  of  a  descendant   of
AlertableObject.  Our  calling  module  does   not   report   any
errors   which   occur  within  the  object's   member   function
(that's   the  object's  responsibility).  However,  our  calling
module   may   be  interested  in  determining  if   the   member
function was able to execute successfully, and if not, why.

This   simple  scenario  is  an  excellent  reason  to  have   an
object's  member  functions return error codes.  It  is  easy  to
declare   a   group   of   constants   which   represent    error
conditions  for  each  class  (see  Example  5).  Not  only  does
this   practice  enforce  good  programming  by  removing   hard-
coded  values  from  the member functions  and  calling  modules,
but  a  wide  range  of  values can  be  represented  within  the
code  returned  by  each member function. In  addition,  returned
error   codes   remove   the  need  for  an  unnecessary   global
variable  (such  as  errno) or class data  member  to  store  the
most recent result of a method execution.

Example   5   -  Error  constants  can  easily  be  created   and
returned from AlertableObjects.

const long int NOERR = 0;
const long int FILENOTFOUND = 1;
const long int OUTOFMEMORY = 2;
...
class MyObj : public AlertableObject
{
   public:
      virtual long int MemFunc1(void);
         //returns error code
   ...
};   //class MyObj

By  testing  the  returned error codes, the module  calling  such
member  functions  can  smoothly recover  from  errors  occurring
within    the   object   while   avoiding   the   "matching-else"
syndrome   for  error  reporting  (see  Example  6).  A  pleasant
side-effect   is   that  the  source  code  becomes   much   more
readable and understandable.

Example   6   -  The  same  algorithm  used  in  Example   1   is
implemented   here,   using   calls   to   an   object's   member
functions   rather  than  "plain"  function  calls.  The   object
MyObj  is  derived  from  class AlertableObject,  allowing  MyObj
to  report  errors  to  the  user when  they  occur.  Notice  how
much  more  readable  the code below appears  than  the  code  of
Example 1.

...
if (MyObj.func1() == NOERR)
   if (MyObj.func2() == NOERR)
      if (MyObj.func3() == NOERR)
         ...

Perhaps  the  biggest  benefit of the  AlertableObject,  however,
lies   in   its  encapsulation.  Because  the  object  is   self-
contained  and  able  to  report its own errors  in  a  platform-
independent   manner,   it   can   be   linked   into    multiple
applications,  none  of  which has to  deal  with  reporting  the
errors  which  could occur internally. Imagine  how  much  easier
your  next  programming  project would be  if  you  could  simply
plug  some  of  your existing classes together and  forget  about
handling  the  errors,  confident  that  they  will  be   handled
automatically! With the AlertDriver Class Library, you can!


Introducing The AlertDriver

The  AlertDriver  is  the class in the hierarchy  which  provides
the     platform-    and    hardware-dependent    error-reporting
capabilities  to  objects  derived  from  AlertableObject.   Each
object   derived   from  AlertableObject  uses   an   AlertDriver
object   to   perform  the  actual  reporting   of   each   error
message.  The  AlertDriver  object  determines  how  each   error
message  is  reported  using  the  API  (application  programming
interface)  calls  and  hardware available  to  the  application;
hence,  each  AlertDriver  object is  an  "error  device  driver"
for   the  application  environment.  Because  AlertDrivers   are
implemented  as  classes, any AlertDriver can  be  subclassed  by
the  application  programmer to change or  control  its  specific
actions.

The  AlertDriver  class library supplies  classes  which  can  be
used   to   report   errors   to  the  screen   for   interactive
applications,  or  log  errors to a  printer  or  disk  file  for
programs   which   normally   run  without   human   supervision.
Additional  AlertDrivers can be obtained  or  written  for  other
platforms  and/or  hardware  devices  and  linked  into  programs
written  for  the  AlertDriver Class Library with  no  change  to
the   underlying  application  logic  or  AlertableObject  source
code.

With  the  concepts  described to this point and  their  inherent
programming   power   and  flexibility,  most   class   libraries
would   stop   here.  However,  the  AlertDriver  Class   Library
provides   solutions  to  even  more  programming  problems.   An
AlertDriver  object  itself  can  reference  another  AlertDriver
object,  effectively  chaining,  or  linking,  the  AlertDrivers.
By   linking   the  AlertDrivers  for  an  AlertableObject,   the
AlertableObject  can  report  a single  error  in  multiple  ways
(for  example,  an  error  can  be reported  to  the  screen  and
logged  in  a  disk  file).  AlertDrivers  can  be  attached  and
detached  from  AlertableObjects in  any  combination  at  either
compile-time  or  run-time  for maximum  program  efficiency  and
flexibility.

Each  AlertDriver  can  also  have  its  actions  controlled   at
either   compile-time  or  run-time  via  a  set  of   processing
flags.   Error-handling,   for  example,   can   be   temporarily
turned  on  or  off  for each AlertDriver  without  the  need  to
disconnect   or  destroy  the  driver.  Other  useful  processing
flags,  which  allow  a tremendous amount  of  control  over  the
AlertDriver during run-time, are also available.

The  AlertDriver  library also mirrors the  concepts  of  client-
server   technology:   each  AlertableObject  can  be  considered
to   be   a  client,  each  AlertDriver  a  server.  The   client
initiates  an  action  (i.e.:   report  an  error  message)   and
each  server  has  the ability to perform that  action.  Multiple
clients  can  be  attached to a single server and  a  client  may
be served by multiple servers.

The  ability  to  connect several AlertableObjects  to  the  same
AlertDriver   allows   applications  to   retain   all   of   the
advantages  previously  discussed  while  minimizing  the  amount
of  memory  overhead  needed  to implement  those  features.  For
example,   in   a   spreadsheet  application,  each   spreadsheet
object  could  can  be derived from an AlertableObject.  If  each
cell   required  its  own  AlertDriver,  then  available   memory
would  decrease  rapidly  as the spreadsheet  grew.  However,  by
allowing  each  cell  to share a common AlertDriver,  the  memory
needed  to  implement  AlertDriver  processing  is  held   to   a
small,   fixed   amount  (the  amount  needed  to  allocate   the
common   AlertDriver).   Since  all  cells   in   a   spreadsheet
normally   report   errors  in  the  same  manner   anyway,   the
unnecessary  allocation  of  duplicate  AlertDriver  objects   is
avoided.

In   the   next   chapter,  we  will  discuss  how  easily   your
programs  can  use  the  AlertDriver Class  Library  as  we  step
through a tutorial.



Chapter 3           An AlertDriver Tutorial
                    

Creating New Objects

To  use  the  AlertDriver Class Library,  you  must  include  the
following line in your source code file:

#include <alertobj.h>

The  ALERTOBJ.H  header  file contains the  declaration  for  the
AlertableObject    class.    (The    implementation    of     the
AlertableObject  class  is supplied in  a  separate  object  code
file   which   is  platform-dependent  -  see  the  Compiling   &
Linking  chapter  for  details  on  compiling  and  linking  your
code with the supported compilers/platforms.)

To  create  classes  of your own, you simply  derive  your  class
from  AlertableObject  using  public inheritance.  (For  purposes
of  demonstration,  we  will present  a  simple,  and  admittedly
contrived,   sample  class  named  Integer,   which   by   itself
doesn't  really  do  anything  useful,  but  will  allow  us   to
demonstrate   the  proper  syntax  and  implementation   of   the
AlertDriver   library   concepts  -  sample  platform-independent
source   code   implementing  Integer  is   included   with   the
AlertDriver   Class  Library.)  Your  class  will   automatically
inherit   the   member  functions  and  data  of  AlertableObject
(see Example 7).

Example  7  -  To  derive  your own classes,  derive  your  class
from AlertableObject using public inheritance.

class Integer : public AlertableObject
{
   ...
   /*declare class-specific member
     functions and data members here*/
   ...
};   //class Integer


Programmer-Defined Codes, Detection, & Reporting

Once    you    have   inherited   the   capabilities    of    the
AlertableObject   class,   you   will   want   to   access   that
functionality   from  your  member  functions.  There   are   two
basic  ways  of  using  the AlertableObject member  functions  to
alert  the  user  of  program conditions:  the  "quick-and-dirty"
method and the "preferred" method.

The Quick-And-Dirty Reporting Method
The   quick-and-dirty  method  of  using  the  AlertDriver  Class
Library   is   usually   used  during  program   testing   and/or
debugging.  Using  this  method, your  object  passes  a  literal
string  to  the  attached AlertDriver to alert the  user  of  the
program's  condition.  The  quick-and-dirty  method  retains  the
platform-independent   output  of  the   AlertDriver,   but   the
messages  themselves  are hard-coded into  the  program,  clearly
not  the  most  elegant solution. However,  this  method  is  the
quickest  and  easiest  to  use  for  quickly  getting   a   test
program  to  work  or  for  porting  existing  programs  to   the
AlertDriver  Class  Library. Programs written  using  the  quick-
and-dirty   method  can  easily  be  ported  to   the   preferred
method later.

To  use  the  quick-and-dirty method,  your  code  calls  one  of
the   Report*()   member   functions.   There   are   four   such
functions   (see   the  Class  Library  Reference   chapter   for
details),   one   for   each  type  of   alert:    ReportError(),
ReportInfo(),      ReportMessage(),     and      ReportWarning().
(ClearMessage(),  a  related function used  in  conjunction  with
member   function  ReportMessage(),  clears  the   last   message
posted.)

Example  8  shows  how  the  code  in  Integer::TestAlertDriver()
makes  calls  to  these  functions, passing  literal  strings  to
each;  the  AlertDriver  for  the Integer  object  reports  those
strings to the appropriate output device/platform.

Example  8  -  Calling  the Report*() member functions  inherited
from   AlertableObject.   Each  of  these   inherited   functions
passes the string along to the object's AlertDriver.

void Integer::TestAlertDriver(void)
{
   ...
   //test literal strings
   ReportMessage("Literal:  This is a message string.");
   ReportError("Literal:  This is an error string.");
   ReportWarning("Literal:  This is a warning string.",
adsYES);
   ClearMessage();
};   //member function Integer::TestAlertDriver

One  final  note  on  reporting alerts:  the  Report*()  function
should  always  be  called  from the member  function  where  the
alert   is   first  encountered  or  generated.  By  consistently
following  this  suggestion, the code  which  calls  your  member
functions  can  be  written to assume that any  alerts  occurring
within  your  member  functions have  already  been  reported  by
the time the function returns.


The Preferred Reporting Method

The  preferred  method of reporting alerts requires  a  bit  more
work  than  the  quick-and-dirty  method,  but  results  in  code
which  is  infinitely  more  reusable  and  flexible.  We  highly
suggest   that  any  new  programs  and/or  objects  be   written
using   the   preferred  method  of  alert  reporting,   as   the
preferred   method   results  in  programs   which   are   better
structured than programs using the quick-and-dirty method.

Before  we  explain  how  to use the preferred  method,  we  must
first  discuss  the  concepts of using  programmer-defined  codes
for   the  alerts.  Each  unique  alert  for  a  class   can   be
assigned  a  unique  constant  value  for  that  type  of   alert
within   the  class.  For  example,  the  error  codes  for   the
Integer  object  are numbered starting at one  (1).  Each  unique
warning   alert  for  the  Integer  object  is  also  represented
with  a  code,  with  the  first warning also  being  number  one
(1).  The  fact  that both alerts have a code  numbered  one  (1)
does  not  cause a conflict - each type of code is  used  in  its
own   context.   Using  constants  (or  #defines)  to   represent
these  codes  in  your  program will  help  both  you  and  other
programmers    to   identify   the   different   contexts    when
reviewing  your  code  (see  Example  9).  While  you   may   use
negative  numbers  as  alert  code constants,  do  not  use  zero
(0)  as  an  identifier for an alert code - zero  is  pre-defined
by  the  AlertDriver Class Library for each  alert  type  as  the
constants  errOK,  infoOK, msgOK, and  warnOK.  Alert  codes  are
represented by long integers.

Example  9  -  Define constants to uniquely identify  each  alert
within  a  class. Each constant for a particular  type  of  alert
within   a  class  may  have  the  same  value  as  the  constant
identifier  of  another  alert  type  in  the  same  class.  Each
constant  may  also  be  equal to constants  of  the  same  alert
type in other classes.

...
//define alert constants for class Integer
const long errIntDivByZero = 1;
const long errIntOverflow  = 2;
const long infoIntConstruct = 1;
const long msgIntDestruct = 1;
const long warnIntChange = 1;
   /*legal - different types of alerts*/
...
//define alert constants for class DiskFile
const long errDiskFileNotFound = 1;   //legal
const long errDiskFileDiskFull = 1;
   /*illegal - same error code (1) defined
     twice for same class*/
...

Because  errors  are  the  most serious  types  of  alerts,  they
should  be  returned  from  member functions  whenever  possible.
This  means  each member function should return  the  error  code
of  any  error  generated  within that function,  or  return  the
error  code  returned  by  functions it  calls.  By  adhering  to
this  principle,  the  program module  which  calls  your  member
functions  will  always  receive  an  error  code  denoting   the
first  error  encountered  during  execution  of  your  function.
Of  course,  your  functions should return  errOK  if  no  errors
occur.

The    use    of    programmer-defined   alert   codes    figures
prominently  in  the  preferred  method  of  alert  presentation.
Rather  than  call  the Report*() member functions,  your  member
functions   call   the  inherited  Handle*()  member   functions.
Each  Handle*()  member  function  corresponds  to  one  of   the
Report*()   member  functions;  the  Handle*()  member  functions
are:     HandleError(),   HandleInfo(),   HandleMessage(),    and
HandleWarning().

Each  of  the Handle*() functions is passed an alert  code  as  a
parameter.  This  alert  code  is  then  passed  to  one  of  the
Get*Text()  member  functions, converted to text,  and  the  text
is   passed  to  one  of  the  Report*()  functions.  Of  course,
there  are  four  Get*Text()  member functions:   GetErrorText(),
GetInfoText(), GetMessageText(), and GetWarningText().
To  use  the  preferred  method of  alerting,  your  object  must
declare  the  appropriate alert constants for  alerts  which  can
be   generated   within  your  object.  You  then  override   the
Get*Text()  functions  to  convert those  constants  to  strings.
In   a   well-developed  class  hierarchy,  if  your   Get*Text()
function  receives  an alert constant which  it  cannot  convert,
it  should  call  the  overridden  Get*Text()  function,  as  the
constant  may  represent  an  alert  which  was  defined   in   a
parent  class.  The  original  Get*Text()  member  functions   in
AlertableObject  produce  generic  messages  (i.e.:   "Error  422
encountered.")   for  unrecognized  alert   codes.   Example   10
shows   the   implementation   of   the   GetErrorText()   member
function for the Integer class.

Example    10   -   Implementation   of   Integer::GetErrorText()
member    function.   Notice   how   error   codes   which    are
unrecognized  by  this class are passed back  to  the  overridden
function.

...
//define error constants for class Integer
const long errIntDivByZero = 1;
const long errIntOverflow  = 2;
...
class Integer : public AlertableObject
{
   ...
   public:
      virtual void GetErrorText(const long errorCode, char
*text);
   ...
};  //class Integer
...
void Integer::GetErrorText(const long errorCode, char *text)
{
   switch (errorCode)
   {
      case errIntDivByZero : strcpy(text, "Divide by zero in
Integer object.");
                             break;
      case errIntOverflow  : strcpy(text, "Value too large
for Integer object.");
                             break;
      default              :
AlertableObject::GetErrorText(errorCode, text);
                             break;
   };   //switch statement to determine error text
};   //member function Integer::GetErrorText

To  invoke  the  use  of your strings in your  member  functions,
you  simply  call  the  appropriate  Handle*()  member  function,
passing  the  correct  alert  constant.  As  always,  you  should
call   the   appropriate  Handle*()  member  function  from   the
member   function   where   the  alert  is   first   encountered.
Because   the  Handle*()  member  functions  automatically   call
the  Get*Text()  and Report*() member functions,  a  simple  call
to  the  appropriate Handle*() member function  sets  the  entire
chain of events into motion.

At   first,  this  may  seem  like  a  roundabout  way  of  doing
things,   until  you  realize  the  amount  of  flexibility   and
reusability   that  your  code  gains.  Because  the   Get*Text()
member   functions  are  virtual,  you  can  override   them   in
descendant   classes.  If  you  don't  like  the   text   for   a
specific  alert  code, you can derive a new class  and  return  a
different  string  for  the code, even  if  you  don't  have  the
source  code  for  the  original  class.  The  Get*Text()  member
functions  don't  even  have  to store  the  alert  text  in  the
program;  the  functions can be written  to  retrieve  the  alert
text  from  an  external text file. Using  an  external  file  to
store  the  strings  for  each alert  allows  an  application  to
use  a  minimal  amount  of  program memory  to  store  text.  In
addition,   the  text  file  can  later  be  altered  to   update
existing  alert  text  without  requiring  recompilation  of  the
application.  Coupled  with  the platform-independent  output  of
the     AlertDriver,     these    capabilities     give     every
AlertableObject  maximum  flexibility  in  presenting  alerts  to
the user.

Example   11   demonstrates  how  the  overloaded  "/"   operator
handles errant attempts to divide Integer objects by zero.

Example   11   -   The   overloaded  operator   member   function
Integer::operator   /()   calls   inherited    member    function
HandleError()  whenever  it  detects  an  attempted   divide   by
zero.  Invoking  the  HandleError()  member  function  causes  an
eventual  call  to  the Integer::GetErrorText()  member  function
shown in Example 10.

int Integer::operator / (int aValue)
{
   int result;   //holds the function result

   if (aValue)
      //specified value is not 0
      result = val / aValue;
   else
   {
      HandleError(errIntDivByZero);
      result = INT_MAX;
   }
   return result;
};   //overloaded operator Integer::operator /



Changing & Sharing AlertDrivers

All   programs   linked  with  the  AlertDriver   Class   Library
automatically    gain    access   to   a   default    AlertDriver
referenced   by  the  defaultAlertDriver  pointer   (defined   in
ALERTDRV.H).    This   global   AlertDriver   is    usually    an
interactive,   screen-oriented  driver   which   provides   alert
reporting   capabilities   for  the  software   environment   for
which     the     program    was    targeted.    For     example,
defaultAlertDriver  may  provide text-mode  output  for  a  text-
mode   program,  but  will  use  Windows  dialog  boxes  if   the
program  has  been  compiled  as a Windows  application.  Because
defaultAlertDriver  is  available to  be  called  from  any  part
of   your  application,  even  your  non-object  code  can   call
defaultAlertDriver's   member   functions   (see   Example   12).
Calling   one   of   the  Handle*()  member  functions   of   the
AlertDriver   class  is  equivalent  to  calling   one   of   the
AlertableObject::Report*()  member  functions  (see   the   Class
Library Reference for details).

Example  12  -  Accessing defaultAlertDriver's  member  functions
from  non-object  code.  Calls  made  in  this  way  are  roughly
equivalent  to  the  quick-and-dirty method of  reporting  alerts
from within objects.

#include <alertdrv.h>
...
void main(void)
{
   ...
   //test the defaultAlertDriver without an object
   defaultAlertDriver->HandleInfo(
      "Text from non-object code.");
   ...
};   //function main()

Each  AlertableObject (and derived object)  that  is  created  by
your  program  initially  uses  the  defaultAlertDriver  as   its
AlertDriver.    By    using   this   common   AlertDriver,    the
AlertableObjects  in your program gain all  of  the  benefits  of
platform-independent   error  reporting   with   a   minimum   of
code/data  overhead.  One  AlertDriver  controls  the  alerts  of
all your program's objects.

There  are  times,  however, when you may  want  to  change  this
default   behavior,  particularly  if  the  default   AlertDriver
does  not  provide  the  appropriate  type  of  output  for  your
object.   For  example,  although  the  default  AlertDriver   is
usually  screen-oriented,  your  may  want  your  object  to  log
alerts to a disk file.

You  can  easily  change  the type of AlertDriver  used  by  your
object                by               calling                the
AlertableObject::ChangeLinkedAlertDriver()    member    function.
To  call  this  inherited  member  function,  you  create  a  new
AlertDriver  object,  then pass its address  as  a  parameter  to
the  function.  Because  all  AlertDrivers  should  be  allocated
on  the  heap,  this can usually be accomplished  with  one  line
of  code.  For  example, the following line of code  will  change
the   AlertDriver  for  object  MyObj  to  an  AlertDriver  which
logs alerts to a text file named "myobj.log":

MyObj.ChangeLinkedAlertDriver(new
TextFileAlertDriver("myobj.log", radFREERESOURCE));

(The  radFREERESOURCE  constant  forces  the  TextFileAlertDriver
to  free  (close) the file whenever it is not in  use;  when  the
file  is  closed  (not recording alerts), it can be  accessed  by
other   TextFileAlertDrivers  or  other  applications.  See   the
Class  Library  Reference  for more details  on  controlling  the
TextFileAlertDriver.)

AlertDrivers  allocated  by your application  can  serve  several
AlertableObjects  at  once,  just  as  the  default   AlertDriver
originally  serves  all of the objects in your  program  as  they
are  created.  Example 13 demonstrates how  to  share  one  newly
allocated AlertDriver among two objects.

Example  13  -  Creating  an AlertDriver object  and  sharing  it
among  multiple  objects. In this case,  both  objects  will  log
their  alerts  to  the  same text file.   Remember,  AlertDrivers
which   you   create  within  your  program  should   always   be
allocated on the heap.

...
AlertDriver *pAlertDriver;
Integer a, b;
...
/*log a and b alerts to the same text file -
  other AlertableObjects continue to use
  defaultAlertDriver*/
pAlertDriver =
   new TextFileAlertDriver("myobjs.log",
                           radFREERESOURCE);
a.ChangeLinkedAlertDriver(pAlertDriver);
b.ChangeLinkedAlertDriver(pAlertDriver);
...

To  completely  remove  (not  replace)  the  AlertDriver  for  an
AlertableObject,          call          member           function
ChangeLinkedAlertDriver()  with  a  parameter  of   NULL;   doing
this   will   remove   all   alerting   capabilities   for    the
AlertableObject.

The  AlertDriver  Class  Library  defines  the  standard  classes
StdAlertDriver   and   TextFileAlertDriver   for   all   software
platforms  and  environments. StdAlertDriver  uses  the  standard
C++  iostreams  cin,  cout, and cerr  to  report  alerts  to  the
user   while  TextFileAlertDriver  logs  alert  text  to  a  file
which    is    specified    in   its   constructor.    Additional
AlertDriver   classes   may   be  defined   for   your   specific
software       environment       (for       example,        class
TurboVisionAlertDriver   is   defined    for    programs    using
Borland's  Turbo  Vision class library). See  the  Class  Library
Reference   chapter   for  full  details   on   the   AlertDriver
classes   which   are   available  in  your   specific   software
environment.

Experienced  programmers  know that one  of  the  toughest  parts
of  debugging  a  program  is  finding  heap  allocation  errors.
Because  all  AlertDrivers are allocated  on  the  heap  and  may
be   shared   among  multiple  AlertableObjects,   you   may   be
wondering  if  there  is  an  easy  way  to  determine  when   an
AlertDriver  is  no  longer being used and when  it  is  safe  to
deallocate  an  AlertDriver. The answer  is  simple:   you  don't
have   to   worry  about  deallocating  any  AlertDrivers,   even
those   which   you  allocate!  The  AlertDriver  Class   Library
handles it all for you!

Each   AlertDriver   contains  a   count   of   the   number   of
AlertableObjects   using   its   services.    This    count    is
incremented  each  time  an  AlertDriver  is  "attached"  to   an
AlertableObject.  An  AlertableObject  is  "detached"   from   an
AlertDriver   whenever   the  AlertableObject   is   deallocated,
goes  out  of  scope,  or is attached to a different  AlertDriver
object   through   member   function   ChangeLinkedAlertDriver();
each  time  this  happens, the attached  AlertDriver's  count  is
decremented.  When  the count reaches zero  (the  AlertDriver  is
no   longer  being  used  by  any  object),  the  AlertDriver  is
automatically  shutdown  and  deallocated.  What  this  means  to
you   as   a  programmer  is  that  you  can  allocate  as   many
AlertDrivers  of  as  many  different classes  as  necessary  and
attach  them  to  as  many AlertableObjects as  you  desire  with
the   understanding  that  each  of  the  AlertDrivers  will   be
deallocated as soon as it is no longer being used.


Linking AlertDrivers

Although  you  can  change  the  AlertDriver  used  for  each  of
your  objects  at  any  time, this still may  not  give  you  the
level  of  flexibility  you need. After  all,  it's  nice  to  be
able   to   switch  between  screen-based  alerting  and  logging
those  alerts  to  a  disk file, but what if  you  wanted  to  do
both  at  the  same  time?  With the AlertDriver  Class  Library,
you  can  attach  several  AlertDrivers  together  in  series  so
that  the  same  alerts  are  reported  in  multiple  ways.  This
process is known as "linking" the AlertDrivers.

To   link   one  AlertDriver  to  another,  you  must  call   the
ChangeLinkedAlertDriver()   member   function   of   the    first
AlertDriver  object.  (The first AlertDriver  object  is  defined
as  the  AlertDriver  which is attached  to  an  AlertableObject,
the   remaining   AlertDrivers   are   each   attached   to   the
previously   linked  AlertDriver.  In  effect,  the  AlertDrivers
form  a  linked  list,  often  referred  to  as  a  "chain.")   A
common  use  of  linking AlertDrivers is  to  provide  disk  file
logging   in   addition   to  screen  alerting   by   linking   a
TextFileAlertDriver   to   an   AlertableObject's    AlertDriver.
This    can    be    most    easily    accomplished    if     the
AlertableObject's   AlertDriver  is   the   default   AlertDriver
(see Example 14).

Example  14  -  By linking a TextFileAlertDriver to  the  default
AlertDriver,  all  objects  using the  default  AlertDriver  will
report  their  alerts  to  both  the  screen  and  a  disk   file
(assuming  that  the default AlertDriver reports  alerts  to  the
screen).

...
//attach a text file AlertDriver to the default driver
defaultAlertDriver->ChangeLinkedAlertDriver(
   new TextFileAlertDriver("adtest.log", radFREERESOURCE));
...

It    is    important    to    note   that    when    you    call
AlertableObject::ChangeLinkedAlertDriver()                     or
AlertDriver::ChangeLinkedAlertDriver(),   you   are   effectively
replacing   all   AlertDrivers   which   are   linked   to    the
object/driver   with   the   AlertDriver   you    specify.    For
illustration,  assume  that  prior  to  calling   the   code   in
Example   14,  defaultAlertDriver  is  the  first  of  four   (4)
AlertDrivers  which  are  linked together.  After  executing  the
code  in  Example  14, defaultAlertDriver will be  the  first  of
two   (2)  AlertDrivers  in  the  link.  The  three  AlertDrivers
which   were   linked   to  defaultAlertDriver   prior   to   the
ChangeLinkedAlertDriver()     call     were     detached     from
defaultAlertDriver  (and  possibly  deallocated  if   they   were
not being used by other objects).

This  concept  works  in the other direction  as  well.  You  can
link  several  AlertDrivers together, than pass  the  address  of
the   first   one   to   ChangeLinkedAlertDriver();   the   first
AlertDriver  will  be  directly  attached  to  the  object/driver
for  which  the  call was made, while all other  AlertDrivers  in
the    link   will   be   indirectly   attached   to   the   same
object/driver.

When  several  AlertDrivers are linked together,  the  first  one
processes  the  alert  from  the  AlertableObject,  then   passes
the   alert   to  the  next  linked  AlertDriver.  This   process
continues  until  there are no more AlertDrivers  in  the  chain.
Because  of  this  series of actions, it is  suggested  that  you
place    only    one   screen-oriented   AlertDriver    in    any
AlertDriver   chain.   We  also  recommend   that   the   screen-
oriented   AlertDriver  (or  any  AlertDriver   which   interacts
with  the  user)  be placed first in the chain -  this  not  only
allows  the  user  to  be  notified immediately  of  any  alerts,
but  also  ensures  that  other AlertDrivers  in  the  chain  are
informed   of   the   user's   choice.   For   example,   if   an
AlertDriver   chain  contained  both  a  StdAlertDriver   and   a
TextFileAlertDriver,   you   should  place   the   StdAlertDriver
first   because  it  interacts  with  the  user.  In  this  case,
warning  alerts  would  be  presented to  the  user  first,  then
the  user's  choice  would be recorded  in  the  text  file.  Had
the   TextFileAlertDriver  been  placed  first  in   the   chain,
recording  the  user's choice correctly in the  text  file  would
not be possible.


Modifying AlertDriver Behavior

At  times,  you  may  wish to temporarily disable  processing  of
a  certain  type of alert or affect the output of  an  alert  for
an    AlertableObject   without   changing   or   detaching   the
AlertDriver.   Such  changes  can  be  made   by   altering   the
processing   flags   which  are  stored  in   each   AlertDriver.
Through    the   use   of   AlertableObject   member    functions
GetAlertProcFlags()   and   ChangeAlertProcFlags(),    you    can
obtain  and  alter  the  settings of  the  linked  AlertDriver(s)
for   an   object.  (You  can  directly  get/set  the  processing
flags   for  an  AlertDriver  by  calling  its  member  functions
GetProcFlags()  and  ChangeProcFlags() - see  the  Class  Library
Reference chapter for details and syntax.)

The  processing  flags  for  an AlertDriver  are  represented  as
bit-mapped  constants  prefixed with the  letters  "adf"  in  the
ALERTDRV.H   header  file.  Four  of  these  constants   act   as
switches   for   processing   the  various   types   of   alerts:
adfERROR,  adfINFO,  adfMESSAGE,  and  adfWARNING.  When  one  of
these  flags  is  enabled  (on), the  AlertDriver  processes  all
alerts  of  that  type which are passed to it.  When  a  flag  is
disabled  (off),  the AlertDriver ignores alerts  of  that  type,
but  still  passes  the  alert  to the  next  linked  AlertDriver
(if any).

The  return  value  of  AlertableObject::GetAlertProcFlags()   is
an   unsigned   short  which  holds  the  value  of   all   flags
currently  enabled  for  the  first linked  AlertDriver  for  the
object.  You  can  test  to  see if a flag  is  enabled  (on)  by
performing a binary "AND" operation:

//see if the first-linked AlertDriver for MyObj is currently
processing warnings
if (MyObj.GetAlertProcFlags() & adfWARNING)
{
   //warnings are being processed for MyObj
   ...
}

Example  15  shows  how  to enable or disable  processing  for  a
specific        type       of       alert        using        the
AlertableObject::ChangeAlertProcFlags()  member   function;   the
second  parameter  determines whether  the  specified  flags  are
enabled  (cfoENABLE),  disabled  (cfoDISABLE),  or  intended   to
overwrite  all  of the current flags (cfoOVERWRITE)  as  well  as
whether   the   changes  are  made  for  the  first   AlertDriver
(cfoDRIVER)   or   for   all   linked  AlertDrivers   (cfoCHAIN).
Please see the Class Library Reference for more details.

Example  15  -  Enabling  and  disabling  AlertDriver  processing
flags  for  an  Integer  object. Notice  that  when  enabling  or
disabling   specific  flags,  other  flags  for  the  AlertDriver
are   not   altered.   In   this  example,   the   first   linked
AlertDriver   for  object  "a"  is  assumed  to  be   a   screen-
oriented interactive AlertDriver; this is normally the case.

...
Integer a;
...
/*temporarily disable information alert
   processing to the screen for a*/
a.ChangeAlertProcFlags(adfINFO, cfoDRIVER | cfoDISABLE);
/*now perform operations on a which are likely to
  generate information alerts*/
...
//enable information alert processing for a
a.ChangeAlertProcFlags(adfINFO, cfoDRIVER | cfoENABLE);
...

The   adfALLALERTS  flag  is  a  combination  of  the   adfERROR,
adfINFO, adfMESSAGE, and adfWARNING flags.

The   adfTIMESTAMP   flag   controls   whether   an   alert    is
timestamped  when  reported.  When  enabled,  this  flag   causes
the   AlertDriver  to  generate  a  string  holding  the  current
date/time  and  appends  the  alert  text  to  that  string.  The
timestamp  is  formatted  with  the  standard  ANSI  C   function
ctime()   (see   the   Class  Library   Reference   chapter   for
details.)  A  typical  timestamped error message  output  through
a TextFileAlertDriver might look like:

Tue Dec 28 15:18:36 1993
ERROR - Literal:  This is an error string.

By  this  point,  you've  no doubt realized  that  a  warning  is
the  only  type  of  alert which needs to return  information  to
the      program.      When     calling      member      function
AlertableObject::HandleWarning(),  you   not   only   specify   a
string  containing  a  question  for  the  user,  but  you   also
indicate  the  default  response  to  the  question  (please  see
the  Class  Library  Reference chapter for details).  If  warning
processing   is   enabled   for   the   object's   first   linked
AlertDriver,  the  question is presented  to  the  user  and  the
response  is  returned.  However,  this  is  one  of  only  three
possible  states  for  the AlertDriver;  warning  processing  may
be   disabled  for  the  AlertDriver,  or  the  object  may   not
currently  be  attached to an AlertDriver. In  the  latter  case,
AlertableObject::HandleWarning()     returns      a      constant
indicating  that  no  AlertDriver  is  attached  to  the  object.
But what should be returned in the former case?

By   default,   if  warning  processing  is  disabled   for   the
AlertDriver,  the  value  returned  is  the  default  value   you
specified.  Your  program  can assume that  the  user  chose  the
default   option  and  execution  continues  normally.   However,
there  may  be  times when you want to know  that  the  user  was
not  given  the  opportunity to make  a  choice.  In  this  case,
you   should  enable  processing  flag  adfDISCLOSURE   for   the
AlertDriver.  When  adfDISCLOSURE is enabled  and  adfWARNING  is
disabled,   calls   to  AlertableObject::HandleWarning()   result
in   a   special   constant   being   returned;   this   constant
indicates   that   processing   for   the   current   alert   was
disabled.

Please          see          the          discussion           of
AlertableObject::HandleWarning()    in    the    Class    Library
Reference   for   full   details  of   that   member   function's
possible  return  values  and  how your  code  should  deal  with
them.

In  most  cases,  the  default  settings  of  each  AlertDriver's
processing   flags  should  be  adequate  for  your  application.
Remember,   when   setting   the   processing   flags   for    an
AlertDriver  which  is  shared  between  objects,  you  will   be
affecting the alert processing for all of those objects.


What About Exception Handling?

Because   exception   handling  (a  fairly   new   C++   language
feature)  is  not  supported  by  all  C++  compilers  (and   not
implemented   consistently  among  those   compilers   which   do
support  it),  we  will  not attempt  to  show  sample  code  for
using  this  feature.  However,  this  does  not  mean  that  the
AlertDriver   Class  Library  is  incompatible   with   exception
handling concepts.

Exception  handling  allows  for the separation  of  a  program's
error-generation    code    from   the   error-reporting/handling
code.   This   is   implemented  through  a  try   block   (which
contains  a  program's/routine's  "normal"  code)  and   one   or
more  catch  blocks  (which handle the  errors  occurring  within
the  try  block.)  Whenever a program exception  (an  exceptional
condition,  such  as  an  error) occurs  within  the  try  block,
the   program  executes  a  throw  (raises  the  exception)   and
control is passed to one of the catch blocks.

Once  the  exception  reaches  the catch  block,  the  exception-
handling  code  can  be  reported using an AlertDriver.  Even  if
your   objects  have  been  written  to  throw  exceptions,  they
themselves  can  catch the exceptions, report  them  using  their
linked   AlertDriver(s),   then  re-throw   the   exceptions   to
higher-level code.

If  you  plan  to  use exception handling in your  C++  programs,
we  suggest  that  you use the AlertDriver  Class  Library  as  a
environment-  and  device-independent means  of  reporting  those
exceptions.  The  concepts  of  the  AlertDriver  Class   Library
are a natural supplement to those of exception handling.


Application Design Techniques - A Summary

By  now,  you've  read  all about the power  and  flexibility  of
the  AlertDriver  Class  Library.  Compiled  below  are  what  we
have   found   to  be  the  most  important  coding  and   design
techniques   to   use   when   writing   applications   for   the
AlertDriver Class Library.
1.Always  invoke  the  appropriate  handling  for  an  alert   in
  the   member   function   in   which   the   alert   is   first
  generated;  this  helps  make your  objects  self-contained  by
  allowing them to process their own alerts.
2.Whenever   possible,  return  error  codes  from  your   member
  functions.  This  practice  allows calling  code  to  determine
  if your member functions were successful.
3.Use   the  preferred  method  of  alerting  whenever  possible.
  This  requires  that  your  class be  designed  so  that  alert
  constants   can   be   defined  early   in   development,   but
  rewards  you  with  highly  reusable code.  By  following  this
  practice,   the   text   for   each   alert   can   be   stored
  externally and/or altered by descendant classes.
4.Use   positive   or   negative  numbers  to   represent   alert
  constants,  but  never  use  zero  (0).  Zero  should  indicate
  that nothing is wrong.
5.Always   try  to  share  AlertDrivers  among  objects  whenever
  possible,   as  this  minimizes  memory  usage.  However,   you
  should  give  an  object  its own (non-shared)  AlertDriver  if
  you  will  be  altering  its processing  flags;  this  practice
  will  prevent  the  inadvertent processing  changes  of  alerts
  from other objects.
6.Use   the  AlertDriver  referenced  by  defaultAlertDriver  for
  environment-independent   alerting   for    non-object-oriented
  code.
7.Always  create  AlertDrivers on the  heap.  When  they  are  no
  longer   used,  they  will  automatically  be  deallocated   by
  the AlertableObject(s) which use them.
8.Link   AlertDrivers  whenever  you  need  to  provide  alerting
  to   multiple  output  devices.  Linking  can  take  place   at
  compile-time or at run-time.
9.Modify   an   AlertDriver's  processing  flags   whenever   you
  need  to  enable  or  disable the output of  certain  types  of
  alerts   either  temporarily  or  for  the  object's  lifetime.
  Processing  flags  can be altered at compile-time  or  at  run-
  time.



Chapter 4           Compiling & Linking
                    
IMPORTANT:       This  chapter  describes  how  to  compile   and
          link   your  programs  to  use  the  AlertDriver  Class
          Library.   You   should  read  this  section   in   its
          entirety    before   using   the   AlertDriver    Class
          Library.


Supported Compilers & Class Libraries

As  of  the  printing  of  this  manual,  the  AlertDriver  Class
Library   has   been  successfully  tested  with  the   following
compilers/class libraries:
  Borland   C++   v3.1  (text  mode,  Container  Class   Library,
  Turbo Vision, ObjectWindows)
  Microsoft C/C++ v7.0 (text mode)

Other  untested  compilers/class libraries may  be  supported  in
text   mode.   You   should   read  the   supplied   file   named
"README.1ST"   on   the  distribution  disks   for   the   latest
information  which  may  describe  features/updates  added  after
the printing of this manual.


Header Files

There  are  four  header files associated  with  the  AlertDriver
Class Library:
ADWINDLL.H  Prototypes    of   functions   used   in    Microsoft
            Windows helper DLL.
ALERTDRV.H  AlertDriver       class       (and       derivatives)
            declarations.
ALERTOBJ.H  AlertableObject class declaration.
ENVIRON.H   Compilation- and target-specific declarations.

You  will  normally  #include  the ALERTOBJ.H  and/or  ALERTDRV.H
header  files  in  your source code files to  give  the  compiler
the  necessary  class  declarations and/or  function  prototypes.
(The  Class  Library  Reference  chapter  lists  the  appropriate
header  file  for  each identifier which your  program  may  need
to access from the AlertDriver Class Library.)

The  ADWINDLL.H  header file is automatically  #included  by  the
AlertDriver  Class  Library whenever  you  compile  your  program
for  the  Microsoft  Windows  platform.  You  will  normally  not
need to be concerned with this file.

The  ENVIRON.H  header  file  is a special  file  which  contains
all   of   the   compilation-  and  target-specific  declarations
needed  to  compile  your  program  for  different  environments.
You  may  need  to  replace the first two (2) #define  statements
in   ENVIRON.H  to  compile  your  program  for  the  appropriate
environment   (see  below).  Don't  worry:   this  is   extremely
easy to do and the header file itself is clearly documented!


Changing The Compilation Environment #define in ENVIRON.H

The   first  #define  statement  in  the  ENVIRON.H  header  file
defines  the  compilation environment and  class  library  to  be
used.  You  should  replace the #define with  one  of  the  valid
entries  listed  below.  Your choice of  compilation  environment
will   cause   the   compiler   to   make   several   assumptions
(detailed   below)   about  your  code.  The  valid   compilation
environment #defines are:
BORLAND_CONTAIN        Borland's    Container   Class    Library.
               OBJECT.H    will    be    #included    and     the
               AlertDriver   Class  Library   classes   will   be
               derived from Object.
BORLAND_OWL    Borland's    ObjectWindows    Library.     Because
               Borland's  Windows  Custom  Controls  Library   is
               used   to   present  some  alerts  in   customized
               dialog    boxes,   BWCC.H   will   be    #included
               (BWCC.H   is   supplied  with  the   Borland   C++
               compiler).   OBJECT.H  will  also   be   #included
               and   the   AlertDriver  Class   Library   classes
               will be derived from Object.
BORLAND_TV     Borland's    Turbo    Vision    Class     Library.
               #defines     several    Turbo    Vision     Uses_*
               preprocessor   definitions,  then  #includes   the
               TV.H    header   file.   The   AlertDriver   Class
               Library  classes  will  be derived  from  TObject.
               When      you     #define     this     compilation
               environment,     you    should    #define     your
               program's   Turbo   Vision   Uses_*   preprocessor
               directives   before   #including   any   of    the
               AlertDriver  Class  Library  header   files.   See
               the    Turbo   Vision   documentation   for   more
               details.
NOROOT         The  AlertDriver  Class Library  classes  will  be
               compiled without a common root class.


Changing The Target Environment #define In ENVIRON.H

The  second  #define  statement  in  the  ENVIRON.H  header  file
defines  the  target environment for the executable  file  to  be
produced.  You  should  replace  the  #define  with  one  of  the
valid    entries   listed   below.   Your   choice   of    target
environment   will   cause   the   compiler   to   make   several
assumptions   (detailed  below)  about  your  code.   The   valid
target environment #defines are:
TARGET_DOS     The  program  will  run under  the  Microsoft  DOS
               (or compatible) operating system.
TARGET_WINDOWS The   program   will  run  under   the   Microsoft
               Windows       (or      compatible)       operating
               environment.    Header    file    ADWINDLL.H    is
               #included      (this     indirectly      #includes
               WINDOWS.H).   When   you   #define   this   target
               environment,     you    should    #define     your
               program's    WINDOWS.H   preprocessor   directives
               (if    any)   before   #including   any   of   the
               AlertDriver Class Library header files.

Of  course,  not  every  combination of  compilation  environment
and   target  environment  is  valid,  so  make  sure  that   the
combination   you  choose  makes  sense  (if  it   doesn't,   the
compiler  will  usually  give you an  error).  For  example,  the
#defines shown here are illegal:

#define BORLAND_OWL   //use ObjectWindows Library
#define TARGET_DOS    //ILLEGAL - cannot compile OWL apps as
DOS apps

The  correct  settings  for  ENVIRON.H  for  this  example  would
be:

#define BORLAND_OWL      //use ObjectWindows Library
#define TARGET_WINDOWS   //OWL apps must be Windows apps


Linking Your Programs

To  link  your  programs, you must link the  appropriate  library
to  your  application  in  your MAKE or PROJECT  file  (see  your
compiler  manual  for details on linking).  Refer  to  the  lists
below   for   the   appropriate  library  for  your   compilation
environment, target environment, and memory model.

IMPORTANT -  Make  sure  you  link  your programs  to  the  right
          library.   If   you  link  the  wrong   library,   your
          program will not execute.

If   you   #defined  TARGET_DOS  in  ENVIRON.H  as  your   target
environment:
     Link  with  one  of  the following libraries,  whichever  is
     appropriate   for   the  setting  you   #defined   for   the
     compilation  environment  in ENVIRON.H  and  your  program's
     memory model:
          ADBRDCCx.LIBBorland  C++  Container  Classes  -   There
                      are  six  of  these files, where  x  =  the
                      first    letter   of   the   memory   model
                      (tiny,   small,  medium,  compact,   large,
                      or   huge).   For   example,   ADBRDCCL.LIB
                      is   for   use  with  large  memory   model
                      programs.
          ADBRDNRx.LIBBorland   C++,  no  root  class   -   There
                      are  six  of  these files, where  x  =  the
                      first    letter   of   the   memory   model
                      (tiny,   small,  medium,  compact,   large,
                      or   huge).   For   example,   ADBRDNRL.LIB
                      is   for   use  with  large  memory   model
                      programs.
          ADBRDTVL.LIBBorland   C++   Turbo   Vision   -    large
                      memory model.
          ADMSDNRx.LIBMicrosoft   C/C++,   no   root   class    -
                      There   are  four  of  these  files,  where
                      x   =   the  first  letter  of  the  memory
                      model    (small,   medium,   compact,    or
                      large).   For   example,  ADMSDNRL.LIB   is
                      for    use   with   large   memory    model
                      programs.
     If    you    #defined   BORLAND_TV   as   the    compilation
     environment, link this file:
          TV.LIB      Borland's     Turbo     Vision      library
                      (supplied    with    the    Borland     C++
                      compiler).

If  you  #defined  TARGET_WINDOWS in  ENVIRON.H  as  your  target
environment:
     Link  with  one  of  the following libraries,  whichever  is
     appropriate   for   the  setting  you   #defined   for   the
     compilation  environment  in ENVIRON.H  and  your  program's
     memory model:
          ADBRWCCx.LIBBorland  C++  Container  Classes  -   There
                      are   four  of  these  files,  where  x   =
                      the   first  letter  of  the  memory  model
                      (small,   medium,   compact,   or   large).
                      For   example,  ADBRWCCL.LIB  is  for   use
                      with large memory model programs.
          ADBRWNRx.LIBBorland   C++,  no  root  class   -   There
                      are   four  of  these  files,  where  x   =
                      the   first  letter  of  the  memory  model
                      (small,   medium,   compact,   or   large).
                      For   example,  ADBRDNRL.LIB  is  for   use
                      with large memory model programs.
          ADBRWOWx.LIBBorland   C++   ObjectWindows   Library   -
                      There   are  four  of  these  files,  where
                      x   =   the  first  letter  of  the  memory
                      model    (small,   medium,   compact,    or
                      large).   For   example,  ADBRWOWL.LIB   is
                      for    use   with   large   memory    model
                      programs.
     Also link this file:
          ADWINDLL.LIBImport     library     for     the     file
                      ADWINDLL.DLL;      make      sure       you
                      distribute    ADWINDLL.DLL    with     your
                      finished application!
     If    you    #defined   BORLAND_OWL   as   the   compilation
     environment, link this file:
          BWCC.LIB    Import   library   for   Borland's   Custom
                      Control   Library  for  Windows   (supplied
                      with   the  Borland  C++  compiler);   make
                      sure    you   distribute   BWCC.DLL    with
                      your application!


Compiler-Specific Notes

When  writing  programs  which  use  the  AlertDriver  C++  Class
Library,  you  may  need  to know how the  supplied  object  code
was  compiled.  The  notes below indicate the  compiler  settings
used  to  compile  the AlertDriver Class Library.  You  may  want
to   consider   purchasing  the  Source  Code  Edition   of   the
AlertDriver  C++  Class  Library if you  want  to  recompile  the
AlertDriver Class Library with different compiler settings.


Borland C++ v3.1

All  object  code  libraries  and  DLLs  were  compiled  assuming
that   all   unsigned   char  variables  should   be   explicitly
defined  (unsigned  chars off). Stack checking  was  turned  off.
DOS   libraries   were  compiled  with  8086/8088   instructions.
Windows   libraries   were  compiled  with  80286   instructions,
target  Windows  3.0  and  above,  and  assume  the  use  of  the
static   versions  of  the  standard,  Container  Class,   and/or
ObjectWindows libraries.


Microsoft C/C++ v7.0

Stack   checking   was   turned  off  and  the   libraries   were
compiled with 8086 instructions.


Compiling The Sample Programs

To  compile  any  of the sample programs, you should  follow  all
of  the  rules  and  suggestions  previously  mentioned  in  this
chapter.  Don't  forget  to  correctly #define  the  compilation-
and target-environment macros in ENVIRON.H!

The  sample  file  INTDEMO.CPP is the main file  for  the  sample
program   using  the  Integer  class.  (The  Integer   class   is
discussed  in  the  Tutorial Chapter.) To compile  this  program,
you   should  compile  the  INTDEMO.CPP  and  INTEGER.CPP  files,
then  link  them  with the appropriate AlertDriver  Library  .LIB
file(s).   These   files  can  be  compiled  for   any   of   the
supported compilation- and/or target-environments.

If  you  want  to  compile the INTDEMO.CPP sample  program  as  a
Windows   application,  you  should  also  link   the   ADWIN.DEF
module definition file to your executable.

Note   that   the   source  code  for  the  Integer   class   (in
INTEGER.CPP),  and  the  INTDEMO.CPP functions  which  manipulate
the   Integer  objects,  are  totally  compilation-  and  target-
environment independent.



Class Hierarchy

In   the   listing   below,  derivative  classes   are   indented
underneath    their    parent   class.   For    example,    class
WindowsAlertDriver  is  derived  from  class  AlertDriver,  which
is derived from class AlertDriverLink.

ROOTCLASS
   AlertDriverLink
      AlertableObject
      AlertDriver
         RecordingAlertDriver
            StreamAlertDriver
               TextFileAlertDriver
         StdAlertDriver
         TurboVisionAlertDriver
         WindowsAlertDriver
            BWCCAlertDriver



Chapter 6           Creating Your Own AlertDrivers
                    

Design Concepts

For  most  of  your  programming projects,  the  AlertDriver
classes  shipped with the AlertDriver Class Library will  be
sufficient  for your needs. If, however, you would  like  to
output  alerts using operating-system API calls or  hardware
devices  which are not supported by the standard AlertDriver
Class Library, then you will need to create (subclass)  your
own  AlertDriver classes. You may also want to subclass  the
supplied  AlertDrivers to change their default behavior.  In
any  case, this chapter presents the guidelines which should
be  followed  while  designing and writing  new  AlertDriver
classes.

Before  designing  your  own custom AlertDriver  class,  you
should  first determine what type of AlertDriver  the  class
will  be.  There are two basic types of AlertDriver classes:
interactive  (those which can interact with  the  user)  and
recording   (those  which  provide  output-only   services).
Interactive  AlertDriver  classes  are  derived   from   the
AlertDriver   class,   while  recording   AlertDrivers   are
subclassed from the RecordingAlertDriver class.

Because  interactive AlertDrivers usually report  alerts  to
the  screen, they should be designed to use standard C, C++,
or  operating-system API calls whenever possible.  Recording
AlertDrivers,  however, usually report to  hardware  devices
(which are somewhat dependent on the operating-system)  such
as   printers  or  RS-232C  devices.  Therefore,   recording
AlertDrivers  often must be designed to use  the  API  calls
provided by the hardware device manufacturer.


Requirements & Suggestions

To  write an AlertDriver class definition, you must  have  a
good understanding of the API calls necessary to control the
output  resource,  as  well  as  an  understanding  of   the
class(es)   from   which  you  will   be   subclassing   the
AlertDriver.  Of course, you must also have a  C++  compiler
for  your  target environment, as well as any  prerequisites
for the compiler.

If  you are planning to create your own AlertDriver classes,
we  recommend that you purchase the Source Code  Edition  of
the  AlertDriver  Class  Library.  By  examining  the  well-
commented source code used to write the supplied AlertDriver
classes,  you  can gain a much deeper understanding  of  the
techniques used to write reusable AlertDrivers.


Updating ENVIRON.H

You will have to update the ENVIRON.H header file if you are
trying to do one of the following:
1.port  the  AlertDriver Class Library to a new  compilation
  environment (i.e.:  use a currently-unsupported  hierarchy
  class   as  the  root  class  for  the  AlertDriver  Class
  Library)
2.port  the  AlertDriver  Class  Library  to  a  new  target
  environment  (i.e.:   target your executable  code  for  a
  currently-unsupported operating system)
3.redefine  one  of  the  macros specifying  an  AlertDriver
  system   setting  (i.e.:   specify  a  different   default
  AlertDriver  or change the maximum size of an  alert  text
  string)

If  you need to update ENVIRON.H, we suggest you first print
the   file  and  study  its  heavily-commented  preprocessor
directives.


Defining Your AlertDriver

To  define  an AlertDriver, you must first plan to  override
the  AlertDriver::Report*() member functions. Remember  that
for     interactive    AlertDrivers,    member     functions
ReportError(), ReportInfo(), and ReportWarning() should halt
program  execution  until  acknowledged  by  the  user.  For
recording  AlertDrivers, program execution should  never  be
halted.  For  full details on the intended  actions  of  the
Report*()   member  functions  for  both   interactive   and
recording   AlertDrivers,  see  the   discussions   of   the
AlertDriver::Report*() member functions in the Class Library
Reference chapter.

If  you are writing a recording AlertDriver, you should call
member     function    GetResourceControl()    from     your
constructor(s), destructor, and Report*() member  functions.
If   the   resource  control  mode  is  radFREERESOURCE   or
radHOLDRESOURCE, your object should explicitly  capture  and
free  the  resource  when  appropriate.  (For  the  supplied
TextFileAlertDriver, this is accomplished by opening/closing
the   output   file.)   Review   the   discussion   of   the
RecordingAlertDriver  data  member  resourceControl  in  the
Class Library Reference for more details on controlling  the
output resource for a recording AlertDriver.

You      may      also     want     to     override      the
AlertDriver::GetTimestamp() member function  to  return  the
computer's current date/time in a format different from  the
default.

Finally,  if  your AlertDriver encounters  an  error  during
output,  you should not attempt to display an error message.
Just   clean   up   any  allocated  resources   and   return
immediately.

That's  all  there  is  to  creating  your  own  AlertDriver
objects!  If  you  have  followed  these  guidelines,   your
AlertDriver  objects should be usable by any AlertableObject
without changes to the AlertableObject.
