GCOOPE Version 1.0 Generic C-language Object Oriented Programming Extension Version 1.0 by Brian Lee Price Released as Public Domain July, 1994 Table of Contents I. Introduction...............................................1 What is PCOOPE ? II. Version 1.0 Ancestory......................................2 Design goals III. The Kernel System.........................................3 Overview A. List manager...........................................3 List based nature of version 3.1 B. Object list............................................3 The object tracking system C. Generic function list..................................4 Generic (virtual) function database D. The garbage collector and memory management shells.....5 Temporary object management E. Function dispatcher module.............................6 with instance memory management F. The pseudo class 'Object'..............................6 Default method installation G. The pseudo class 'Class'...............................7 Class system installation H. Other planned pseudo classes...........................7 Extending the kernel IV. High Level Data Structures.................................7 Dynamic structures in the PCOOPE kernel A. Object handles..........................................7 B. Instance Data Block.....................................8 C. Class Definition Data Block.............................9 V. Kernel Theory of Operation................................11 By module and function A. listmgr.c module.......................................11 1. addItem.............................................11 Private Function 2. rmvItem.............................................12 Private Function 3. compactList.........................................12 Private Function i B. objlist.c module.......................................13 1. getObject...........................................13 Private Function 2. getObjDef...........................................14 Private Function 3. addObject...........................................14 Private Function 4. rmvObject...........................................15 Private Function C. generic.c module.......................................15 1. tagCmp..............................................16 Public Function 2. addGeneric..........................................16 Private Function 3. rmvGeneric..........................................16 Private Function 4. getMthd.............................................17 Private Function 5. addMethod...........................................17 Private Function 6. rmvMethod...........................................17 Private Function D. garbage.c module.......................................18 1. garbage.............................................18 Private Function 2. makePerm............................................19 Public Function 3. outOfElems..........................................19 Private Function 4. s_calloc............................................19 Public Function 5. s_free..............................................20 Public Function 6. s_malloc............................................20 Public Function 7. s_realloc...........................................20 Public Function E. funcdisp.c module......................................21 1. g...................................................21 Public Function ii 2. steer...............................................22 Public Function 3. getIVptr............................................22 Public Function 4. initInst............................................23 Private Function 5. makeInst............................................23 Public Function 6. getCVptr............................................24 Public Function VI. Pseudo Class Descriptions.................................25 Definition of pseudo class A. Object pseudo class module.............................25 1. Object_Install......................................25 Public Function 2. kill................................................26 Default method for Kill generic 3. err.................................................27 Default method for Err generic 4. getClassOf..........................................27 default for classOf generic 5. getIVsize...........................................28 default for ivSize generic 6. defRespondsTo.......................................28 default for respondsTo generic iii GCOOPE Version 1.0 SDK Debug Kernel Technical Reference Manual Written by Brian Lee Price, July 1994 I. Introduction What is GCOOPE ? Every few days I ask myself this question, and I always come up with a different answer. Originally conceived and designed as an object oriented programming extension to the C programming language, GCOOPE's inherent flexibility makes it something more. GCOOPE is an experimental/educational Object Oriented Programming environment for standard ANSI C. While the following paragraphs will mention a number of possible futures for the package, much work needs to be done in co-operation with the user's group before it achieves even a fraction of its potential. In truth, the structure of GCOOPE may not prove itself to be suitable for commercial applications. In the meantime, or in any case, it is an instructive exploratory framework for experimentation and research into the OOP world. Is it SmallTalk for C ? Not without a preprocessor it isn't, but it is similar. It is worth noting that many Smalltalk techniques will work quite well in the GCOOPE framework. Is it an operating system ? Well, it is an operating system for objects, but no it doesn't have the potential to be a true operating system. (Yet, I will admit to daydreaming about a GCOOPE accelerator in hardware. :). Is it an applications framework ? It could become one once it has been ported to enough systems and appropriate platform dependant expansion modules are written to a standardized interface, after all, portability is a key issue. Is it a system resource ? Well, not right now, but if you encapsulated the kernel as a DLL (or similar construct) and provided instantized kernel memory,...or GCOOPE on a MACH kernel ? One of my main concerns in designing and writing GCOOPE is not to arbitarily close the door to any expansion possibility. The last word on what GCOOPE is will be written in the future. In the meantime, explore.... 1 II. Version 1.0 Ancestory Version 1.0 of the GCOOPE system has been designed to correct some deficiencies in PCOOPE version 2.1 and 3.0, as well as to better support advanced features. The primary design goals have been: 1. Ease of use. 2. Kernel modularity. 3. Kernel expandability. 4. Future dynamic linking and loading support. 5. Future multi-tasking support. 6. Increased flexibility. 7. Memory efficiency. Fortunately, a number of these goals go hand in hand. I adopted a few early strategies to achieve these goals. A modular kernel (written correctly) is an expandable one. An expandable kernel lends itself to implementing flexible approaches. Encapsulation of the kernel and class definitions make it easier to support dynamic linking and loading. A side effect (or a method of) encapsulation is a simpler interface which (hopefully) will make the package easier to use. A glaring shortcoming of PCOOPE version 2.1 was its poor memory efficiency. Another related problem was that of memory fragmentation. GCOOPE Version 1.0 does much to remedy the situation: 1. 'Flag' classes normally require no instance variable memory. 2. Instance variable memory is concatenated, ie. the inherited instance variable memory is included in the owning instance memory block. 3. All internal tracking data is maintained in dynamic tables, these tables are always a multiple of a minimum number of elements in size. This reduces the need for reallocs and the resulting fragmentation. In summary, although the basic nature of GCOOPE remains the same as the PCOOPE system, this implementation has an overhauled design approach. If you liked the concept behind PCOOPE, you'll be suitably impressed by GCOOPE. 2 III. The Kernel System A. List manager. Version 1.0 is based on lists. Central to this approach is the list manager. The list manager in V1.0 is a cross between traditional fixed element size list management techniques and a partial implementation of a memory manager. Each list has a header structure detailed in gcstruct.h. These headers maintain the current pointer to the dynamically allocated list, the size of the list's elements, the maximum number of elements in the currently allocated list size, and a dual purpose index to the first free element slot. The list manager (source code in listmgr.c) is suitable for ordered or unordered lists. It has three routines (addItem, rmvItem, and compactList) and is self-initializing. This simple approach allows the code to be re-used many times in the kernel. The manner in which the list manager is implemented also makes it suitable for maintaining lists of lists. There are two major lists in V1.0; the object list, and the generic function list of lists. The object list is a simple unordered list, and the generic function list of lists is an unordered list of lists ordered by class tag. B. Object list. It really makes no difference conceptually whether objects are referenced by a pointer to a memory location or by an index to a table. Previous versions of GCOOPE used the pointer approach, while workable, it directly led to many of the memory problems. Version 1.0 uses the index approach, the object list is a dynamic table of objectEntry type structures. These structures contain a pointer to the object's memory, the ID of the process they belong to, and a use indicator used by the garbage collector to determine which objects can safely be deleted. The object list doubles as the garbage collector buffer, so in reality, compared to earlier approaches, this approach uses very little additional memory. In fact, due to the organization of the instance memory blocks, it uses quite a bit less. 3 I should point out here that although the type object, which is used as the object handle, is 32 bits or more in size, only 15 bits are available for object table indexing. In both the source code and the documentation, you will see many references to 'tag' value. This is simply the least significant 16 bits of the object handle. C. Generic function list The basic list of generic functions is very similar to the object list, each entry contains a generic function tag value (used as the index), an optional default method address, and a pointer to a dynamically sized list of methods for that function, indexed by class tag. Each entry in the list of methods has a second class tag called owner. This owner tag is used by the current instance tracking mechanism, it is the class tag of the class for which the method is defined rather than inherited. 4 The default method may be called explicitly by passing the class handle Object as the first parameter. Otherwise, if no class method exists for the calling class in the called generic function, and if a default method exists, it will be called. More detail on the actual use of the polyList will appear in the section on the function dispatcher. D. The garbage collector and memory management shells In V1.0 the garbage collector is very similar to the PCOOPE system in V2.1, the only real differences are the use of the object list instead of a dedicated buffer, the addition of process ID awareness, and the direct interface with the memory management shell routines. There are a number of good reasons to use memory management shells with the GCOOPE system. Number one is the need for co-operation between the garbage collector and the memory management functions, next is the effect of eliminating the need for checking the return pointer for a NULL pointer condition. As long as the operation of the default error handler is not modified, a NULL pointer will never be returned. The most subtle reason is to provide a one stop shop for upgrade to an aftermarket memory manager library. When you examine the source code for the garbage collector system, you will see references to the adaptive nature of the garbage collector routine. Basically, depending on the setting of certain constants in gcstruct.h, the garbage collector has two distinct modes. In the normal mode, the garbage collector only examines a few entries in the list each time it is called and it only subtracts a small value from the last access variable. As the counter timeVal times out, the garbage collector control variables will be set to a slower pace, when a memory error occurs, they will be set to a faster rate. In the memory error mode the garbage collector no longer pays any attention to the process ID (except for the permanent process ID) and runs at the maximum values allowed by the constant settings. In effect, the garbage collector system operates probabilistically, with the adaptive algorithm approximating a first order low pass filter. As far as overall processing time is concerned, there are many more efficient approaches, but because the garbage collector operates much like a background task, the overall effect is much quicker in operation than most other algorithms. (Plus its simple.) 5 E. Function dispatcher module In PCOOPE Version 2.1 I hid the function dispatcher behind macros and used a number of of global variables to track methods for each generic function. This proved cumbersome in practice, with GCOOPE the function dispatcher is out in the open (so to speak) and the entire generic function tracking system is implemented via the generic function list. The function dispatcher takes a generic type parameter and through some creative use of the va_... function/macro accesses the first parameter in the call to the method. The first parameter in a method call is always of type object and is the object handle for the instance or class being operated on. The function dispatcher gets the list indexed by the generic parameter and looks up the class of the object handle, if a match is found it will adjust the object handle to point to the correct instance and return the method. However if either the object handle is equal to 'Object' or no match is found, the method returned will be that of the default method. If no default method is registered for the generic function being accessed the method returned will be that of the default error handler. Without going into too much detail (this IS supposed to be an overview), the function dispatcher and memory access system has been completely overhauled from the PCOOPE approach. The index to the object table, along with a current instance offset, and a flag class value are all compacted in a 32 bit object handle. The instance memory block has been concatenated. In other words all ancestor instances are part of the same memory block as the owning class instance. Routines are provided in this module to access the instance and class variables as well as to create an instance block. There is also a routine dedicated to updating the current instance pointer, normally this is done automatically, however in certain cases it must be called directly. (Cases such as inside a New, Kill, or Err method). F. The pseudo class 'Object' Because the class mechanism has not yet been installed prior to installation of 'Object', 'Object' installs itself using low level kernel functions. In fact, 'Object' is not a true class because its methods are installed as the default methods for the generictions it loads. You should not inherit 'Object', its methods are always available. 6 G. The pseudo class 'Class' Similar to 'Object', 'Class' installs using low level kernel functions. 'Class' actually is a true class with methods that are installed as generic methods, however, there is no point to inheriting the class 'Class' since it only deals with the process of installing a class definition object into the GCOOPE kernel. Once 'Class' has been loaded, normal class definitions may be installed. H. Other planned pseudo classes Using the techniques and methods used by and made available through 'Object' and 'Class' it is possible to install other pseudo class modules for the purpose of extending the kernel and the pseudo-classes 'Object' and 'Class'. Such extensions may be used to add dynamic loader/unloader capabilities, multi-tasking, platform GUI support, etc. With the framework provided it is possible to add library functions, generic functions, methods and default methods to existing generic functions, and global variables. All added functions and variables could be published and made available through an add on symbol list manager for dynamic loading support. IV. High Level Data Structures NOTE: in the following discussion, it is assumed that you have a hardcopy of the file gcstruct.h. A. Object handles The typedef for 'object' insures that its size will be at least 32 bits. The type object is used in a variety of ways in GCOOPE, here we will examine the structure imposed on a variable of type object when it is used as an object handle. --------------------------------------- | 31 FLAG | 30 FEXT 16 | 15 TAG 0 | --------------------------------------- OBJECT HANDLE STRUCTURE The actual structure definition for this structure is given in gcstruct.h as objHndl. TAG is an index to the objList list for the object. It may be accessed through the tag member of objHndl. 7 FEXT is the offset from the start of the instance memory block to the current instance. The current memory block is located at the address given by the objDef member of the objectEntry structure indexed by TAG. FEXT may be accessed through the fext member of objHndl. FLAG is cleared to 0 for classes and normal instances, for instances of special 'flag' classes which impose a custom structure onto the OBJECT HANDLE STRUCTURE, FLAG is set to 1. When FLAG is set, FEXT is the actual class tag value. These flag classes either have no instance variables or they use the object handle for instance variable storage. It should be noted that the type object is the default return type for methods. You should cast the return value to the appropriate type. The type object is capable of passing any data type of 32 bits or less in size. B. Instance Data Block The basic structure of an instance data block is simple, it consists of an integer class tag value followed by a variable number of bytes where the instance data can be/is stored. If the class of an instance inherits other classes, those instance data blocks will be appended to the end of the owning instance data block. Where it gets a bit complicated is the common case where the inherited class instance data block has its own inherited instance data blocks appended. The following is a graphical explanation of the instance data block structure for the three cases described: ----------------- | class tag | ----------------- BASIC INSTANCE BLOCK | instance vars | ----------------- ------------------------- | owning class tag | ------------------------- | instance vars | INSTANCE BLOCK WITH ------------------------- | inherited class 1 tag | TWO ANCESTORS ------------------------- | instance vars | ------------------------- | inherited class 2 tag | ------------------------- | instance vars | ------------------------- 8 ----------------------------- | owning class tag | ----------------------------- | instance vars | ----------------------------- | parent 1 class tag | INSTANCE BLOCK WITH ----------------------------- | instance vars | TWO ANCESTORS, EACH ----------------------------- | grandparent 1-1 class tag | WITH TWO ANCESTORS OF ----------------------------- | instance vars | THEIR OWN ----------------------------- | grandparent 1-2 class tag | ----------------------------- | instance vars | ----------------------------- | parent 2 class tag | ----------------------------- | instance vars | ----------------------------- | grandparent 2-1 class tag | ----------------------------- | instance vars | ----------------------------- | grandparent 2-2 class tag | ----------------------------- | instance vars | ----------------------------- Additional levels of inheritance follow the rules described and implied in the above. C. Class Definition Data Block As you can see from the above, while the layout of the instance data blocks is straight forward, keeping track of it is not. This is the job of the class definition data block. Rather than reprinting the relevant portions of gcstruct.h, I'll refer you to them as needed in the following explanation. The first entry in a class definition data block is always a classEntry type structure (described in gcstruct.h). The first member of that structure (class) is set equal to the class tag value. Member cvSize is set to the size of the class variables data area that is shared by all instances of the class. Member ivSize is the size of the class's own instance variables and totSize is the total size of an instance data block for that class (this includes inherited instance data blocks). 9 The numSuper member is the number of immediate superClasses inherited (parents). Member numMthds is the number of defined (not inherited) method functions for the class, while member cVars is the beginning of the class variables storage area. The next area is comprised of numSuper's worth of superEntry structures (see gcstruct.h) and is the data storage area for the instance tracking mechanism. Each entry consists of a class tag value and the offset from the start of the child class's instance memory block where the superClass instance memory block begins. Note that the class member of a superClass entry will match the class tag value located at the given offset within the child instance block. To remain memory efficient, only the immediate parents are listed, however because of the layout of the instance memory block(s), you can find the offset for a great-great-...etc. grandparent instance simply by adding the offsets for each successive parent in the direct lineage. The tail end of the class definition data block is the method data area. This area begins at the end of the superEntry list. It consists of numMthds worth of methodEntry type structures (see gcstruct.h), each entry contains the generic index value of the generic function through which the method is accessed, and the address of the method function. This area is primarily used for inheritance purposes although it could play a roll in dynamically unloading a class. 10 V. Kernel Theory of Operation In the following descriptions, Private Function means that the functions is meant to be called only from within the kernel. Public Function indicates that it will be made available to applications programs. A. listmgr.c module As noted it the Overview, version 1.0 is based on lists. Basic list management is performed by the listmgr module. In the following discussion, please refer to the DYNLIST_STRU macro and the dynList structure typedef in gcstruct.h. The dynList structure (and all structures beginning with the DYNLIST_STRU macro) has (at least) the following members: elemSize - this is the size in bytes of an element of the list. firstFree - the index value of the first unused slot (element) in the list. maxElems - the maximum number of elements in the current list size. listPtr - a pointer to the actual list. Note: the functions in listmgr will alter the address stored here so it should not be locally stored. The listmgr module contains three functions; addItem, rmvItem, and compactList. Their operation is detailed below. 1. addItem usage: newItemIndex=addItem(listControlStrucAdr, elemSize); error value: returns -1 on error. prototype: int addItem(void *, int); This routine adds an item to a list control structure whose first structure elements are equivalent to a dynList structure. If the member listPtr is NULL, the list will be created and initialized. Note that the size of a list created/managed always has the maxElems equal to a multiple of the constant MIN_LIST_ADD. The actual size of the list is given by elemSize times maxElems. 11 If on entry, firstFree is equal to or greater than maxElems, the list will be increased in size by MIN_LIST_ADD elements. The structure member firstFree will be set to the next unused element in the list and the index value of the new element will be returned. In order to be considered unused, an element slot must contain all zeros. This is a private kernel function. 2. rmvItem usage: nextFree=rmvItem(listControlStrucAdr, elementIndex); error value: returns -1 on error. prototype: int rmvItem(void *, int); This routine frees up a previously occupied slot, it does not reduce the total list size. The element freed will be zeroed out, and if the freed element index is less than the current firstFree, firstFree will be set to the index of the freed element. Normally, this routine returns the new firstFree. This is a private kernel function. 3. compactList usage: status=compactList(listControlStrucAdr, booleanSorted); error value: returns FUNCFAIL on error. prototype: stat compactList(void *, boolean); This routine attempts to free up unused memory in a list. The boolean type booleanSorted when true tells the routine that it may perform the compaction operation, squeezing out unused slots. This compaction process should only be done on sorted lists because an indexed list would become un-indexed. Regardless of whether or not the list has been compacted, this routine will check to see if enough empty slots exist at the top of the list to justify reallocating it. This is a private kernel function. 12 B. objlist.c module This module provides the object database routines for the kernel. Each object (with certain specific exceptions, namely the flag class instances) that is active will have an entry in the objList structure's list. These entries conform to the structure typedef'd as objectEntry. This structure has three entries: objDef - a pointer to the object's memory block. procID - the process ID number of the process that created the object. lastAcc - a use indicator used by the garbage collector. Note that the list maintained in the objList control structure is an unordered list. The entries are located by the object handle tag values which are indices to the list. The objlist.c module contains four functions; getObject, getObjDef, addObject, and rmvObject. These functions are detailed below. 1. getObject usage: objectEntryPtr=getObject(object_tag_value); error value: returns NULL on error. prototype: objectEntry * getObject(int); This routine returns the pointer to a structure objectEntry in the list maintained in objList at the index equal to the tag value object_tag_value. This is a private kernel function. 13 2. getObjDef usage: objDefinitionPtr=getObjDef(object_tag_value); error value: returns NULL on error. prototype: void * getObjDef(int); This routine returns a pointer to the actual object definition instead of the objectEntry like getObject. Otherwise it is the same as getObject. This is a private kernel function. 3. addObject usage: newObjTagValue=addObject(objectDefinitionPtr, processID); error value: returns BAD_OBJ on error. prototype: int addObject(void *, unsigned char); The value processID is used to enable the garbage collection mechanism to be multi-thread aware, it is also used by makePerm and in newClass to prevent the garbage collector from killing off non-temporary objects by setting the value to PERM_PROC_ID. The object definition pointer must be a valid pointer to an actual object definition. The return value is the tag value of the new object. This is a private kernel function. 14 4. rmvObject usage: functionStatus=rmvObject(object_tag_value); error value: returns FUNCFAIL on error. prototype: int rmvObject(int); This function removes an object Entry from the objList after freeing the object definition memory area pointed to by objDef (if it is != NULL). It returns FUNCOKAY if no error. This is a private function. C. generic.c module. This module tracks the generic functions and their methods. They are maintained in an unordered list of generic functions, with each generic function entry having an associated pointer to an ordered lists of methods. The generic function entries are contained in structures of type genEntry. Note that genEntry structures begin with the DYNLIST_STRU macro, this means that they have the same capabilities as a dynList structure in regards to the routines in listmgr.c. In addition, these structures have a member defMthd, used to store the (optional) default method address. The method lists for each generic function are comprised of genMethod type structures. These structures are sorted according to their first member 'class'. This member is the class tag of the owning instance in calls to the function dispatcher. The next member, owner, is the class tag of the class for which the method is defined rather than inherited. This member is used by the instance tracking mechanism to insure correct instance referencing. The last member of the structure is the address of the associated method. This module contains six functions; intCmp, addGeneRic, rmvGeneRic, getMthd, addMethod, and rmvMethod. These functions are detailed below. 15 1. tagCmp usage: used by qsort, bsearch, etc type standard ANSI-C library functions. error value: none. prototype: int tagCmp(void *, void *); This function is a simple tag compare routine designed for use with the standard library qsort, bsearch, etc. routines. This is a public function. 2. addGeneric usage: newGenericIndex=addGeneric(symbolStringPtr, defaultMethod); error value: returns MAX_GEN on error. prototype: Generic addGeneric(char *, method); This function adds a new Generic function to the genList. It also installs the defaultMethod for the new entry. It returns the index value (Generic tag) of the new entry which is used as the handle to the Generic function in calls to the function dispatcher. This is a private kernel function. 3. rmvGeneric usage: status=rmvGeneric(Generic_tag_of_func2rmv); error value: returns -1 on error. prototype: int rmvGeneric(Generic); This function attempts to remove the Generic function indexed by Generic_tag_of_func2rmv. Normally a non-zero positive number will be returned. This is a private kernel function. 16 4. getMthd usage: genMethodPtr=getMthd(Generic_tag, class_tag); error value: returns NULL on error. prototype: genMethod * getMthd(Generic, int); This function will lookup the correct Generic function entry indexed by Generic_tag and do a binary search for the genMethod entry in its method list for class_tag. It returns a pointer to the genMethod type structure with a class entry that matches class_tag. This is a private kernel function. 5. addMethod usage: status=addMethod(Generic_tag,method_address, class_tag, owning_class_tag); error value: returns FUNCFAIL on error. prototype: int addMethod(Generic, method, int, int); This function adds a method to the method list of an existing Generic function. It sorts the list by class after filling in the new entry. Normally this routine returns FUNCOKAY. This is a private kernel function. 6. rmvMethod usage: status=rmvMethod(Generic_tag, class_tag); error value: returns FUNCFAIL on error. prototype: int rmvMethod(Generic, int); This function will remove a previously installed method from a Generic function's method list. It normally returns FUNCOKAY. This is a private kernel function. 17 D. garbage.c module. This module is home to the memory management functions for the kernel. It also provides for automatic destruction of temporary objects as well as the means to make a temporary object permanent. There are seven functions in this module, garbage, makePerm, outOfElems, s_calloc, s_free, s_malloc, and s_realloc. They are described below. 1. garbage usage: garbage(); error value: none. prototype: static void garbage(void); This function is a round-robin style garbage collector routine with adaptive features. It searches the objList for unused objects and calls p(Kill)(... to delete them. It is multi-process aware, under normal circumstances, it will only examine objects which belong to the currently executing process. When an out of memory (or out of list space) condition occurs, it will kick into high gear and ignores process IDs. The algorithm is adaptive, a use-based timer is employed, on time-out the algorithm will be reset to slower values. At the end of an out of memory condition, it will reset to faster values. To gain a full understanding of this function, it is recommended that you examine the source code in detail. A full discussion of its operation would require a separate document. This is a private kernel function. 18 2. makePerm usage: status=makePerm(object_handle); error value: FUNCFAIL is returned on an error. prototype: int makePerm(object); This function makes a temporary object into a permanent one, thereby disallowing access to it by the garbage collector function. FUNCOKAY is normally returned. This is a public function. 3. outOfElems usage: outOfElems(); error value: none. prototype: void outOfElems(void); This function basically fakes an out of memory condition and calls the garbage collector. It is provided for the unlikely event that the objList runs out of list memory space. It is also used by the s_... functions to avoid code duplication. This is a private kernel function. 4. s_calloc usage: newBlockPtr=s_calloc(numItems, itemSize); error value: returns NULL if default error routine returns instead of aborts. prototype: void * s_calloc(unsigned, unsigned); This routine is used exactly like the standard ANSI-C library routine calloc. Unlike the standard routine, this routine interfaces directly with the garbage collector. Returns a pointer to a dynamically allocated block of memory of size numItems*itemSize. The new block will be cleared to all zeros. This is a public function. 19 5. s_free usage: s_free(oldBlock); error value: none. prototype: void s_free(void *); This routine is mostly provided for completeness, it behaves exactly as the standard library free(). This is a public function. 6. s_malloc usage: newBlockPtr=s_malloc(size); error value: returns NULL if default error routine returns instead of aborts. prototype: void * s_malloc(unsigned); This routine is used exactly like the standard ANSI-C library routine malloc. Unlike the standard routine, this routine interfaces directly with the garbage collector. It returns a pointer to a new dynamically allocated block of memory of size 'size'. This is a public function. 7. s_realloc usage: newBlockPtr=s_realloc(oldBlockPtr, oldSize); error value: returns NULL if default error routine returns instead of aborts. prototype: void * s_realloc(void *, unsigned); This routine is used exactly like the standard ANSI-C library routine realloc. Unlike the standard routine, this routine interfaces directly with the garbage collector. It returns a pointer to the relocated and resized block of memory of size 'newsize'. This is a public function. 20 E. funcdisp.c module. This module is responsible for dispatching Generic functions to the correct method, tracking the current object instance, accessing class variables, and creating/accessing instance variables. This module contains six functions; g, steer, getIVptr, initInst, makeInst, and getCVptr. They are described below. 1. g usage: methodReturnValue=g(GenericTag)( object_handle,...); error value: dispatches to the default error routine if an error occurs. prototype: method g(generic,...); This routine dispatches the Generic function indexed by GenericTag to the method in the method list with a class equal to the tag value of object_handle. Due to the way C compiles the statement given in usage, the method returned from g will immediately be called upon the return from g. Another aspect of the function dispatcher g is that it accesses the first parameter (object_handle) in the call to the method. In most cases, g will update object_handle via a call to steer. This insures that further calls to ancestor methods will be routed correctly and that calls to getIVptr will return a pointer to the correct instance memory block. Normally, g will dispatch to the method that corresponds to the class of the object_handle in the method list maintained for the Generic function indexed by GenericTag. If a method for that class does not exist, p will attempt to return the default method, if no default method is listed, the dispatcher will return the address of the default error handler. For further information about this complex function, please consult the user's guide and carefully study the provided source code. If you need further information or assistance contact me via the GCOOPE USER'S GROUP. This is a public function. 21 2. steer usage: updatedObjectHandle=steer(classObjectHandle, instanceObjectHandle); error value: returns NULL on error. prototype: object steer(object, object); This routine 'steers' the fext field of the OBJECT HANDLE STRUCTURE to contain the offset within the owning instance memory block referenced by instanceObjectHandle for the class referenced by classObjectHandle. It normally returns the updated object handle. Most applications will never require this function, however class definitions will make use of it at least in the constructor method (New). It will also be required for any destructor method or error handling method. In general, this routine must be called directly any time you attempt to call a method that is defined for both the current object class and an ancestor object class and you are trying to invoke the ancestor's method. Another place where it may be required is when you use local method pointer caching (see the user's guide). This is a public function. 3. getIVptr usage: instancePtr=getIVptr(instanceObjectHandle); error value: returns NULL on error. prototype: void * getIVptr(object); This routine is used to access the current instance memory area. It gets the pointer to the owning instance memory block, adds the fext value, and adds the size of the first entry (an integer: classTag). It returns the adjusted pointer. This is a public function. 22 4. initInst usage: status=initInst(class_tag, curOffset, newBlockPtr); error value: returns 0/NULL on error. prototype: object initInst(int, int, char *); This routine is used by makeInst (see below) to initialize a new instance memory block. It is a recursive function calling itself for each immediate parent class of class_tag. Initialization consists of writing the correct class tag values at the correct offsets within the new instance memory block. Normally this routine returns a non-zero value. When examining the source code for this routine, keep the previously detailed description of the INSTANCE DATA BLOCK close at hand. This is a private kernel function. 5. makeInst usage: instancePtr=makeInst( ptrToInstanceObjectHandle); error value: returns NULL on error. prototype: void * makeInst(object *); This routine, when called by the owning/creating class, will create a new instance memory block and initialize it. It will update the Instance object handle pointed to by ptrToInstanceObjectHandle with the new instance object handle created. If the calling class is not the owner, it will simply dispatch to getIVptr. For a better explanation of the use of this function consult the user's guide. This is a public function. 23 6. getCVptr usage: classVariablesPtr=getCVptr( classObjectHandle); error value: returns NULL on error. prototype: void * getCVptr(object); This routine will return a pointer to the class variables area for the class given by classObjectHandle. This is a public function. 24 VI. Pseudo Class Descriptions. Pseudo classes are code modules which are not part of the kernel proper, but neither are they true class definition modules. These are used for providing both necessary high level kernel functions and for kernel expansion. An additional point is that pseudo classes may have actual class names and descriptions and they may also utilize generic functions. In the basic GCOOPE system there are two linked pseudo classes; Object and Class. They and their functions/methods are described below. A. Object pseudo class module This module provides the default routines for the Generic functions Kill and Err. It also installs the Generic function New although it provides no default for it. Additional informational 'Smalltalk' style default functions are defined for the generic functions: classOf, ivSize, respondsTo, deepCopy, and shallowCopy. This module contains eight functions; Object_Install, kill, err, getClassOf, getIVsize, defRespondsTo, defDeepCopy, and defShallowCopy. They are detailed below. 1. Object_Install usage: status=Object_Install(); error value: returns FUNCFAIL on error. prototype: stat Object_Install(void); This routine installs the class 'Object' into the kernel databases, it also installs the defaults for the generic functions New, Kill, Err, classOf, ivSize, respondsTo, deepCopy, and shallowCopy. Normally it returns FUNCOKAY. Although Object is sort of a class, it cannot be inherited. Its purpose is to provide the basic default methods for the kernel. IMPORTANT NOTE: DO NOT under any circumstances add methods to the class 'Object'. This is a public function, however it should only be called during kernel initialization. 25 2. kill usage: result=g(Kill)(instanceHandle); or direct call via: result=g(Kill)(Object, instanceHandle); error value: returns NULL on error after calling the default error routine. prototype: static object kill(object,...); This routine is the default method for the Generic function Kill. It may also be called explicitly by passing 'Object' as the first method parameter. This routine will take the place of a missing class kill routine as well as actually killing off the object when all the proper conditions are met. When used as a replacement for a missing class Kill method, it performs the minimum necessary duties for a class Kill method. For each direct parent class of the class for which it is acting as a replacement routine, it will update the fext field and call the Kill generic with the updated object handle. Thus it will call any existing ancestor class Kill method. When it is called explicitly and the fext field of the second parameter is not zero, it will simply return a non NULL value. Else if called explicitly and the fext field of the second parameter is zero or if it was called as the default to replace an owning class Kill method, it will remove the instance object and free the instance memory block. For more information on the Kill generic see the user's guide. 26 3. err usage: result=p(Err)(objectHandle,...); or direct call via: result=p(Err)(Object, objectHandle,...); error value: this version aborts at the end of the routine and does not return any value. prototype: static object err(object,...); This routine is the default method for the Generic function Err. It may also be called explicitly by passing 'Object' as the first method parameter. In the debug version of the kernel, (which this is) the routine prints all available information about the error to stderr and them dumps the last 64 bytes on the stack. After printing this information, it aborts. 4. getClassOf usage: called via the generic classOf. error value: (object) 0 prototype: static object getClassOf(object instance,...); This is the default function for the classOf generic. It returns the class of the passed instance. Like all default functions it may be called explicitly by passing Object as the first parm with the actual instance as the second parm. 27 5. getIVsize usage: called via the generic ivSize. error value: (object) -1 prototype: static object getIVsize(object instance,...); This is the default function for the ivSize generic. It normally returns the size of the instance variable structure for a given class cast as type object. 6. defRespondsTo usage: called via the generic respondsTo. error value: returns (object) NULL. prototype: static object defRespondsTo(object instance,...) This is the default function for the respondsTo generic. The parameter following the actual instance object handle should be of type generic. This routine will return the method address of any existing class method for the combination of instance and generic. The return value is cast as type object. 7. defDeepCopy 28 usage: called via the deepCopy generic. error value: returns (object) 0 on error. prototype: static object defDeepCopy(object instance,...) This is the default function for the deepCopy generic. It will duplicate the instance passed creating a new instance, copying the instance block, and returns the handle of the new instance. Classes which maintain a dynamically allocated area of memory should define their own methods for deepCopy that correctly allocate and copy the dynamic memory area. 8. defShallowCopy usage: called via the shallowCopy generic. error value: returns (object) 0 on error. prototype: static object defShallowCopy(object instance,...) This is the default method for the generic function shallowCopy. Immeadiately following the instance handle should be the source handle. This function will only copy the current instance portion of the source to instance. It will not work with instances of class Class, nor will it work if the class of the current instance of source is not the owner or an ancestor of the destination instance - instance. Normally it returns the handle to the updated instance. As with deepCopy above, special care needs to be taken with classes that manage dynamic memory or other system resources. 29 B. Class pseudo-class module. This module provides the means for the installation and initialization of class definitions and forms the primary interface for those definitions. It should be installed after Object and prior to any other pseudo-classes or class definitions. Although Class actually is a true class, its inclusion of normal functions and use of low level kernel functions makes it a pseudo-class. Additionally, Class is the one class that cannot be inherited, or at least, I can't think of any reason why you would want to. This module contains six functions/methods; inhMthd, newClass, addGMthd, rmvGMthd, killClass, and Class_Install. These are defined below. 1. inhMthd usage: status=inhMthd(classObjectHandle, superClassObjectHandle); error value: returns FUNCFAIL on error. prototype: int inhMthd(object, object); This routine causes the class given by classObjectHandle to inherit all of the methods for superClassObjectHandle. It normally returns FUNCOKAY. The main use of this function is its direct use by newClass to inherit the methods of ancestor classes, however, it is provided as a Generic method for potential use by pseudo class modules that add functionality to existing class definitions. This is a postulated possibility, not a foreseen one. It does however, present an alternative method of class definition expansion. Most likely this will end up on the forgotten/unnecessary public function list. However, this is a public function. 30 2. newClass usage: myNewClass=p(New)(Class,"myNewClass", sizeofCVs,sizeofIVs, parent1,... parentx, END); error value: returns END on error. prototype: static object newClass(object,char *, int,int,...); This is the method for genmorph New when the first method parameter is Class. It creates a new class object, adding the name to the symbol list, creating the class definition memory block, initializing the block, adding the object to the object list, inheriting all of the ancestor classes in the parent list which is terminated by END, and setting up the instance memory tracking system for instances of the new class. Normally it returns the object handle of the new class object. This method is a necessary part of a class definition's installation procedure. More detail on its use appears in the user's guide. 3. addGMthd usage: status=addGMthd(objectHandleOfClass, GenericFunctionStringName, addressOfNewMethod); error value: returns FUNCFAIL on error. prototype: int addGMthd(object, char *, method); This adds a new method to the named generic function for the indicated class. If the named generic function does not exist, it will be created. Under normal conditions it returns FUNCOKAY. This is a public function. 31 4. rmvGMthd usage: status=rmvGMthd(objectHandleOfClass, GenericFunctionStringName); error value: returns FUNCFAIL on error. prototype: int rmvGMthd(object, char *); This routine removes a class method from the named Generic function. Under normal conditions it returns FUNCOKAY. This is a public function. 5. killClass usage: result=p(Kill)(Class, classToKill); error value: returns FUNCFAIL on error. prototype: static object killClass(object, object); This method for Class kills a class object given by classToKill. It will remove all class methods. This feature is of limited use until a dynamic loader add on module is defined. Normally this method returns FUNCOKAY. 6. Class_Install usage: status=Class_Install(); error value: returns FUNCFAIL on error. prototype: int Class_Install(void); This installs the class 'Class' and adds newClass and killClass to their respective Generic functions. Normally this routine returns FUNCOKAY. Although this is a public function, it should only be called during initialization. 32 VII. Summary The story of GCOOPE is far from complete, although the above described kernel functions, modules, and pseudo classes do form a self sufficient core that can be used to write programs without any additional modules except for user defined class definitions and an initialization routine. Additions to the kernel such as a dynamic loader pseudo class module, a multi-tasker pseudo class module, etc. will have their own separate documents. So this technical reference manual is complete for version 1.0 as it stands now. The above material is presented primarily for the programmer who wants to either expand the kernel, introduce speed up routines, or otherwise modify it. Any modifications should preserve the given prototypes and interface for public and Generic functions for compatibility reasons. 33 Appendix A. Appendix A. User's group Information. JOIN THE GCOOPE USER'S GROUP! For a one year membership send check/money order in the amount of $20.00, (made payable to Brian Lee Price) to the address below: Brian Lee Price GCOOPE USERS GROUP RD2 BOX239AA CLEARVILLE, PA. 15535 Please include my middle name to avoid local post office difficulties :). User's group members receive the latest updates to the GCOOPE system, new class libraries, expansion modules, etc. Limited (5hrs/yr) free technical support over the phone is available, and access is granted to a User's group only private limited access bbs, you will be added to the quarterly newsletter mailing list, and other services are available. 34 Appendix B. Appendix B. Developer's notes to GCOOPE 1.0 This package is primarily intended for experimental, exploratory, and educational purposes. It encorporates a Smalltalk like framework in a standard ANSI C compatible manner. GCOOPE is the upgraded and revamped child of the PCOOPE package. While similar in operation to its ancestor, GCOOPE provides a more programmer friendly environment and has much improved capabilities. In the first release of the ancestor package, I made some claims and misleading statements which while unintentional, have given me cause to regret. The ancestor package ended up reaching entirely the wrong audience, I was being contacted by professional programmers and commercial interests who believed it to be applicable to mainstream programming. Unfortunately, my limited experience and lack of resources makes it currently impossible for me to develop the framework into its full potential as a commercial product. GCOOPE and its ancestors are my learning environment for object oriented programming. As I write and modify the package, I continually find new capabilities and alternate approaches that were not purposely designed into the package. GCOOPE has been designed to be a modular, extensible, and easy to maintain package that offers as much flexibility as possible within its chosen approach to object oriented programming. You will find that the basic class library included in the package is full of experimental and exploratory approaches. This is because I am learning OOP and GCOOPE as I write them. Prior to my involvement with the ancestor package, I had no experience in OOP. Many of the improvements embodied in GCOOPE have come as a direct result of feedback from user's group members, in particular Norman Culver, and Mark Murphy. It is hoped that future user's group members will provide a similar service for the GCOOPE package. GCOOPE is not intended as a competitive alternative to C++, Smalltalk, or other commercial OOP approaches. It is ludicrous to assume that one person working alone with a student's budget and only a year's experience in C programming could develop and support a full fledged commercial programming environment. However I do believe that GCOOPE is suitable as an educational/experimental environment for exploring various topics in OOP. 35 Technical support and upgrades are available only through the GCOOPE user's group. In the works are speed up routines for the kernel functions, including platform dependant inline assembly optimizations, multi-tasking with threads, pipes, etc., primitive GUI class libraries, and dynamic loading support. Currently, I am limited to the IBM-PC hardware platform with MS-DOS, however, I will be more than happy to assist any user's group member with ports of the package to Unix, Atari ST, MAC, etc. In fact, if you are SERIOUS about doing the port, I'll throw in a free year's membership. I would also be interested in co-operating with other programmers in the development of a commercial version of the GCOOPE framework, however to qualify you must be able to provide information and capabilities beyond what I have locally. To summarize, GCOOPE is an educational and experimental approach to providing OOP in an ANSI C environment. It attempts to mix Smalltalk like features with the power of standard C. While I have tried to keep the system compatible with C++, it is a different approach to OOP than that provided by C++. This package is intended primarily for students of OOP and C, although anyone is welcome to use it for commercial purposes as they see fit (just send me a job application form!). Thank you for examining GCOOPE, please pass it on, thank you. Sincerely, Brian L. Price 36