                                            |
                                            | The Object Engine(tm)
                                            | C++ Class Library
                                            | and Code Generator
                                            | for The Paradox Engine(tm)
                                            |
                                            | Version 2.01
                                            | October 10, 1993
                                            |
                                            | Dr. Mark Brittingham
                                            |
                                            |
                                            |

The Object Engine is a C++ class library that encapsulates the core
functionality of the Paradox Engine database library.  This encapsulation
provides a significantly easier interface to the Paradox Engine functions.
Rather than spending your time writing functions to copy data back and forth 
between data structures and record buffers, you need only call the "Store" 
and "Link" methods of your (newly) persistant classes.  Furthermore, 
these functions (and others) are written for you automatically.  You simply 
specify the fields that will populate your new class, and the Code Generator 
automatically generates the class and all of its methods for you!  The 
generated code will work under both Windows and DOS and can be compiled
with Borland C++ 3.1 or MSVC++.  Samples for both environments are included
to help you get started.

The library covers Alphanumeric, Short, Long, Double, Float (currency), Date, 
and Blob types.  The generator allows you to create both primary and secondary 
indices.  And, you can create your class instances using the primary or any 
secondary index to open the underlying table (including composite indices).

The library is very efficient: it adds less than 12K to your application 
(with speed optimization).

Finally, the Object Engine's BLOB support includes two functions that 
simplify BLOB programming.  Blob::OEPut and Blob::OEGet put and get BLOB data 
with one call in each direction.  You simply pass in or read out the data
in character arrays.  When reading the data, we even create the data array
for you. To use these functions, your BLOB data must be no larger than 64K 
bytes to use these functions.  Of course, an example is included in this
package (see blobmain.cpp).

Disclaimer
==========
The Object Engine is now SHAREWARE. If you plan on using the software, you
must register the software and pay a (modest) registration fee.  See the 
register.txt file for information.

Brittingham Software Design, Inc. disclaims all warranties as to this
software, whether express or implied, including without limitation any 
implied warranties of merchantability, fitness for a particular purpose,
functionality, data integrity or protection.

Brittingham Software Design, the Brittingham Software Design logo, 
Object Engine(tm), and ObjEng(tm) are trademarks of Brittingham Software 
Design, Inc.

Borland C++, Paradox(tm) and the Paradox Engine(tm) are trademarks of 
Borland International, Inc.

MSVC(tm) and Microsoft Visual C++(tm) are trademarks of Microsoft, Inc.

MS-DOS(tm), and Windows(tm) are trademarks of Microsoft Corporation.

Netware and Novell are registered trademarks of Novell, Inc.

zApp is a registered trademark of Inmark Development, Inc.

Trademarks of other companies mentioned in this documentation appear for
identification purposes only and are the property of their respective
companies.

Using the Library
-----------------
To use the library, you should first plan out your database structure.  You
should have a clear idea of the classes needed in your application and their
interrelationships.  Next, run the Gen2.exe application (it runs under 
Microsoft Windows(tm)) and create each of your classes.  Gen2 is quite 
simple.  A single button allows you to create a class or to edit whichever 
class is highlighted in a list of classes.  You can save your class
definitions in a format recognized by Gen2 and restore them later.  Thus,
you need not complete the entire set of class definitions in one sitting.
  
When you are satisfied with the set of classes you have defined, you can 
generate the code to implement these classes as persistant objects with the 
push of a button. You will specify the directory and filename of the header
and C++ files.  Keep in mind that you can go back, change your classes, 
and regenerate your code.  However, any changes YOU make to the generated 
code will be overwritten (unless you use another file name).  Thus, it is 
best to make changes in a separate file.  If lots of people register, I will
consider adding a parsing facility to the Object Engine.

There are three switches that can be toggled on the file menu: "Character 
Date Types", "Generate zApp Code", and "Generate Set/Get Encapsulation".

The first toggle is for character date types.  Use this if you want to 
maintain strings (or zStrings- see next paragraph) for the date rather than 
struct tm* structures.  I have found this useful when working with my 
interface code since the date interface class uses character strings.

zApp is an Application Framework from Inmark Development, Inc.  I don't 
do Windows without it and I recommend it highly.  If you are also a zApp
user, toggle the "Generate zApp Code" selection on the FILE menu.  This
will cause the code generator to output zString classes for your variables
instead of char *xxx pointers.  zStrings are a string class and are more 
useful and safer than char pointers.

Set/Get encapsulation is a fancy way of saying "create a GETxxx and a Setxxx
function for each variable xxx".  In previous versions, you could only gain 
access to a persistant object's variables using these Set<var> and Get<var> 
functions.  This was done to support a full encapsulation of the persistant 
classes.  However, you now have a choice with version 2.01.  From the FILE 
pull-down menu, you can toggle the "Generate Set/Get Encapsulation" 
selection to specify whether you want the Get/Set functions generated.

While not using these encapsulation functions detracts from the C++ "purity" 
of the library, the fact is that these classes exist for data i/o and not 
for in-class processing per se.  In my own work, it has proven wise to 
remove this level of indirection.  However, I can imagine situations where
you don't want the actual database value but rather something derived from
it.  So, I have left leave the choice up to you.

Also new to the 2.01 release of the Object Engine: the objectID field will
be shown in a class field listing.  The objectID is a unique identifier for
each record in your database.  Usually, the objectID is the last field in a
record and you can safely ignore it (it is used for internal record keeping).
In version 2.01, you may use the objectID as part of your primary key.
The objectID is 0 until an object is stored and then is unique within the
system afterwards (see below - especially for warnings about the objectID).  

No further documentation is included for the generator simply because it is
so straightforward.  Please fire it up and see for yourself how quickly you
can generate some real persistant classes.

To work with the generated code, you need to do three things:

        1) include the generated header file in the appropriate code files, 
        2) place the generated C++ file in your project or makefile, and
        3) place the objeng.cpp file in your project/makefile 

Remember what you named the header and C++ files during the generate phase
so you know what to include in steps 1 and 2. 

Now you are ready to declare, manipulate, store, or retrieve instances of 
the classes you have defined! You never need to worry about initializing the
engine, manipulating or creating tables, or reading or writing data.  Of 
course, you do have control over Paradox Engine initialization (if you 
register the software) as well as table manipulation if you need it.  
But, for the vast majority of your work, you need only deal with standard 
C++ syntax.


Two Keys To Object Engine Programs
----------------------------------
Two core ideas will help you to understand how to use the library.  First, to 
store an object in the library, simply call its "Store" method. To retrieve a
database record and place it in an object, you will "link" the object to the 
record.  This can be done either by the contents of a field (possibly a key 
field), or navigationally (first, next, etc.). Thus "linking" is the act of 
making an in-memory data structure reflect an on-disk record. Of course, you 
needn't think of it in terms of database records at all!

The following link functions are defined:
          LinkToKey    (one or more key fields)
          LinkToSKey   (to link to secondary & composite keys)
          LinkToRecord (a record position)
          LinkToField  (contents of a field)
          LinkToID     (objectID - see below)
          LinkToFirst
          LinkToLast
          LinkToNext
          LinkToPrev

The second key idea is found in the "objectID".  Each object has an objectID 
associated with it.  When you link an object to a particular database record, 
your object is given the record's unique objectID. This allows each instance
of a persistant class to have its own pointer into the database and allows 
you to store objects without worrying about whether they are new or an update
to a previously stored record.  However, you need to keep these object IDs
in mind when working with persistant classes.

An object without an objectID will be stored as new (assuming that there 
are no conflicting key fields).  Before being stored, the object will auto-
matically be assigned an objectID by the system (YOU should never be 
assigning these yourself!).  You can clear the objectID and make a record
appear "new" by calling the Unlink() member function.  

When you store a record with a non-zero objectID, the system will search for 
the record with the matching objectID and will update that record. 
Note that if you create a new object using the copy constructor or set 
one object to another (using '='), then both will have the same objectID.  
If you change these two objects separately and save them both, then the 
record will reflect the last one stored (see example code in main.cpp).

You can set the objectID for an object but you should ONLY do so if you are 
going to then use the objectID in a search.  If your search doesn't succeed, 
use Unlink() to clear the objectID again (see above).  If you don't clear 
the value and later do a "Store", this object may later be overwritten by
a new object stored to the database.

Note that the use of objectIDs has one other implication; if you are using
objeng data tables with other tools (i.e. Paradox), you will have to maintain
the objectIDs.  To do this, you have to use the "objeng.db" table.  This table
has one record and one field (a long value ("N") named "ID").  To manage
records outside of the objeng, you will have to retrieve this record and use
the value in its ID field whenever you CREATE a new objeng object. Remember
that every record in every table has its own unique objectID.   After 
retrieving the record and using it as a new objectID, you MUST increment the 
value and place it back in the table.  A table is used so all applications 
can have access to the objectID index, possibly with locking and concurrence.

If you have any questions, you can either look at the source code or contact
me on Compuserve.

***************************************************************************
             Persistant Class Functions  - Introduction

When you generate code using gen2.exe, you will see that your class 
definition:

a) is derived from "PersistClass", 
b) has one or more variable declarations, and
c) has six other methods (2 Constructors, operator=, Store, Retrieve, 
                                                        and LinkToKey).  
d) may have Set/Get functions depending on the state of the File | Set/Get
        toggle.

<Note: the Set and Get functions are optional in Version 2.01.>

In addition to the six functions defined with the class, your class inherits 
many functions for linking to various records as well as network (locking)
operations, informational functions, Paradox Engine interface methods (getting
table and record handles, etc.), and more.  All of these functions are 
documented below.

Functions that return a value will return Paradox Engine error codes 
(PXSUCCESS, PXERR_XXX).  In all cases (functions, operators, constructors), 
the variable "status" will be filled with the most recent Paradox operation's
return value (use GetStatus() to inspect). Thus, you will need to be aware of
the Paradox Engine's error codes. 

If you created a secondary key in the Gen2 code generator, you may call
object constructors with the secondary key ID as a parameter.  If you do so,
the link-to functions will retrieve records sorted by the secondary key (see
example code in main.cpp).

***************************************************************************
                    Class Function Documentation

For purposes of clarity, assume that you have declared a class "Client."  
The following functions would be defined:

//------------------- Native (Generated) Functions ------------------------
//-------------------------------------------------------------------------
Constructor:  Client(int indexID = 0)   [format: <ClassName>(int indexID)]
Creates an instance of Client.  If the client database does not exist, it
is created.  If it is not open, it is automatically opened.  The object does
not reflect any record in the database when first created.  All strings 
are empty, all scalar values are set to zero.  The database is opened
on the index given by indexID.  If indexID is omitted, it defaults to 0 
(opened on primary index or by physical order).  Otherwise, the secondary
index given by indexID is used (the table will appear to be sorted in 
secondary index order).

To open a table on a composite secondary key, you will have to open a table 
with no index. Then call "GetFieldHandle" with the name of the composite key 
to get its numerical field handle (the name is all you know ahead of time).
"GetFieldHandle" is a method of PersistClass and is documented below.

Since key handles stay the same (except, possibly, if you drop and then re-add
them), you could do this in one sample run and then just use the returned
field handle (as a constant) for all subsequent runs.

//-------------------------------------------------------------------------
Constructor: Client(Client& val)   [the copy constructor]
Creates a new, identical instance of Client.  The client database will be
created and open since you must have already created a client to pass in!
The new object reflects all of the values of the passed in object. This 
includes the objectID of the object.  Thus, you will now have two objects
looking at exactly the same database record.  Moving either object (using
LinkTo...) will not affect the other.

//-------------------------------------------------------------------------
Operator: Client &operator=(Client &)    [the assignment operator]
See the copy constructor.  Best used during the declaration of a reference.

//-------------------------------------------------------------------------
Private function: int Retrieve   [note "Private", do not make public!]
Copies from the database record to the class' variables.  Called from all
of the "Link..." functions (see below).

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int Store()  [see PXRecInsert, PXRecUpdate]
Moves the contents of the object into the database. If the object is already
linked to a database record (objectID != 0) then this function will UPDATE 
the matching database record. If the object is not yet linked to a database 
record (objectID is equal to 0), then calling Store will add it to the 
database (PXRecInsert).

It is an error to store a record with a duplicate primary key.  If you are
working with a non-keyed table, a new record will be stored in the position
before the current record.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int LinkToKey(int numkeys, int mode)  [See also, PXSrchKey]
When you define a class, you can specify one or more of its variables
(starting with the first), as keys.  A key variable corresponds to key 
fields in the database.  To search using key values, you place these values 
in the object's key variables and call this function.  For example, if you
have three key fields and want to search on the first two, you should place
values for these first two key fields in the corresponding variables and
call this function with numkeys = 2.  Set Mode equal to any mode legal under 
the PXSrchKey documentation (SEARCHFIRST, etc.) according to your search
requirements. For example:
        ...
        Client.ID = 1000;
        if (Client.LinkToKey(1, SEARCHFIRST) == PXSUCCESS)
          <do great things...>

Note: if the field is not keyed, this function does nothing but return an
error.  New in V2.0: If you perform a CLOSESTRECORD match, then the 
object engine will retrieve the record even if a PXERR_RECNOTFOUND error
was returned (thanks for the suggestion: Mike Sagner).

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int LinkToSKey(char *fldName, int mode) [See also, PXSrchFld]
If you define one or more secondary keys in the Gen2 program, secondary 
keys will be automatically created when your table is created (by default,
these are INCSECONDARY keys: see Paradox Engine documentation).  You can use
these keys by filling the appropriate fields with your search criteria and
calling the LinkToSKey function.  This function takes the name of the field
(or composite key) and the mode (SEARCHFIRST, SEARCHNEXT, etc.).  Note that
the case-insensitive and composite fields you create are accessed by name
(by default, Gen2 creates case-insensitive names as "CI<fieldname>".  If you 
forget the name of a composite or case-insensitive key, you can look in your 
class constructor.  For each such key there will be a "RegisterCompKey" call; 
the key name is the fourth argument to this call.  The MAIN.CPP example file 
demonstrates a call to LinkToSkey.  New in V2.0: If you perform a 
CLOSESTRECORD match, then the object engine will retrieve the record even if 
PXERR_RECNOTFOUND was returned (thanks for the suggestion: Mike Sagner).

//------------------- Set/Get Functions -----------------------------------
//-------------------------------------------------------------------------
NOTE: These functions are only generated if you toggle the "Generate Set/Get
Encapsulation" switch on the FILE menu.

Functions:
void Setxxx(char *inxxx) 
void Setxxx(short inxxx)
void Setxxx(long inxxx)
void Setxxx(struct tm *inxxx)
...etc.

Sets the field contents using the given value.

const char* Getxxx()
const long& Getxxx()
const short& Getxxx()
const struct tm* Getxxx() 
Blob* Getblobtest()  <-- Note: no corresponding Set function since blob
                                is created for you (see below).
...etc.

Returns a pointer or reference to the named field value.  Pointers/refs are
const to provide encapsulation (you have to use corresponding Set functions
to change the value).


//------------------- Inherited Functions ---------------------------------
//-------------------------------------------------------------------------
Function: int LinkToField(char *fieldname, void *value, int mode)
                                                                                                        [See PXSrchFld]
To search for a specific value in a specific field, pass in the fieldname,
the value (cast to a void pointer), and the search mode.  For example:

  client.LinkToField("LNAME", (void *)"Brittingham", SEARCHFIRST)
  client.LinkToField("ID", (void *)&i, SEARCHFIRST)

Note that the value must be a void pointer.  If you really want overloaded
functions, let me know.  It takes up more space but might be worth doing.
Note also that searches using compound keys are on the way...

New in V2.0: If you perform a CLOSESTRECORD match, then the object engine 
will retrieve the record even if a PXERR_RECNOTFOUND error was returned 
(thanks for the suggestion: Mike Sagner).

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int LinkToRecord(int position) [See PXRecGoto]
Make object reflect contents of the nth record where n = position.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int LinkToID(const long &ID)   [See PXSrchFld]
Explicitly set object to database record with objectID = ID.  Should not
generally be needed.  Note, no mode argument since objectIDs are unique.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int LinkToFirst()
Make object reflect contents of first database record.  If database is
empty, contents are not changed and error code is returned.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int LinkToLast()
Make object reflect contents of last database record.  If database is
empty, contents are not changed and error code is returned.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int LinkToNext()
Make object reflect contents of next database record.  If there is
no next record, contents are not changed and error code is returned.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int LinkToPrev()
Make object reflect contents of previous database record. If there is
no previous record, contents are not changed and error code is returned.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: void Unlink();
Removes the objectID from the object.  This allows you to copy an object 
and then unlink it from the database record.  If you then Store the object
it will go in under a new objectID (assuming there is no key conflict).

Return Value: None

//-------------------------------------------------------------------------
virtual int Destroy()  [See PXRecDelete]
Deletes the database record to which this object corresponds.  If the
object has not been linked, it returns PXERR_RECNOTFOUND.  Does not delete
the object or change any of its values.  Note that deleting an object does
not remove it from the database.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int GetFieldHandle(char *fldName, FIELDHANDLE &fieldHandle) 
Retrieves the handle of the field named by fldName and places it in 
fieldHandle.  Useful for figuring out what handle has been assigned to a 
composite key.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
int NRecs(RECORDNUMBER & nrecs)  [See PXTblNRecs]
Retrieves the number of records in the table that the object works with.
The number of records is placed in the nrecs parameter.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
const int GetStatus()
Returns the status value. Status is set by the most recent Paradox Engine
function.  This function is useful for testing the success of functions
that don't return a status value (constructors and operators) although it
can be used after any Object Engine call.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
TABLEHANDLE GetTblHdl()
Returns the Paradox Engine table handle.  Can be used if you want to bypass
the library and work directly with the table.  Since the Object Engine does
not provide direct table manipulation (copy, delete, etc.), you may find
this function useful.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
RECORDHANDLE GetRecHdl()
Returns the Paradox Engine record handle.  Can be used to bypass the library
and work directly with the record buffer.  Should not be needed unless you
want to work with multi-field secondary indices (which will be available in 
the next release anyhow).

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
const char *GetTblName()
Returns the name of the table to which this object is attached.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
long GetObjectID()
Returns the objectID.  Should not be needed.

//-------------------------------------------------------------------------
long SetObjectID()

Sets the objectID.  Should ONLY be used to set the objectID before a 
LinkToSkey or LinkToKey.  LinkToKey applies only if you have put the objectID 
in primary key position).  When you search using an objectID, remember to 
clear it if the search fails (using the Unlink() function).

//-------------------------------------------------------------------------
int OEPutDate(const int &fieldno, const char *date) and
int OEPutDate(const int &fieldno, const struct tm &date) and
int OEPutDate(const int &fieldno, const zString &date).
int OEGetDate(const int &, char *date) and
int OEGetDate(const int &, zString &date) and
int OEGetDate(const int &, struct tm &date)

Used in the classes derived from PersistClass to place dates into specific
record buffer fields.  Used to simplify the process of generating code.
You should NOT need to use these functions since you will not work directly
with record buffers.  The code generator will create either char dates 
("11/27/65"), zString dates, or struct tm dates depending on the "Date Type" 
and zApp switches (toggled in the FILE menu).

NOTE! The implementation of struct tm structures (or, rather, the functions
that work with them) in Borland C++ and MSVC uses zero-based months.  For
example, you get 0-based months if you use the date functions to 
automatically fill a struct tm (i.e. Today's date).  Before storing struct
tm structures in the database, I add 1 to the month and subtract 1 from the
month when retrieving since Paradox expects 1-based months.  If you have 
implemented your own date functions and don't use 0-based months AND you are
using the structs (rather than character dates) then you will have to change 
these functions in objeng.cpp.

Return Value: PXSUCCESS or other engine error code.

//------------------- Network/Locking Functions ---------------------------
//-------------------------------------------------------------------------
These functions implement the network management for the Object Engine.
Note that you must initialize the Object Engine (ObjEng object) with
a user name, net name path, and netcode for these functions to be effective.
In particular, the netcode argument passed to the ObjEng constructor should
be NETSHARE or LOCALSHARE.  Note that no harm is done if these functions are
in your code when running under NOSHARE (or NOTONNET); they just won't do
anything.  There is no encapsulation of the PXNetErrUser and PXNetUserName
functions since there is no object on which these functions bear (and thus
no value to encapsulating them).  You will, however, find these functions
useful in network programming.  See pp. 116-139 of the Paradox Engine 
"C Reference" for more information.  

//-------------------------------------------------------------------------
Function: int LockFile(int lockType);  [See PXNetFileLock ]
 See
Paradox Engine for lockType documentation.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int UnlockFile(int lockType);  [See PXNetFileUnlock ]

Unlocks the object's Table file.  You may want to use UnlockTable instead. 
See Paradox Engine for lockType documentation.

Return Value: PXSUCCESS or other engine error code.  

//-------------------------------------------------------------------------
Function: int LockRecord(LOCKHANDLE &lckHandle);   [See PXNetRecLock  ]

Makes sure that the current record of the underlying table points to this
object (updating or inserting a record as necessary) and then locks it.
If the lock succeeds, the lckHandle parameter holds the handle for this 
record.  You can return to this record, knowing that it hasn't been touched 
by another user, using the GotoLock function.  The IsRecordLocked function 
(next) can be used to query an object whether the current record has a 
lckHandle on it. It is an error to lock one record more than once.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int IsRecordLocked(int &isLocked);  [See PXNetRecLocked  ]

Makes sure that the current record of the underlying table points to this
object (does not update or insert record) and then queries its lock status.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int UnlockRecord(int lckHandle);       [See PXNetRecUnlock ]

Unlocks the record locked with lckHandle. Does not change the record
pointed to or the object itself.

Return Value: PXSUCCESS or other engine error code.  

//-------------------------------------------------------------------------
Function: int GotoLock(LOCKHANDLE lckHandle);  [See PXNetRecGotoLock  ]

Moves to the locked record and fills the object with the data in this
record.

Return Value: PXSUCCESS or other engine error code.  

//-------------------------------------------------------------------------
Function: int LockTable(int lockType);    [See PXNetTblLock ]

Locks the underlying table with the given lockType.  For example, if the
"Client" object uses the "client" table, then that table is locked.
See Paradox Engine for lockType documentation.

Return Value: PXSUCCESS or other engine error code.  


//-------------------------------------------------------------------------
Function: int UnlockTable(int lockType);  [See PXNetTblUnlock ]

Unlocks the lock placed on the underlying table by the LockTable function
described above. See Paradox Engine for lockType documentation.

Return Value: PXSUCCESS or other engine error code.  


//-------------------------------------------------------------------------
Function: int IsTblChanged(int &isChanged);        [See PXNetTblChanged ]

Query function.  Stores TRUE or FALSE in the isChanged parameter.

Return Value: PXSUCCESS or other engine error code.

//-------------------------------------------------------------------------
Function: int TableRefresh();    [See PXNetTblRefresh ]

This function resynchronizes the internal PXEngine cache with the database
in case any changes have been made.  Should test with IsTblChanged before
calling this function.

Return Value: PXSUCCESS or other engine error code.


***************************************************************************
                      The Blob Class - Introduction

The Blob class allows you to store large text or binary objects in the
Object Engine database.  Most of the Object Engine Blob methods provide
a thin encapsulation around the corresponding Paradox Engine function
calls.  They do simplify blob programming, however, since they take care
of the record and field handles for you.  The real value added lies in the
OEGet and OEPut functions.  Not only do these functions provide one-call
storage and retrieval of blob data, they also illustrate blob programming
techniques that you will use when using the more basic functions.  You will
need the more primitive functions only if you need to store blobs whose size
exceeds 64K bytes.

You will use Blob objects by declaring a field to be of type 'Mxxx', 'Bxxx',
'Fxxx', 'Oxxx', or 'Gxxx' in the code generator (where xxx is some positive 
integer).  When you generate code, one or more of the fields in your class
will be instances of class "Blob".  To work with the blob data, you will 
use the blob pointers attached to this class instance.  For example, if the 
field "imageblob" is a blob field, the following would store data in the blob and
write the blob to the "public" area of the blob file (see the PXEngine 
documentation for a discussion of Private and Public blobs):

    // Put the buffer into the blob using the ObjEngine shortcut...
    dbclass.imageblob.OEPut(imagebuffer, BUFLEN);
    // NOTE: Must STORE the class before its blob is publicly stored!
    dbclass.Store();

An illustration of Blob programming using the OEGet and OEPut functions 
can be found in the blobmain.cpp file in this package.

Of course, all of the PXEngine blob functions are covered as well.  These 
functions follow the same naming convention as the Paradox Engine except 
that "PXBlob" is stripped from the front of the function name.  Since these
are methods of the Blob class, the prefix is redundant.  Their arguments 
are also the same as the Paradox Engine and in the same order EXCEPT the
RECORDHANDLE and FIELDHANDLE arguments are always omitted.  Record and 
Field are implied by the Blob object you are using (the Persistant Object
you are working with holds the record handle, the Blob instance within the
persistant object gives the field handle).  These functions are documented
below.

***************************************************************************
                    Blob Class Function Documentation

All of the functions in this category that return a value (except operator=)
return the last Engine state (PXSUCCESS, PXERR_XXX).

//-------------------------------------------------------------------------
Constructor: void Blob(OEHandle &, FIELDHANDLE)
You should never have a reason to use this constructor.  Leave it
alone- its bad for your health.  This function is used in the persistant
class constructor once we know some facts about the table.  The FIELDHANDLE 
given to this function corresponds to the field given in Gen2.  The OEHandle
argument is responsible for letting a Blob know which table record it 
works with.

//-------------------------------------------------------------------------
Function: Blob &operator=(const Blob &inblobobj)
Used in the Persistant Object operator= so we can just set one blob equal
to another without worrying about internal variables.

//-------------------------------------------------------------------------
Function: int Clone()
See PXBlobClone: creates a private blob so no one else screws you up by
changing a blob while you are in the middle of reading one.

//-------------------------------------------------------------------------
Function: int Close(int accept)
See PXBlobClose: Closes blob opened by OpenRead or OpenWrite.  Accept 
determines the final disposition of blob (see PXEngine doc!).

//-------------------------------------------------------------------------
Function: int Drop()
See PXBlobDrop: Clears space taken by a blob.

//-------------------------------------------------------------------------
Function: int OpenRead()
See PXBlobOpenRead: Prepares a blob for reading (opens a blob handle).  
Don't worry about the blob handle when you open a blob!  I automatically 
keep it around for your next Get or GetSize operation so you don't even 
have to know that one exists.

//-------------------------------------------------------------------------
Function: int OpenWrite(unsigned long size, int saveCurrent)
See PXBlobOpenWrite: Prepares a blob for writing (opens a blob handle).
Again, don't worry about the blob handle, that's why you have me around...
See PXEngine documentation for saveCurrent information.

//-------------------------------------------------------------------------
Function: int GetSize(unsigned long *size)
See PXBlobGetSize: Returns the size of a blob you have opened (usually for
reading).  Generally, this function is what tells you how many times you
have to call Get.

//-------------------------------------------------------------------------
Function: int Get(unsigned size, unsigned long offset, void* buf)
See PXBlobGet: Puts blob data into buf.  All of the handles (record, field,
and blob) are taken care of for you.  Called in chunks to get all of the
data (limit 64K at a time).  If you know you will only need one Get, then 
use OEGet and skip all the rest of this stuff.

//-------------------------------------------------------------------------
Function: int QuickGet(int bufsize, void* dest, int* bytesRead)
See PXBlobQuickGet: Grabs whatever header data you've stored directly in
the table (up to 240 bytes).

//-------------------------------------------------------------------------
Function: int Put(unsigned size, unsigned long offset, void* buf)
See PXBlobPut: Puts buf into private blob.  All of the handles (record, field,
and blob) are taken care of for you.  Called in chunks to put all of the
data (limit 64K at a time).  If you know you will only need one Put, then 
use OEPut and skip all the rest of this stuff.

//-------------------------------------------------------------------------
Function: int OEPut(char *buff, unsigned size)
See example above and source code.  Just stick "buff" in a blob.  Doesn't
get written to the "public" area (sort of like "written to the table") 
until you do a "Store" on the persistant object to which the blob belongs.

//-------------------------------------------------------------------------
Function: int OEGet(BlobData &bdata)
Hey, this Blob stuff is easy after all, isn't it?  Grab a blob using this
function.  The BlobData structure is a struct:

typedef struct {
  char *data;        <-  Holds the blob data
  unsigned size;     <-  Holds the size of the blob
} BlobData;

This is used so that you will have your data and know how big it is too...
Note that when you create an instance of BlobData before calling OEGet, 
the "data" field is empty (points to nothing).  When you call OEGet, 
I create a buffer of the appropriate size, make "data" point to it, and 
place the buffer size in "size".  Finally, I read the blob data into the 
"data" field of the BlobData struct and return.  YOU have to remember to 
delete the memory that "data" points to.  After all, I can't do everything 
for you. For an example of OEGet see the "blobmain.cpp" file. 

//-------------------------------------------------------------------------
Function: int GetStatus() const
Returns the status value.  This value is always set to the return code
from the last PXEngine function call (which is also what most functions
return).  Especially useful for testing the result of a constructor call!

//-------------------------------------------------------------------------
Function: int GetRHdl() const and int GetFHdl() const 
For internal use.  You can use these to tell you what the blob thinks its
handles are.

***************************************************************************
                   Object Engine Methods of Interest

The object engine class has a number of methods that may be of interest
in your database programming.  First, the constructors for this class are
responsible for setting the Paradox Engine defaults and initializing the
engine.  You can change the arguments to this declaration to tell the object 
engine where your data base files are or to give it information about your 
network status.  Note that you can NOT change the cache size or number of 
table handles using the objeng constructor parameters anymore.  Instead,the 
parameters should be set up with capacities appropriate for your application 
(once) by modifying the ObjEng constructor in the objeng.cpp file. 

                             ***IMPORTANT***
When Gen2 generates code for you, a global "objeng" pointer variable 
is placed at the top of the generated file.  It is up to you to include an
"extern" pointer to objeng in your "main" cpp file and to initialize objeng
in your winmain() or main() function.  

By default, the Object Engine supports 4 tables and a 16K cache.

//-------------------------------------------------------------------------
ObjEng::ObjEng(char *tableDirectory, char *userName, char *netNamePath, int netCode) or

There are no longer separate constructors for Windows and DOS.  The same 
constructors are used in both cases.

The first constructor is used if you wish to declare a table directory (where
database files are to be stored).  Under Windows or in a DOS network 
environment you should declare a userName (generally the user's name or the 
name of the application).  This is the name the application instance will be
known by when locks are placed.  The netNamePath parameter specifies where
the network lock files are located (use the directory only: "q:\\paradox\\").
The netCode parameter specifies the kind of network connection (see page 122
of the "C Reference" manual).

Important WINDOWS 3.1 Note: The ObjEng constructor now takes care of changing
the WIN.INI file automatically if the application's resource needs exceed the
settings in WIN.INI.  Thus you do NOT have to distribute and document the 
PXENGCFG.EXE file. I always thought it was a little stupid to have the 
application blow up if the configuration was shy of a few table handles rather
than just change the configuration.  So...I fixed it!  See the objeng.cpp file 
for details.

//-------------------------------------------------------------------------
ObjEng::ObjEng()

This constructor uses defaults for all of the needed parameters (directory
is the same as the application, name is "Objeng", and no network is 
declared).

//-------------------------------------------------------------------------
Functions: 
int ObjEng::DropKey(char *tblname, FIELDHANDLE fldID)
int ObjEng::RegisterKey(char *tblname, int nIdxFields, 
                            FIELDHANDLE *IdxFieldHandles, int idxType)
int ObjEng::RegisterCompKey(char *tblname, int nIdxFields, 
                            FIELDHANDLE *IdxFieldHandles, char *keyName, 
                            int idxType, int caseMode)

These functions add or drop keys from the named tables.  You cannot use 
the RegisterXXX functions while the corresponding tables are open.  They 
can safely be used in the constructor in the IsRegistered block between
"EnsureExist" and "Register".  See the generated constructors for examples
of how to use these functions.  The "idxType" arg is PRIMARY, SECONDARY or 
INCSECONDARY (PRIMARY not allowed in RegisterCompKey).  The "caseMode"
argument is either CASESENSITIVE or CASEINSENSITIVE.  

The DropKey function is a wrapper for the Engine's PXKeyDrop function.


//-------------------------------------------------------------------------
** Please avoid using any other Object Engine class methods.  
   These are for internal book-keeping only and their inappropriate use 
   may result in corruption of your databases.


           Copyright (c) 1993, Brittingham Software Design, Inc.
