7. EXAMPLES

7.1 Complete Program With OrgC++
7.2 Converting Real Data into OrgC++ Organization
7.3 Saving With Virtual Functions
7.4 Advanced Example: DLL Under WindowsNT

7.1 COMPLETE PROGRAM WITH ORGC++

This chapter contains a simple but complete program with OrgC++. Comments have been included to explain each step. If you are a new user, read this program carefully. You may also type it into a computer and try to modify it (if you type every statement, you'll notice things that you may miss if you just read the existing code). This program is a C++ version of the program test15a.c, available on the distribution disk. If you understand this example, you understand the essence of OrgC++.

The task is to read a list of employee records, link the records into two lists, sort one alphabetically and the other by salary, and then print both lists. Input format: emplName salary.

In OrgC++, rings are always used instead of NULL ending lists. Operations on both organizations are very similar, but rings allow better protection against run-time errors.

The OrgC++ library provides two types of rings:

In the following program, the COLLECTION is used. The Header object encapsulates the entry points for both lists.

#include <stdio.h> // usual io include 
#define ZZmain // main file for OrgC++ global variables 
#include "zzincl" // OrgC++ will generate this file 
class Header { // header for both lists 
    ZZ_EXT_Header // automat.controlled pointers
public:
}; 
class Employee { // employee record 
    ZZ_EXT_Employee // automat.controlled pointers 
public:
    static int sfun(const void*,const void*); 
    static int nfun(const void*,const void*); 
    char *name; 
    int salary; 
}; 
ZZ_HYPER_SINGLE_COLLECT(bySalary,Header,Employee); 
ZZ_HYPER_SINGLE_COLLECT(byName,Header,Employee); 
ZZ_HYPER_UTILITIES(util); 
#define BSIZE 80 
main(int argc, char **argv){ 
    char name[BSIZE],buff[BSIZE],*nn; 
    FILE *file1,*file2; 
    Header *hp; // header for both lists 
    Employee *ep; //just a pointerT 
    int sal; 
    if(argc<=1)file1=stdin; else file1=fopen(argv[1],"r"); 
        if(argc<=2)file2=stdout; else file2=fopen(argv[2],"w");A 
    hp=new Header; // automatically empty lists 
    while(fgets(buff,BSIZE,file1)){ 
        sscanf(buff,"%s %d",name,&sal); 
        ep=new Employee; // get new Employee object 
        nn=util.strAlloc(name); // allocate and copy name 
        ep->name=nn; 
        ep->salary=sal; 
        bySalary.add(hp,ep); // add to one list 
        byName.add(hp,ep); // add to the second list 
    } // all input loaded in 
    bySalary.sort(Employee::sfun,hp); // sort one list 
    byName.sort(Employee::nfun,hp); // sort the second list 
    bySalary_iterator sIter(hp); 
    while(ep=sIter++){ // traverse list bySalary 
        fprintf(file2,"%s %d\n",ep->name,ep->salary); 
    } 
    fprintf(file2,"\n"); 
    byName_iterator nIter(hp); 
    while(ep=nIter++){ // traverse list byName 
        fprintf(file2,"%s %d\n",ep->>name,ep->>salary); 
    } 
} 
// Compare functions are similar to qsort(). 
// Return: -1 for p1<p2,0 for p1==p2,+1 for p1>p2
int Employee::sfun(const void *p1, const void *p2){ 
    Employee *e1,*e2; 
    e1=(Employee *)p1; e2=(Employee *)p2; 
    if(e1->salary<e2->salary)return(-1); 
    if(e1->salary>e2->salary)return(1); 
    return(0); 
} 
int Employee::nfun(const void *p1, const void *p2){ 
    Employee *e1,*e2; 
    e1=(Employee *)p1; e2=(Employee *)p2; 
    return(strcmp(e1->name,e2->name)); 
} 
#include R zzfunc.cR // class generator creates this file.

We assume that we are running on PC DOS with BorlandC++ 3.0, Organized C++ has been installed in the file c:\orgC , and the program and input file are both in the directory c:\test\myDir, called test.cpp and inp. BorlandC++ has been installed in c:\bc, and we are using the medium memory model.

First we run the class generator: try1 test
Then we compile and link: btry2 test
And then we execute the program: test inp out

Input (file inp)

White, J. 1100
Brown,G. 850
Jones,C. 200
Green,F. 900
Jones,S. 450
Black,R. 790
Moody,S 950

Result of the run (file out):

Jones, C. 200
Jones, S. 450
Black, R. 790
Brown, G. 850
Green, F. 900
Moody, S. 950
White, J. 1100
Black, R. 790
Brown, G. 850
Green, F. 900
Jones, C. 200
Jones, S 450
Moody, S. 950
White, J. 1100

Comment:

Files: try1.bat and btry2.bat below show how to invoke the class generator, and how to compile and link.

try1.bat: c:\orgC\zzprep %1.cpp
try2.bat: bcc -mm -Vt -Lc:\bc\lib -Ic:\bc\include %1.cpp c:\orgC\lib\bmlib.lib

7.2 CONVERTING REAL DATA INTO ORGC++ ORGANIZATIONS

Programming with OrgC++ enforces the object oriented style of programming. Before you start to code, you have to plan not only the logic of the program, but also the organization of the data.

The relations between data, usually represented as a network of pointers, must be reduced to standard data sets such as linked lists, trees, or hash tables.

Depending on your personal background, this can be done in three different ways:

Our experience is that most users of OrgC++ libraries fall into the second (middle) category. For that reason, we will start from pointer diagrams in the two examples below.

Example 1:

An electrical netlist (VLSI chip or printed board layout) involves the following entities. A library contains masters of cells; each cell has formal terminals; each formal terminal is a logical entity; connections to a formal terminal may enter the cell through one or more physical pins. The whole chip, and each block of the layout is composed of instances of cells connected by signal nets. A cell has actual terminals (which are instances of formal terminals), and actual pins (which are instances of formal pins). A signal net connects one or more actual terminals.

The following diagram not only describes the data relations; it also contains the decision about which traversals require fast access. For example, if you plan to build the layout and gradually add new cells, a singly-linked list of cells is the best solution. If you plan to delete cells often, a doubly-linked list works better.

The two-sided relation between cell and aTerm, and between net and aTerm is sometimes called a netlist, and is the logical skeleton of the whole organization.

Pins are the most abundant object here, and if not handled carefully, they can consume a lot of memory. For this reason, actual pins are usually not stored, and are only derived. For a given aTerm, the program can quickly get its corresponding fTerm, and from there all its formal pins. The position of the pins is derived on-the-fly by combining the position and rotation of the cell with the coordinates of the pin within the master cell.

Usually, when deciding on an OrgC++ organization, we try to extract the largest organizations first. In this example, there are no trees or graphs, only several aggregates (formerly called triangles, see Chap.11.3). These aggregates are: aTerms under Instance, aTerms under Net, and fTerms under Master. The paths from a fPin to the fTerm and from a net to the block are usually not required; we use a collection, which is less memory expensive than an aggregate, because it does not require the parent pointer.

The whole organization can be declared in several lines:

ZZ_HYPER_SINGLE_COLLECT(inst,Chip,Instance);
ZZ_HYPER_SINGLE_COLLECT(nets,Chip,Net);
ZZ_HYPER_SINGLE_COLLECT(lib,Library,Master);
ZZ_HYPER_SINGLE_AGGREGATE(terms,Master,fTerm);
ZZ_HYPER_SINGLE_AGGREGATE(byInst,Instance,aTerm);
ZZ_HYPER_SINGLE_AGGREGATE(byNet,Net,aTerm);
ZZ_HYPER_SINGLE_COLLECT(pins,fTerm,fPin);
ZZ_HYPER_SINGLE_LINK(iLink,Instance,Master);
ZZ_HYPER_SINGLE_LINK(tLink,aTerm,fTerm);

Example 2:

This example involves a collection of real-time data of the eye positions of people watching a television program.

The data relates to several video tapes and several people. One experiment consists of recording a sequence of x,y coordinates from one viewing, and should provide easy comparison of recordings for the same subject and a different person.

There are many xy coordinates in each sequence, and storing them in an array saves memory. Each sequence has a different number of recorded positions (the length of observation is somewhat random), therefore a dynamic array is required. The diagram below shows a sketch, such as one encounters when discussing a new project.

When designing the data organization, we have to consider which relations are most frequently accessed:

This description translates into the following OrgC++ declaration:

ZZ_HYPER_SINGLE_RING(pRing,Person);
ZZ_HYPER_SINGLE_RING(sRing,Subject);
ZZ_HYPER_SINGLE_AGGREGATE(byPerson,Person,Sequence);
ZZ_HYPER_SINGLE_AGGREGATE(bySubject,Subject,Sequence);
ZZ_HYPER_ARRAY(positions,Sequence,Eye);
ZZ_HYPER_NAME(pName,Person);
ZZ_HYPER_NAME(sName,Subject);

7.3 SAVING TO DISK AND VIRTUAL FUNCTIONS

Naturally, when programming in C++, one of the most important questions is how to save objects when virtual functions are used (persistency). With virtual functions, pointers forming the data organization often connect only to the object from which other objects are derived. When traversing or saving the data the programmer does not have direct access to the derived objects. The following program is a typical example. If you want to try or modify this example, the source code is in orgC/test/test23a.c.

In this example, instead of using the global util.save() function, which collects all objects and saves them, we traverse all base objects and save the derived objects using the virtual function save().

The program forms a ring of Shapes, where each Shape can be either a Square or a Rectangle. The whole organization is saved on disk using ASCII format, then brought back into memory, saved again for testing purposes, this time in the binary format, then retrieved again, and the result is printed. In both input/output a Square is described by one side, a Rectangle with two sides.

#include <stdio.h>
#define ZZ_INHERIT
#define ZZmain
#define ZZasci
#include "zzincl.h"

class Root
{ 
    ZZ_EXT_Root // OrgC++ pointers are private
public: 
    void save(char *); 
}; 
ZZ_FORMAT(Root,""); // needed for ASCII save only
class Shape {
    ZZ_EXT_Shape
public
    virtual void prt(void){};
    virtual void save(char *){}; 
}; 
ZZ_FORMAT(Shape,""); 
class Square : public Shape{ 
    int x; 
    ZZ_EXT_Square
public: 
    void prt(void){printf(R Square %d\nR ,x);}; 
    void set(int a){x=a;};
    virtual void save(char *file);
}; 
ZZ_FORMAT(Square,"%d,x"); // all classes with ZZ_EXT_..
class Rectangle : public Shape{
    int x,y; 
    ZZ_EXT_Rectangle
public:
    void prt(void){printf("Rectangle %dx%d\n",x,y);};
    void set(int a,int b){x=a; y=b;};
    virtual void save(char *file); 
}; 
ZZ_FORMAT(Rectangle,"%d %d,x,y");
void Root::save(char *file){ZZ_STORE(Root,file);};
void Square::save(char *file){ZZ_STORE(Square,file);}; 
void Rectangle::save(char *file){ZZ_STORE(Rectangle,file);};
ZZ_HYPER_SINGLE_COLLECT(all,Root,Shape); // Root contains Shapes
ZZ_HYPER_UTILITIES(util); // needed for save() and open()
#define BF 80
char buff[BF];
int main(void)
{ 
    char c[20],*v[1],*t[1];; 
    int x,y;
    Root *rt;
    Square *sp; 
    Rectangle *rp;
    Shape *pp;
    void prtData(Root *);
    rt=new Root; // all data under one root 
    while(gets(buff)){ // read until the EOF 
        sscanf(buff,"%c",c); 
        switch(c[0]) { 
            case 'S': 
                sp=new(Square); 
                sscanf(buff,"%c %d",&c,&x); 
                sp->set(x); 
                all.add(rt,(Shape *)sp);
                break; 
            case 'R': 
                rp=new(Rectangle); 
                sscanf(buff,"%c %d %d",&c,&x,&y); 
                rp->set(x,y);
                all.add(rt,(Shape *)rp); 
                break;
            default: printf("wrong input code\n"); 
            } 
        } 
        prtData(rt); // print all data as loaded
        rt->save("afile"); // save the root 
        all_iterator it(rt); // initialize iterator 
        while(pp=it++){ // traverse the collection
            pp->save(R ); // save each object 
        } 
        util.close("afile"); // close the file 
        util.open("afile",1,v,t); // retrieve data from "afile"
        rt=(Root *)v[0]; // recover key entry 
        prtData(rt); // prints the second copy now in memory 
        util.mode(0,1,0,0); // switch to binary format
        rt->save("bfile"); // save the root 
        it.start(rt); // re-start the iterator 
        while(pp=it++){ // traverse the collection
            pp->save("bfile"); // save each object
        }
        util.close("bfile"); // close the file
        util.open("bfile",1,v,t); // retrieve data from bfile
        rt=(Root *)v[0];
        prtData(rt); // prints the third copy now in memory 
        return(0); 
    }
    //____________________________________________________________ 
    void prtData(Root *rt) //print out all data 
    { 
        Shape *pp; 
        all_iterator it(rt); 
        while(pp=it++){ pp->prt(); }
    } 
    #include "zzfunc.c"

INPUT DATA:

S 5
S 8
R 2 6
R 3 1
S 7

COMMENT:

Instead of saving individual objects, as shown above, the entire data can be saved in one command:

v[0] = (char*)rt; t[0] ="Root";
util.save("afile",1,v,t);

OrgC++ saving utility is intelligent enough to traverse the collection of shapes, to detect the type of each shape, and to invoke the appropriate saving function automatically.

IMPORTANT:

Read the comment about initialization of objects and the use of ZZ_INIT() on Chap 13.2. This macro must be in all constructors, if you use automatically allocated objects.

7.4 ADVANCED EXAMPLE: DLL under WindowsNT

The purpose of this example is to demonstrate the overall organization and settings needed for the VisualC++ environment, without going into details of the actual coding. This is not an introductory example - it assumes you already know quite a bit about OrgC++, and you are using the Microsoft Development Studio with the Visual C++ under Windows NT.

We have the problem as shown in the following diagram. The program module with classes A,B,..,G uses the OrgC++ library, and should be implemented as DLL (dynamic link library). Class A provides the external interface through functions A::a1(), A::a2(), A::a3(). For each of the library classes, we will have two usual files, *.h and *.cpp. The outside world does not know about classes B,C,...,G; it knows only A.h. This DLL (we will call it mylib ) uses its own, local copy of OrgC++, which must not interfere with other parts of the system that may also use OrgC++.

Also, in this case, the library must call some external functions, for example when reporting errors or special events. Let us assume that these external functions are methods of class X:

Classes A,B,...,G form the organization shown in the next figure. This diagram matches the ZZ_HYPER_.. statements recorded in file hyper.h which is a part of the mylib project.

To make this example more realistic, let us assume that mylib may need to save/restore its internal data from disk, and let us place this functionality into A::a1() and A::a2(). Note that we will not save the instance of A which is not an OrgC++ registered class. It does not have the ZZ_EXT_A statement because class X does not know about OrgC++ and file zzincl.h . The pointer between A and D is just a regular pointer, not a ZZ_SINGLE_LINK.

Here is the file hyper.h , which also includes the functions required for the hashing:

ZZ_HYPER_HASH(cHash,D,C);
ZZ_HYPER_SINGLE_AGGREGATE(bAggr,D,B);
ZZ_HYPER_SINGLE_AGGREGATE(fAggr,B,F);
ZZ_HYPER_DOUBLE_COLLECT(cColl,F,C);
ZZ_HYPER_SINGLE_TREE(gTree,D,G);
ZZ_HYPER_SINGLE_LINK(eLink,G,E);
ZZ_HYPER_NAME(cName,C);
ZZ_HYPER_NAME(eName,E);
ZZ_HYPER_UTILITIES(util);

//----------------------------------------------------
//		A
//		|ptr
//		v  gTree	eLink	eName
//	+-----o D o-----x G ----- >E ----- >string
//	|	o
//	|	|bAggr
//	|	|
//	|	x  fAggr	cColl	cName
//	|	B o-----x F o---- x C ----- >string
//	|			    x
//	|		cHash	    |
//	+---------------------------+
//
//----------------------------------------------------- 

inline int cHash_class::cmp(C *c1,C *c2){...}
inline int cHash_class::hash(C *c, int size){...}

In addition to files A.cpp, B.cpp, ... , G.cpp, you also need file func.cpp, which represents the 'main' file for OrgC++, and includes file zzfunc.c generated by the OrgC++ code generator.

File func.cpp:

#include <stdio.h>
#include <string.h>
#define ZZmain
#include "zzincl.h"
#include "A.h"
#include "B.h"
#include "C.h"
#include "D.h"
#include "E.h"
#include "F.h"
#include "G.h"
#include "zzfunc.c"

Implementation of each class depends only on the classes and ZZ_HYPER_.. statements that relate directly to each class. For example, for class G, we have file G.h:

class G { 
    ZZ_EXT_G 
public:
    G(); 
    int fun(char *c); 
    ... 
}; 
ZZ_FORMAT(G,"..."); // only when ASCII saving to disk

File G.cpp depends on files D.h, E.h, and G.h:

#include <stdio.h>
#include <string.h>
#include "zzincl.h"
#include "D.h"
#include "E.h"
#include "F.h"
ZZ_HYPER_SINGLE_TREE(gTree,D,G);
ZZ_HYPER_SINGLE_LINK(eLink,G,E);
G::G(){ ... }
int G::fun(char *c){ ... }

In order to invoke the OrgC++ code generator, you will need a special batch file which we will call generate.bat :

copy c:\orgc\lib\envmsft.h environ.h
copy A.h all.inc
type B.h > all.inc
type C.h > all.inc
type D.h > all.inc
type E.h > all.inc
type F.h > all.inc
type G.h > all.inc
type hyper.h > all.inc
c:\orgc\zzprep all.inc

The definition of classes A and X requires an intricate definition of how to communicate with the other part of the project. Class A keeps a pointer to each of the external functions:

File A.h:

#ifdef A_SIDE
#define A_EXPORT __declspec(dllexport)
#else
#define A_EXPORT
#endif

class A_EXPORT A {
typedef void (X::*x1Type)();
typedef int (X::*x2Type)(int);

x1Type x1Fun;
x2Type x2Fun;
D *dPtr;
X *xPtr;
public:
A(X *xp, x1Type x1f,x2Type x2f);
void a1(char *fileName); // save data
void a2(char *fileName); // restore data
int a3(); // whatever
...
};

File A.cpp:

#define A_SIDE 
#include <stdio.h>
#include <string.h> 
#include "zzincl.h"
#include "A.h"
#include "X.h"
ZZ_HYPER_UTILITIES(util);
A::A(X *xp, x1Type x1f, x2Type x2f){
    xPtr=x; 
    x1Fun=x1f; 
    x2Fun=x2f; 
    ... 
} 
void a1(char *fileName){
    void *v[1]; char *t[1]; 
    v[0]=dPtr; t[0]="D"; 
    util.save(fileName,1,v,t); 
}
void a2(char *fileName){ 
    void *v[1]; char *t[1]; 
    util.open(fileName,1,v,t); 
    if(!util.error() &&
    !strcmp("D",t[0]))dPtr=(D*)(v[0]); 
    else (xPtr-*x1Fun)();
} 

Note the use of X::x1() in the last statement of the implementation of A::a2()!!

Since X.h is needed for class A, its definition also has to reflect the arrangements about the interface. Here is file X.h:

#ifdef A_SIDE 
#define X_EXPORT __declspec(dllimport)
#else
#define X_EXPORT
#endif 
class X_EXPORT X { 
    ... 
public
    void x1(); 
    int x2(int i); 
    ... 
}; 

And file X.cpp:

#include "X.h"
#include "A.h"

void X::x1(){ ... }
int X::x2(int i){ ... }

Here are the suggested Build/Settings values for this situation. Don't forget that for Debug/Release options, you have to link to a different version mllib (see Chap.2.4).

General: Nothing special
Debug: Nothing special
Custom Build: Nothing special (see below)
C/C++: Precompiled headers, not using them
Link: Object/library modules: include c:\orgc\lib\mllib.lib
    Input: ignore libraries: libcd.lib

Microsoft Developer Studio offers `Custom Build' and `Preprocessor' options, but these options seem to fit only primitive preprocessors. We have not succeeded to apply them to the OrgC++ code generator, zzprep. We found that it does not cause much difficulties to call generate.bat manually (either from `run' or from the DOS window) when modifying any ZZ_HYPER_.. statement or class members in any of the *.h files.  

 

Chapter 6: What is OrgC++? Chapter 8: Syntax