| | The Object Engine | C++ Class Library | and Code Generator | for The Paradox Engine | | Version 0.9 | | 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 large degree of OODBMS functionality to engine users. Rather than spending your time writing functions to copy data back and forth between data structures and record buffers, you need only call "Store", and "Link" methods of the object engine's 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 (a sample is included to help guide you). The library covers Alphanumeric, Short, Long, Double, Float (currency), Date and List types (see below). Support for Blobs will be added in the version 2.0. In addition to these features, the object engine provides a unique database list class that allows you to store a heterogeneous set of persistant objects. You can construct arbitrarily complex data relationships without the need for common (relational) fields by placing pointers to these lists within persistant classes. You gain nearly all of the power of a full fledged OODBMS simply by adding the Object Engine library to your current Paradox Engine application. Plus, the library is very efficient: it adds less than 13K to your application (with speed optimization). Distribution Policy ------------------- The files contained on this disk: objeng.cpp, objeng.hpp, manual.txt, and GEN2.EXE, are all copyrighted material. However, you may use, copy and distribute this software free of charge under the following conditions: 1. Never change any part of the Copyright statement. 2. All of the enclosed files must be distributed together. 3. You must not distribute changed versions of the program. I do not want to be receiving correspondence about bugs that I have not produced. If you have a suggestion, contact me at the address below and I will consider your suggestion. If I incorporate your change, you will be credited in the documentation. 4. The author is not liable for any damage on your side caused by the use of this program. 5. The author has no duty to remedy for the deficiencies of the program. Copyright (c) 1992, Brittingham Software Design, Inc. 6. If you wish to distribute this software with publications or a product, you must print the BSDI copyright statement somewhere on the disk or on the package. As long as those conditions are satisfied, you do not need to get the author's permission to use or to distribute the software. How to contact Brittingham Software Design ------------------------------------------ The best way to contact me is to send MAIL to me on CompuServe: 72740,2244. If you do not have a CompuServe account, you may write to me at: Dr. Mark Brittingham 15 Pheasant Ct. Flanders, NJ 07836 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 probably best to either subclass the generated classes or at least to keep your own code separate. 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. The objeng.cpp file is included in the Object Engine Zip file. 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 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. Copyright (c) 1992, Brittingham Software Design, Inc. 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) 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. You need to keep this in mind when working with persistant classes. For example, 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. That is, storage is mediated by the objectID. An object without an objectID will be stored as new (assuming that there are no conflicting key fields). An object whose objectID matches a record found in the database will update that record. If you simply keep in mind that an object has an identity that you must respect, you will have no problem using the library. *************************************************************************** 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, c) has one or more matching "SetXXX" functions to set the variables, d) has one or more "GetXXX" functions to get the value of variables, and e) has only six other methods (2 Constructors, operator=, Store, Retrieve, and LinkToKey). If a class contains a list (an "OEList") of other persistant classes, then there is no corresponding "GetXXX" function and the "SetXXX" function returns a non-const pointer to the actual list. All other returned values are const to ensure encapsulation. You can change the "SetXXX" functions to reflect any validity checking or dependent variable manipulations that your class requires. Since the OEList already features its own encapsulation (and since the code generator architecture made it a pain to make the list variables public), a non-const pointer to the list is returned from the "GetXXX" function. Copyright (c) 1992, Brittingham Software Design, Inc. In addition to the Set and Get functions and the six defined with the class, your class inherits many functions for linking to various records as well as functions for testing status, returning Paradox Engine variables (table and record handles, etc.), and more. 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. *************************************************************************** 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() [format ()] 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 and lists are empty, all scalar values are set to zero. //------------------------------------------------------------------------- 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. OELists are not copied! The same OEList pointer is now used in both objects. If you make a change to the list using the first object, you are also changing the list used by the second. Since the list is meant to reflect the database, this is exactly as it should be! //------------------------------------------------------------------------- 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 (either from a link function or because it was copied from another object) then this will UPDATE the database record. Any object possessing a non-zero objectID is assumed to be linked to the matching db 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). Note that this function reflects the documentation for PXRecUpdate and PXRecInsert: an attempt to enter a duplicate key is an error, a non-keyed table insert will place the record before the current record, etc. Return Value: PXSUCCESS or other engine error code. Copyright (c) 1992, Brittingham Software Design, Inc. //------------------------------------------------------------------------- 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) Note: if the field is not keyed, this function does nothing but return an error. Return Value: PXSUCCESS or other engine error code. //------------------- 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... 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. Copyright (c) 1992, Brittingham Software Design, Inc. //------------------------------------------------------------------------- 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. //------------------------------------------------------------------------- int NRecs(RECORDNUMBER & nrecs) [See PXTblNRecs] Returns the numbers of records in the table that the object works with. 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. Copyright (c) 1992, Brittingham Software Design, Inc. //------------------------------------------------------------------------- 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. Return Value: PXSUCCESS or other engine error code. //------------------------------------------------------------------------- char GetDeleteFlag() char SetDeleteFlag(char flag) GetDeleteFlag and SetDeleteFlag are used in conjunction with OELists. When an OEList is deleted or cleared, it will check the deleteFlag for each of the elements that is points to. If the deleteFlag is TRUE, the object will be deleted. Otherwise it will not. In both cases, any reference to the object is removed from the OEList. Thus, if you create a pointer to an object that you get from an OEList, you should set its deleteFlag to FALSE if you want to delete the list but keep the pointer. Return Value: PXSUCCESS or other engine error code. //------------------------------------------------------------------------- int OEPutDate(const int &fieldno, const struct tm &date) and int OEGetDate(const int &, struct tm &) 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 never need to use these functions since you will not work directly with record buffers. Return Value: PXSUCCESS or other engine error code. Copyright (c) 1992, Brittingham Software Design, Inc. *************************************************************************** Object List (OEList) Methods - Introduction You will generally use OELists by declaring a field to be of type '^' in the code generator. This will generate all of the code you need to Store and Retrieve nested classes. For example, if you have multiple phone numbers for a client, you may want to create a nested list of phone number objects and store it with a client record. To pursue the example, assume that you have declared a class "Client" and a class "Phones." You want to associate zero or more phones with a client but don't want to use a unique relational field in both tables since a given phone might be used by more than one client (a husband and wife, for example). In this case, you can declare the Client class with an OEList called "phones." When working with a client you can add a phone number in the following manner: Client client; Phone phone; client.LinkToField("LNAME", (void *)"Brittingham", SEARCHFIRST); phone.Setnumber("555-1212"); client.Getphone()->Insert((PersistClass *)&phone); client.Store(); Note the following important points: The phone object Inserted into the list and the link to the client object are both immediately Stored. You do NOT need to call Store on an object that you place in an OEList (whether or not the list is owned by another PersistClass object). This means that changes you make to a class's OEList variables cannot be undone simply by not storing the object. You CAN have arbitrarily nested OELists. For example, the Phone object could have a list of people that the phone number serves. This list may include the client object defined above. We avoid recursive infinite loops by NOT retrieving the data in a list until you take an action (such as inserting an object). This also avoids retrieving a all of the data associated with a complex object with nested OELists if you just want to see a single field. If you want to make sure that all of the data associated with an OEList is in memory immediately, you can use the OEList::Init() function. OELists have listIDs (just as objects have objectIDs). When you place objects in an OEList, the objectID of the object and the listID of the list are associated and stored. If you later call the OEList constructor with a listID (and call Init()), the system will search for all objects that were ever put on the list and pull them into memory. This is how OELists are stored on PersistClasses: the listID is stored in the object's table as a LONG. When the object is retrieved, the listID is used to construct a new OEList (see generated source code). The OEList is both a container and an iterator. This is achieved by having the "()" (function) operator return the "current" object on the list. The "++" operator moves to the next object on the list. The Reset() function makes the first object on the list the current object. For example, you can do the following: Copyright (c) 1992, Brittingham Software Design, Inc. OEList objlist; PersistClass* temp; Foo* footemp; objlist.Insert((PersistClass *)fooptr1); //First position objlist.Insert((PersistClass *)fooptr3); //Second position objlist.Insert((PersistClass *)fooptr4); //Third position // objlist() would now return a pointer to fooptr4. // Move to first position before iterating. objlist.Reset(); while ((temp = objlist++) != NULL) { ((Foo *)temp).PrintSelf(); <- Some user-declared function } // OR use a for loop... for (objlist.Reset(); footemp = (Foo *)objlist(); objlist++) { // Insert after fooptr object with ID of 1 (id is user-declared) if (footemp->Getid() == 1) { objlist.Insert((PersistClass *)fooptr2); break; } } *************************************************************************** OEList Class Function Documentation //------------------------------------------------------------------------- Constructor: OEList() Create a new OEList. This object will be a simple shell without ID or any objects inside. //------------------------------------------------------------------------- Constructor: OEList(long inID); Create a new OEList. This object will be a simple shell but will have the given ID. When Init() is called, all objects associated with the given listID will be pulled into memory. //------------------------------------------------------------------------- int Init() Instantiate the given OEList. If no listID has been assigned, generate a new (unique) listID. If a listID exists, pull out all associated objects from the database and store them in memory. Return Value: TRUE or FALSE. //------------------------------------------------------------------------- Destructor: ~OEList(); Deletes all of the objects pointed to by the OEList EXCEPT those whose deleteFlag is FALSE. Copyright (c) 1992, Brittingham Software Design, Inc. Also, this function WILL CAUSE AN ERROR WINDOW OR MESSAGE to pop up if you attempt to delete a list that has multiple pointers to it. That is, whenever an object is copied, it will copy any OEList pointers. In order to be a good citizen, it will also inform each OEList (via the AddReference function, see below) that a new pointer exists. When you want to delete a list, you should make sure that you check this pointer count. The RIGHT way to delete a list is: if ((oelist != NULL) && (oelist->DeleteOK() == TRUE)) delete oelist; The code generator automatically puts this line into the destructors of all classes that have OEList elements. In general, you do not need to worry about this at all unless you are declaring your own lists and creating multiple pointers. If you just use the code generated by Gen2, you are guaranteed never to run into any problems. //------------------------------------------------------------------------- void Insert(PersistClass *curelem) Inserts a persistant class into an OEList. If you put an element into the OEList and want to keep it around even if the list goes away, call the object's SetDeleteFlag function with an argument of FALSE. If you delete an object pointed to by a list you may crash your program if you work with the list later. //------------------------------------------------------------------------- void Remove() Removes the current object from the list (you change the "current object" by iterating through a list- see above). If the deleteFlag is TRUE, this function deletes the object from memory. Does NOT destroy the object's corresponding database record. The on-disk representation of the list is immediately updated to reflect this removal. //------------------------------------------------------------------------- PersistClass *operator()() Returns the current object. Does not increment or otherwise change the state of the list. //------------------------------------------------------------------------- PersistClass *operator++() Returns the current object and then increments the "current object" pointer. //------------------------------------------------------------------------- void Reset() Moves the "current object" pointer to the first element of the list. //------------------------------------------------------------------------- int Count() Returns the number of elements in the OEList. Do not use this for iterating unless you are SURE that you will not be changing the list in your iteration. //------------------------------------------------------------------------- void Clear() Removes all of the elements on the list. See the Remove() function. //------------------------------------------------------------------------- void AddReference() If you will be creating your own pointers to a list, this function is used to inform the list of your pointer. Copyright (c) 1992, Brittingham Software Design, Inc. //------------------------------------------------------------------------- char DeleteOK() Decrements the reference count and returns it. This is the opposite of AddReference. You should not call this function unless you are removing your pointer. Also, you should delete the list if this function returns 1. (See the documentation for the destructor). //------------------------------------------------------------------------- const int GetStatus() Returns the status of the list. Can be any of: NOTINIT, READY, or FAIL. //------------------------------------------------------------------------- const long GetListID() Returns the listID. This number is used in a table to keep track of the associations between lists and objects.