GEOS Programming: 3.2 The GEOS Object System: Object Structures

Up: GEOS SDK TechDocs | Up | Prev: 3.1 GEOS Terminology | Next: 3.3 The GEOS Message System

You do not need to know what data structures are used to store objects and classes; understanding them can make programming GEOS much easier, however.

Each object is implemented in two parts: the instance data chunk and the class definition. Although both are integral parts of the object and they are interconnected, they are stored in different places.

An object's instance data is stored in an instance chunk. This instance chunk is sometimes referred to as the object itself, but this isn't quite accurate--the instance chunk contains only the object's data along with a pointer to its class structure. The structure of the instance chunk is given in Instance Chunk Structures .

An object's class structure contains all the code for the class. Since the class code may be accessed by many objects, the class definition resides in a geode's fixed memory resource. Every class (except the root, MetaClass ) has a pointer to its superclass so it can inherit that class' methods and structures.

All objects of a given class use the same code--the class' code--for their functions. They dynamically access this code so the code blocks need to be in memory only once, no matter how many objects are actively using them. Additionally, each class dynamically accesses its superclass' code, so any class may be accessed by all the objects of the subclasses as well. Class structures are shown in Class Structure and Class Trees .

Instance Chunk Structures

Each object's instance data is stored in a Local Memory chunk. Several chunks are stored in one memory block, called a local memory heap. (See the Local Memory chapter.) This local memory heap, containing objects, is known as an object block .

Each object block has a special header type that distinguishes it from normal local memory heaps. After the header in the block is the chunk handle table: a table containing offsets to each object in the block. Following the chunk handle table are the objects.

Each object is referenced by an object pointer, or optr. The optr contains two items: the global memory handle of the object block and the chunk handle of the object. Note that because the optr is made up of handles, an object must be locked before its instance data can be accessed. The GEOS kernel takes care of this automatically when executing methods.

Only the object's instance data is stored in the chunk pointed to by the optr; the method table and code used by the object are stored in the class' data structures, not the object's. To reconcile this separation of code and data, every object's first four bytes of instance data are a pointer to the object's class definition. This pointer is traversed by the kernel automatically and should not be altered or accessed by applications.

Included in an object's instance chunk are certain fields generated and filled by either Goc or the kernel. Following these fields is the object's instance data, grouped by master part. It's unlikely you'll ever have to know the actual structures used in the instance chunk because the kernel automatically calculates the proper offsets to individual instance data fields. However, understanding the underlying structures may help in understanding how the object system of GEOS works.

Instance data within an instance chunk is stored in "master parts" or "master groups." A master group is simply a number of instance data fields grouped according to their appropriate master class levels. Master classes are detailed in Master Classes .

A class designated as a master class resembles a normal class in all respects save one: it determines how instance data is grouped in a chunk. Each master class is the head of a class subtree; all the classes below it in the class tree (down to the next master class) are considered to be in that master class' group. Instance data for all classes in the master group are lumped together in the instance chunk; each master group's instance data within the chunk is accessed via a special offset stored within the chunk.

The first four bytes of an object's chunk contain a pointer to the object's class structure. The class structure (described in Class Structure and Class Trees ) resides in fixed memory. (A variant-class object has a slightly different structure; this is detailed in Variant Classes .)

An object that has no master classes in its class ancestry (unusual) has its instance data directly following its class pointer. Objects belonging to master classes or their subclasses, however, are somewhat more complex. This distinction can be mostly ignored by application and library programmers (with the exception of deciding which classes should be master classes and which should not).

Each master part of the chunk is located by an offset inserted directly after the object's class pointer in the chunk. The position of the word containing this offset is then stored in the master class structure so the class can find its instance data later. The combination of the class pointer and the various master offsets make up the object's "base structure." When a typical object is instantiated, the base structure is all that is created.

Each master part is left unallocated (for efficiency) until it is first accessed via a message sent to a class in the master group. When a class in the master group receives its first message, the entire master part of the chunk is allocated and initialized. This means that an object's chunk remains as small as possible until it absolutely must grow larger. Some classes even detect when a master part of the object will no longer be needed and actually remove (shrink to zero) the unwanted instance data from the chunk ( GenClass does this by shrinking the Vis master part to zero size when a gadget is taken off the screen).

Any object may have "variable data" instance data fields; these are fields that may be added or removed dynamically to keep from having unused space in the instance chunk. Generic UI hints are "variable data" (also called vardata )--if an object has the hint, it appears in its instance chunk, if the object does not have the hint, the chunk does not have unused space in it.

Vardata entries are stored all together at the end of the instance chunk, regardless of their master groups. An object with two master groups and three variable data fields, for example, would look like that shown in the figure below. Variable data and its use are discussed in full in Defining and Working With Variable Data Fields .

Master Classes

A master class provides a conceptual break between levels within a class tree. Each master class is the head of a class subtree, and all its subclasses are considered to be in its "master group."

The purpose of making a class a master class is to separate its instance data from that of its superclass. Each master group's instance data is lumped together in one section of the object's instance chunk and is not initialized until a class in the master group is accessed. The initialization (allocation of extra memory within the instance chunk) occurs automatically.

As shown in the figures below, an object of RookClass would have an instance chunk with two master groups, one for the PieceClass master class and one for the GamePcClass master class. The first of the two master parts represents the instance data for PieceClass only; the second master part represents the object's instance data for all of GamePcClass , ChessClass , and RookClass .

The functionality of master classes is required to implement GEOS variant classes (see Variant Classes ). A variant class allows a single class to have a version of "multiple inheritance" in that it can have different superclasses depending on the system context.

Class Structure and Class Trees

For the most part, you won't ever need or want to know the internal structure of a class as implemented in memory. The class structure is created and partially filled by the Goc preprocessor and Glue linker; the remainder is filled by the kernel when the class is loaded. It's unlikely you will need to know the actual class structures; you won't ever have to manually build a class unless your program dynamically creates it (not a common procedure for typical applications).

This section will describe how the class is implemented and how class trees are structured and managed. However, it will not discuss creating new classes during execution.

Classes are implemented with special data structures and code blocks. Each class is defined within and exported by a particular geode; when the geode is loaded the class definition and its code are loaded into the geode's fixed memory. All references to the class are then relocated by the kernel into pointers. For example, if a class is defined by a library, that library's "core block" (the special information kept about it by the kernel) contains an absolute pointer to the class' definition in a fixed memory resource owned by the library. Any applications then using that class load the library. The kernel examines the library's core block for the proper pointer and uses it each time the application references that class.

Because of this, each class is loaded into memory just once; all objects that use the class use the same class structure and code. Each object has a pointer in its instance chunk directly to the class structure; each class contains a pointer to its superclass' class structure. Using these pointers, the kernel can travel up an object's class tree to access any appropriate code.

A class is a combination of data structure and code. The data structure ( ClassStruct ) contains information about the class, its superclass, its methods, and the structure and size of its instance data. The code consists of methods (message handlers). A diagram of the data structure is given below.

Class_superClass

Every class has as its first four bytes a pointer to its superclass. This points to the superclass' ClassStruct structure in all cases except two: The root of any class tree has a null superclass pointer, indicating that the root has no superclass. Variant classes have the integer 1 (one) always, indicating that the superclass is determined in a special manner. For more information on variant classes, see Variant Classes .

Class trees are constructed when classes are defined; a new class is created as the subclass of some existing class, and its Class_superClass pointer is automatically set to point to the superclass. There is no need to point down the tree; messages are always passed to superclasses and never to subclasses.

Class_masterOffset

Class_masterOffset stores the offset indicating how far into the instance chunk the object's offset to this class' master part is. Note that use of this offset is entirely internal; individual classes do not have to figure out where their instance data is within the chunk (they may, however, have to know what master level each class is).

The master offset is used primarily because an object can have some of its master parts initialized and others uninitialized. If only one master part of the object had been initialized, the location of the instance data in the chunk may be different than if all master parts had been initialized.

Class_methodCount

Class_methodCount stores the total number of methods referenced in the class' method table. This is the total number of methods defined for this class only; other methods defined in other classes (even in the same master group) are stored in the method tables of those classes.

Class_instanceSize

Class_instanceSize holds the number of bytes to be allocated whenever an object of this class is instantiated. If the class is a master class or a subclass of a master class, this is the size of the master part. If the class has no master class above it, this is the number of bytes to allocate for the entire object (including superclass pointer).

Class_vdRelocTable

Class_vdRelocTable is a near pointer (16 bit offset) to the variable-data relocation information. The relocation information contains the type of relocation to be done for each data type. There is one entry in the variable data relocation table for each relocatable field in each particular variable-data type. Variable data (also called vardata ) is described in full in Defining and Working With Variable Data Fields .

Class_relocTable

Class_relocTable is a near pointer (16 bit offset) to the relocation information for the non-variable data instance fields of the class. The relocation information contains the type of relocation done for each relocatable instance field (other than variable-data entries). A relocatable instance field is one which must be updated when the object is loaded--pointers, offsets, etc. The entry in the relocation table is defined with the @reloc keyword, described on @reloc <iname>, [(<count>, <struct>)] <ptrType>; .

Class_flags

Class_flags contains seven flags (shown below) that determine the characteristics of the class. Declarers for these flags are used in the @classdecl declaration (see Defining a New Class or Subclass ).

CLASSF_HAS_DEFAULT
This flag indicates that the class has a special default method to handle unrecognized messages (typically, this handler simply passes the unrecognized message on to the superclass). This flag is not implemented in C. This flag is set by declaring the class as hasDefault .
CLASSF_MASTER_CLASS
This flag is set if the class is a master class. This flag is set by declaring the class as master .
CLASSF_VARIANT_CLASS
This flag is set if the class is a variant class. This flag is set by declaring the class as variant (all variants must also be declared as masters).
CLASSF_DISCARD_ON_SAVE
This flag indicates the instance data for the class can be discarded when the object is saved. This flag applies only to master classes and will be heeded only when the master group is immediately above a variant-master group. This flag is set by declaring the class discardOnSave .
CLASSF_NEVER_SAVED
This flag indicates objects of this class are never saved to a state file or loaded in from a resource. Goc will not build a relocation table for a class with this flag set. This flag is set by declaring the class neverSaved .
CLASSF_HAS_RELOC
This flag indicates that the class has a special routine for relocating and unrelocating objects of the class when they are loaded from or written to disk. This flag is set by declaring the class hasReloc .
CLASSF_C_HANDLERS
This flag indicates the class' methods are written in C rather than in assembly. This flag is set by the compiler and should not be set by applications.

Class_masterMessages

Class_masterMessages contains a number of flags set by Goc indicating whether this class has methods for messages defined within a given master level. It is used to optimize the search for a method to handle a message. These flags are internal and should not be used by programmers.

The Method Table

Every class has a method table, a table that indicates the location of the code to be executed when a certain message is received. The method table is in three parts and begins at the byte labelled Class_methodTable (this is simply a label, not a specific data field).

The first part of the method table is a list of messages the class can handle. Each entry in this list is two bytes and contains the message number of a message handled by the class.

The second part of the method table is a list of pointers to methods. Each entry in this list is a pointer (four bytes) which points to a specific routine in a code block. If the code is in a fixed block, the pointer will be a far pointer; if the code is in a moveable or discardable block, the pointer will be a far pointer containing a virtual segment . (A virtual segment, something you do not need to know about, is a handle shifted right four bits with the top four bits set. Since this represents an illegal segment address, GEOS recognizes it as a virtual segment and will take the necessary actions to lock the block into memory before access and unlock it after access. Manipulation of the bits in the virtual segment is completely internal to GEOS.)

The kernel searches the message list until it comes across the appropriate message number and notes the message's position in the table. It then looks at the corresponding position in the pointer list. If the pointer there is a virtual segment and offset, it will load the appropriate code block, lock it, and execute the code. If the pointer points to fixed memory, the code will be executed immediately. (If the message number is not found in the table, the kernel will either execute the class' default handler or pass the message on to the class' superclass.)

Variant Classes

A variant class is one which has no set superclass. The variant's superclass is determined at run-time based on context and other criteria. Note that objects may not be variant--only classes may be variant. An object always has a specific class to which it belongs, and its class pointer always points to that class' ClassStruct structure. In addition, every variant class must also be a master class.

A variant class, however, may have different superclasses at different times. This functionality provides a form of "multiple inheritance": the class may inherit the instance data and functions of different classes depending on its attributes and desired features. Note, however, that a variant class may have only one superclass at any given moment.

The most visible example of a variant class is GenClass and how a generic object is resolved into its specific UI's appropriate representation. Each generic object (for example, a GenTrigger), is a subclass of the master class GenClass . All the instance data belonging to GenTriggerClass and GenClass , therefore, is stored in the Gen master part of the instance chunk.

GenClass , however, is a variant class, meaning that it does not know its superclass when the object is instantiated. Each generic object's class will be linked directly to another class provided by the specific UI in use: the specific UI's class provides the visual representation while the generic UI class provides the object's functionality. In this way, the object can actually perform many of its generic functions without having a visual representation.

The resolution of the superclass comes when the generic object is displayed on the screen: the kernel sees that the object has no superclass and looks into its instance data and class structure. The kernel then determines what the appropriate specific UI class will be for the object's class and provides the superclass link necessary. It also then initializes the superclass' master part of the object (in this case, the master part belonging to VisClass ), updating all the master part offsets in the instance chunk's master offset fields.

You can see from the above discussion that GenClass must know at least something about its potential superclasses. In fact, all variant classes must know at least the topmost class of all its potential superclasses. The definition of GenClass is

@class GenClass, VisClass, master, variant;

The @class keyword declares the new class, GenClass . GenClass is to be a variant class and therefore must also be a master class. All the superclasses of GenClass will be related to VisClass ; this means that all specific UI classes which may act as Gen's superclass must be subclassed from VisClass . (Another way of looking at the definition is that GenClass is an eventual subclass of VisClass --you have no way of knowing beforehand how many class layers may be between the two, however.)

The variant must specify an eventual superclass so the kernel knows how many master offset fields it must allocate when an instance of the variant is created. For example, a GenTrigger has two master groups: that of GenClass , and that of VisClass . Because the GenClass master group is necessarily below the VisClass master group in the class hierarchy (after the superclass link has been resolved), the GenClass master offset in the instance chunk must be after the VisClass master offset. If the definition did not specify VisClass as an eventual superclass, no master offset field would be allocated for it, and the Class_masterOffset field of GenClass ' Class structure would not be able to hold any particular value.

As stated at the beginning of this section, there are no "variant objects." Every object belongs to a specific class, and the object's class can never change. All instances of a variant class, however, can be resolved to different superclasses due to the way the superclass of each variant is resolved. One example of this is the generic-to-specific mapping of the GenInteraction object.

All GenInteractions are of class GenInteractionClass ; this never changes. GenInteractionClass , however, is a subclass of GenClass , a variant class. This means that the class tree of the GenInteraction object is only partially completed; before the GenInteraction is resolved, it looks like the simplified diagram below.

The GenInteraction object may be resolved into one of several different specific UI classes. For example, the motif.geo library has several classes for GenInteractions; among them are the classes that implement menus, dialog boxes, and grouping interactions. These classes are all specialized subclasses of VisClass , the eventual superclass of GenClass .

The class tree of the GenInteraction is not complete. A class tree must have links all the way back to MetaClass for it to be complete; this only goes to GenClass . GenClass has a special value in its Class_superClass field, 0001h:0000h. This represents a reserved "pointer" that indicates to the kernel that the class is a master class.

The superclass of the variant can be different for every instance because the superclass pointer is actually stored in the object's instance chunk rather than in the class' ClassStruct structure. This also allows a class tree to have more than one variant class in its hierarchy; for example, one variant could be resolved to be the subclass of another variant. The tree must always be headed by MetaClass .

The superclass pointer for the variant is stored in the variant's master group instance data. Not all master groups have superclass pointers; only those for variant classes. After the GenInteraction is resolved, the pointer (the first four bytes of the Gen master part) points to the proper superclass for this object (in this case, OLMenuWinClass ). The object, with its full class tree, is shown in the figure above.

An In-Depth Example

This section gives an example of a GenTrigger object after its variant part has been resolved. This example provides in-depth diagrams of the class and instance structures for those programmers who wish to understand them. There is no need to know them, however; you will not likely ever need to access the internals of either a class structure or an instance structure.

The GenTrigger taken as an example is the "New Game" trigger of the TicTac sample application. This trigger is the only child of the Game menu GenInteraction; it is shown below. The code defining the trigger is given in TicTac's New Game Trigger .

Code Display 5-4 TicTac's New Game Trigger

/* The TicTacNewTrigger has a moniker and an output. All its other instance data
 * is set to the GenClass defaults. The content of these fields is not important
 * for this example, however. */
@object GenTriggerClass TicTacNewTrigger = {
    GI_visMoniker = "New Game";
    GTI_destination = TicTacBoard;					/* Send the action message to the
					 * TicTac game board object. */
    GTI_actionMsg = MSG_TICTAC_NEW_GAME; 	/* The action message. */
}

The GenTrigger's Instance Chunk

The GenTrigger object has two master parts, just like the GenInteraction object shown in Variant Classes : the Gen master part holds the instance data for GenClass and GenTriggerClass . The Vis master part holds the instance data for VisClass and OLButtonClass . The MetaClass instance data consists only of the object's class pointer and has no master part.

The trigger's instance chunk's basic structure consists of the class pointer (four bytes) followed by two words of offset into the chunk. The first offset gives the location of the Vis master part, and the second gives the location of the Gen master part. After the offsets are the master parts themselves, and if the trigger had any variable data, it would appear at the end of the chunk.

Each master part has the master class' instance fields first, followed by those of its subclasses. All the fields that belong to VisClass begin VI_..., all those that belong to OLButtonClass begin OLBI_..., etc.

Notice also the first four bytes of the Gen master part: they contain a pointer to the "superclass" of GenClass for the trigger. Although the trigger typically does not have different forms in any given specific UI (as the GenInteraction does), it will have a different class for each specific UI it encounters. For example, the OSF/Motif class is OLButtonClass ; another specific UI will use a different class for GenTriggers.

The GenTrigger's Full Class Tree

Since GenClass is a variant, it has a superclass pointer of 0001h:0000h. This special value (with an illegal segment address) indicates to the kernel that this object's GenClass superclass is stored in the instance chunk itself. The superclass is stored in the first four bytes of the Gen master part, as shown in the previous section.

GenTriggerClass' ClassStruct Structure

Because all classes have the same class structure, only GenTriggerClass will be examined here. The class structure and the instance chunk structure are closely linked in several ways.

As shown in the diagram, the instance chunk points directly to the class. The class points to its superclass, thereby providing inheritance of all the methods and structures of classes higher in the class tree such as GenClass .

The class structure contains some information about the instance chunk's format, specifically Class_masterOffset and Class_instanceSize . Class_masterOffset gives the offset into the instance chunk where the offset to the master part is stored. Class_instanceSize contains the size of the master part so the kernel can quickly allocate the needed space when the master part is initialized.

The method table resides at the end of the class, and it has entries for each message handled by the class. GenTriggerClass handles seven messages (stored in Class_methodCount ); any message received by this trigger and not recognized by GenTriggerClass is passed up the class tree for handling. Thus, a MSG_GEN_SET_NOT_ENABLED sent to the trigger will be passed on to GenClass and will be handled there.

The method table has two parts: The first part is a listing of message numbers, and the second part is a listing of pointers to the method code. When the object receives a message, the kernel scans the first part to see if the class handles the message. If it does, the kernel then scans the second part of the table to get the pointer to the code. It then executes the code there as if that code were a function called by the object's code.

How a Message Is Handled

Most aspects of messages and messaging are described in the following section. This section, however, describes how the kernel finds and executes the code when a message is sent to the GenTrigger. The message is MSG_GEN_SET_USABLE (handled by GenClass ).

Messages are sent directly to an object using its optr. That is, when you send a message to this particular GenTrigger, you send it directly to TicTacNewTrigger, not to some monolithic "case" statement run by your application. Since the object's optr uniquely identifies the location of the object's instance chunk in memory, the kernel can quickly access the code for the handler.

When MSG_GEN_SET_USABLE is sent to the TicTacNewTrigger, for example, the kernel looks in the object's instance chunk for its class pointer. It follows this pointer and then looks in GenTriggerClass ' ClassStruct structure. It scans the first part of the class' method table for MSG_GEN_SET_USABLE . If the message is not there (and it isn't), the kernel will then follow the class' Class_superClass pointer and look in GenClass ' ClassStruct structure. It then scans the first part of GenClass ' method table for the message. GenClass has an entry for MSG_GEN_SET_USABLE , and therefore the kernel checks the second part of the method table for the code pointer. It follows this pointer to the method's entry point and begins executing the code there.


Up: GEOS SDK TechDocs | Up | Prev: 3.1 GEOS Terminology | Next: 3.3 The GEOS Message System