Because GEOS implements its own messaging and object system, standard C programming must be supplemented with GEOS-specific programming. This chapter describes the syntax and commands available to GEOS programmers in the Goc preprocessor.
1 Basic Data Types and Structures
1.1 Records and Enumerated Types
1.2 Handles and Pointers
1.3 Fixed Point Structures
2 Goc and C
2.1 Goc File Types
2.2 Conditional Code in Goc
2.3 Macros in Goc
2.4 Using Routine Pointers in Goc
2.5 Esoteric Source-Code Management
3 The GEOS Object System
3.1 GEOS Terminology
3.2 Object Structures
3.3 The GEOS Message System
4 Using Classes and Objects
4.1 Defining a New Class or Subclass
4.2 Non-relocatable Data
4.3 Defining Methods
4.4 Declaring Objects
4.5 Sending Messages
4.6 Managing Objects
In addition to the standard data types available in C, the Goc preprocessor handles several other types specific to GEOS. These are all defined in the file geos.h . Some of these types, shown below, were carried over from the world of assembly language.
The Boolean type behaves as most Boolean types--any nonzero value represents a
true
state, and zero represents the
false
state. Throughout the documentation,
true
and
false
are taken to be these meanings. Note that the constants TRUE
and FALSE
are defined and may be used as return values from your functions and methods. Do not compare Boolean variables, however, against these constants. A Boolean may be
true
without actually equaling the TRUE value.
GEOS objects and routines make extensive use of flag records and enumerated types. A flag record is a byte, word, or dword in which each bit represents the state (on or off) of a particular attribute or function. An enumerated type is a byte or word in which each enumeration has a unique constant value.
To define a flag record, you should use one of these types and then define the flags to be bits within the record. To set flags, OR them (bitwise OR) with the record; to clear them, AND their bitwise inverses (bitwise AND) with the record. Creating and working with flag records is shown in Flag Records and ByteEnums .
There are two basic enumerated types: The standard enumerated type supported by your C compiler uses word-sized values. GEOS also allows byte-sized enumerated types with the
ByteEnum
type. Use of this type is shown in Flag Records and ByteEnums
.
Code Display 5-1 Flag Records and ByteEnums
/* Define flag records to be the optimized length for the number of flags. For * example, the sample type MyFlag has six flags and therefore should be a byte. * Flag values should be constants equivalent to having a single bit set in the * flag record. */
typedef ByteFlags MyFlag; #define MF_FIRST_FLAG 0x01 #define MF_SECOND_FLAG 0x02 #define MF_THIRD_FLAG 0x04 #define MF_FOURTH_FLAG 0x08 #define MF_FIFTH_FLAG 0x10 #define MF_SIXTH_FLAG 0x20
/* In a section of code, to set a flag, bitwise OR it with the record. To clear the * flag, bitwise AND its inverse with the record. You can set any number of flags * at a time as shown in the following examples. */
... MyFlag myFlagsRecord; /* Set up a variable of the flag record type */
/* Set the second and fourth flag. */ myFlagsRecord = MF_SECOND_FLAG | MF_FOURTH_FLAG;
/* Set the first flag and then clear the fifth and sixth flags. */ myFlagsRecord = (myFlagsRecord | MF_FIRST_FLAG) & ~(MF_FIFTH_FLAG | MF_SIXTH_FLAG);
/* The ByteEnum type can be used instead of the standard enumerated type, which * is implemented by most compilers as a word type. To define a ByteEnum, define * the type and then a unique constant value for each enumeration as in the * following example. */
typedef ByteEnum USCity; #define USC_HARTFORD 0x00 #define USC_CHARLOTTE 0x01 #define USC_WICHITA 0x02 #define USC_PIERRE 0x03 #define USC_ORLANDO 0x04
Handles and pointers are present everywhere in GEOS--they are the essential elements that make dynamic linking and efficient memory management possible.
GEOS pointers are all far pointers; that is, they are 32-bit addresses that reference specific locations in memory. They are normal C pointers and can be used as such. Two other pointer types are also used by GEOS: Object pointers (optrs) and segment pointers. Object pointers are described below; segment pointers are 16-bit addresses described in the Memory Management chapter.
Handles are 16-bit, unsigned values used for several purposes. They provide abstraction when the exact address of a data structure or other item is not known or is an inconsistent state. The kernel maintains a handle table to keep track of many of the handles in the system. Each entry in the handle table is 16 bytes that contains information about the item referenced by the handle; these 16 bytes are opaque to applications and libraries and can not be accessed or altered except by the kernel. Other handle types are managed in other tables by the kernel.
Handles are used for the following primary purposes. For a full description of how handles are used, see the Handles chapter.
The
NullHandle
value (zero) is used to indicate a null handle.
There are over a dozen different types of handles that can be used by any sort of geode. These are listed in the GEOS Programming chapter. All are 16-bit unsigned integers.
Objects and small data structures are stored in small memory pieces called chunks. Chunks are stored in memory blocks known as local memory heaps, and each local memory heap can contain several chunks. Each chunk is referenced by a combination of two handles: The MemHandle handle locates the local memory heap, and the ChunkHandle locates the chunk within the block. A null chunk handle value is specified by
NullChunk
.
Objects are referenced in the same way as chunks, but the handle and chunk handle are combined into a single structure called an Object Pointer, or optr. Each optr uniquely identifies a particular object in the system. Note that optrs are often used to reference non-object chunks and data structures. A null value is specified by
NullOptr
.
GEOS provides several macros, all defined in geos.h , for creating and parsing optrs.
ConstructOptr()
This macro constructs an optr from a MemHandle and a ChunkHandle.
OptrToHandle()
This macro extracts the MemHandle portion of the given optr.
OptrToChunk()
This macro extracts the chunk handle portion of a given optr.Pointers can be used normally as in C. All Goc-generated pointers are far pointers; that is, they are 32-bits long, composed of a 16-bit segment and a 16-bit offset.
GEOS provides macros for extracting the segment and handle portions of pointers.
PtrToSegment()
This macro returns the segment portion of the given pointer (returned as type "word").
PtrToOffset()
This macro returns the offset portion of the given pointer (returned as type "word").GEOS automatically loads code resources when needed. However, when you call routines through pointers, you must take special measures to see to it that the routine is properly loaded into memory. This is discussed below in Using Routine Pointers in Goc .
When you want to represent non-integral numbers (i.e., real numbers), you can use either the standard C floating-point format or the following special structures of GEOS for fixed point math. Note that fixed-point calculations are faster than the corresponding floating-point math, so if you want to optimize your code, you should use the GEOS fixed-point structures with the GEOS math routines.
Code Display 5-2 GEOS Data Structures
/* Fixed-Point Structures * The following structures are used to represent fixed-point numbers: * numbers with a fractional portion and an integral portion. Notice that * there are several formats of fixed-point numbers; each uses a different * number of bits for the parts of the number. Choose whichever is most * appropriate (for optimization). */
/* BBFixed * One byte integer, one byte fraction */ typedef struct { byte BBF_frac; /* fractional portion */ byte BBF_int; /* integral portion */ } BBFixed;
/* BBFixedAsWord * Sometimes it is convenient to refer to a BBFixed value as type word. * The BBFixedAsWord type is used for this purpose. */ typedef word BBFixedAsWord;
/* WBFixed * One word integer, one byte fraction */ typedef struct { byte WBF_frac; /* fractional portion */ word WBF_int; /* integral portion */ } WBFixed;
/* WWFixed * One word integer, one word fraction */ typedef struct { word WWF_frac; /* fractional portion */ word WWF_int; /* integral portion */ } WWFixed;
/* WWFixedAsDWord * Sometimes it is convenient to refer to a WWFixed value as type dword. * The WWFixedAsDWord type is used for this purpose. */ typedef dword WWFixedAsDWord;
/* DWFixed * two words (one dword) integer, one word fraction */ typedef struct { word DWF_frac; /* fractional portion */ sdword DWF_int; /* integral portion */ } DWFixed;
/* Three-byte structure * The WordAndAHalf structure is used when you need a 24-bit value and you want to * optimize and avoid using a 32-bit value. */
typedef struct { word WAAH_low; /* the low 16 bits */ byte WAAH_high; /* the high 8 bits */ } WordAndAHalf;
Three special macros are also available to work with the
WWFixed
type. These are listed below:
MakeWWFixed
This macro creates a
WWFixed
structure from a given floating-point number or dword number.
WWFixedToFrac
This macro produces the fractional portion of a
WWFixed
structure.
WWFixedToInt
This macro produces the integral portion of a
WWFixed
structure.
Two other macros are provided for use with
WWFixedAsDword
structures:
IntegerOf()
This macro returns the integral portion of a
WWFixedAsDword
structure.
FractionOf()
This macro returns the fractional portion of a
WWFixedAsDword
structure.Goc is a superset of the standard C programming language. Goc actually acts as a sort of preprocessor before the code is run through a standard C compiler. There are several differences you must be aware of, though. These differences are covered in the following sections as well as throughout the documentation.
Esoteric Source-Code Management
@include, @optimize
When programming in Goc, you will use several different types of code files. Files ending in .goh are the Goc equivalent of C .h files--they contain routine headers, constants, and other included data and structures necessary for the program. Files ending in .goc are the Goc equivalent of C .c files--they contain code and data for the program. You should keep any Goc-specific code or header information strictly in the Goc files, and standard C code should be kept in C files. C code can also be put in .goc and .goh files, but for consistency, you should try to keep it separate.
Not all of your program's header files need be .goh files--if the header file contains only C constructions (structures, routine definitions, and so on), then you may leave it as a standard C .h file, included by means of the #include directive.
The rule of thumb is that if a header file contains any Goc code or includes a .goh file, then it must be a .goh file. Note also that .goh files are allowed to contain simple standard C code; if you are not sure, then, you can make all your header files .goh .
Standard C programs use the
#include
directive to include header (
.h
) files. When using Goc, you can use this directive in standard
.c
and
.h
files; when including
.goh
files in
.goc
files, though, you have to use the
@include
directive, which is Goc-specific. It has the same syntax as
#include
.
An example of using
@include
comes from the Hello World application, which includes the
stdapp.goh
file. (All GEOS applications will need to include this file to compile properly; it must be
@include
d before any standard C headers are
#include
d.) The line from Hello World is
@include <stdapp.goh>
The syntax of this directive, as stated above, is the same as for the C directive
#include
. One exception is that the
@include
directive will include a file just once, even if it is included by an included file--there is no need to conditionally include a file (checking first to make sure it hasn't already been included).
If you will be including a Goc file in many different applications, or if it is very long and elaborate, it is a good idea to put the keyword
@optimize
at the top of the file. This instructs the Goc preprocessor to generate a special stripped-down version of the file with a
.poh
suffix. The compiler will then automatically keep the
.poh
file up to date, and use it in compilations instead of the
.goh
file. The
.poh
file contains all the data of the
.goh
file, but is somewhat faster to compile into an application; thus, by using the
@optimize
keyword, you incur a longer compilation whenever you make a change to the
.goh
file, but a shorter compilation time when the
.goh
file is unchanged since the last compilation. You may choose to leave the
@optimize
directive out while the header is being developed, then put it in when the header is fairly stable.
@if, @ifdef, @ifndef, @endif
Many C programs use the directives
#if
,
#ifdef
,
#ifndef
, and
#endif
to define conditional code--code that should be compiled into the program only if certain conditions are met. When working with standard C code in your GEOS applications, you should still use these directives; when working with Goc code, however (in
.goh
and
.goc
files), you should use the Goc directives
@if
,
@ifdef
,
@ifndef
, and
@endif
.
Goc conditionals are more limited than C conditionals. Conditional expressions may be based on numbers, names of macros, and the Boolean operators OR (
||
) and AND (
&&
). Some examples of Goc conditional expressions are shown below:
@ifdef (MyMacro) /* code compiled if MyMacro is defined */ @endif
@if 0 /* code that will not be compiled at all */ @endif
@if defined(MyMacro) || MY_CONSTANT /* code compiled if either MyMacro is * defined or MY_CONSTANT is not zero */ @endif
@ifndef 0 /* code always compiled */ @endif
@define
The C programming language allows definition and use of macros, and most programmers use macros extensively. You can use the
#define
directive in standard C code in your GEOS programs to define macros that use only standard C code.
Similarly, you can use the
@define
Goc directive to create macros in Goc. (Macros must be defined with
@define
; otherwise, the Goc processor will skip the
#define
directive and process the macro as if it were standard code to be processed normally.)
Macros in Goc have a somewhat different syntax than standard C macros though they are very similar. Some examples of simple Goc macros follow below:
@define mlply(val1,val2) val1 * val2 @define defChunk(a) @chunk char a[] = "text"
When using Goc macros in your code, you must preface them with the "
@
" Goc marker, indicating to the processor that it is a macro. If you do not preface the macro with "
@
", then Goc will pass over it and will not process it, leaving it up to the C compiler--which will likely give an error. For example, using the second macro defined above (defChunk), you could create a number of chunks easily:
@defChunk(firstText) @defChunk(secondText) @defChunk(thirdText)
The above would equate to the following:
@chunk char[] firstText = "text"; @chunk char[] secondText = "text"; @chunk char[] thirdText = "text";
Using "defChunk" without the "
@
" marker would most likely result in a compilation error in the C compiler.
ProcCallFixedOrMovable_cdecl(), ProcCallFixedOrMovable_pascal()
Most GEOS code is kept in movable resources. If you call a routine explicitly from source code the Goc preprocessor generates appropriate directives to see to it that the resource is loaded into memory when it is called. However, if you call a routine with a routine-pointer, GEOS cannot take these precautions. Accordingly, when you are calling a routine with a pointer, you must either see to it that the resource is loaded, or use one of the two
ProcCallFixedOrMovable
routines to instruct the kernel to lock the appropriate resource.
If you know the routine is in a resource which is locked or fixed in memory, you can use the routine pointer exactly the way you would in standard C. This is usually because the calling routine is in the same resource as the routine or routines which may be called.
If you are not sure that the resource is loaded, you should call the routine with either
ProcCallFixedOrMovable_cdecl()
or
ProcCallFixedOrMovable_pascal()
. Each of these routines is passed the following arguments:
Both routines return whatever the called routine returns.
If the routine to be called was defined with standard C calling conventions (the default), you should use
ProcCallFixedOrMovable_cdecl()
. If the routine was declared with the keyword
_pascal
, it uses Pascal's calling conventions; you must then use the routine
ProcCallFixedOrMovable_pascal()
. Most kernel and system-library routines are declared with Pascal's calling conventions.
Code Display 5-3 Using ProcCallFixedOrMovable_cdecl()
extern int SomeRoutineCalledViaAPointer(int anArg, int anotherArg, const char *someText);
int (*funcPtr) (int, int, const char *); /* A function pointer */
funcPtr = SomeRoutineCalledViaAPointer;
/* We want to do * SomeRoutineCalledViaAPointer(1, 2, "Franklin T. Poomm"); * but we want to call it through the pointer, even though it's in another * resource: */
ProcCallFixedOrMovable_cdecl(funcPtr, /* The pointer to the routine */ 1, 2, "Franklin T. Poomm");
@optimize, @protominor
There is a small number of Goc keywords which are used only to ease the burden of maintaining header files which will be used and shared often.
The
@optimize
keyword is used to speed compilation of Goc header files. It may be placed at the top of a
.goh
file. The directive instructs Goc to generate a specially processed
.poh
file which contains all the information of the
.goh
file, but is somewhat faster to compile. This
.poh
file is automatically regenerated if the corresponding
.goh
file has been changed since the last compilation. Its syntax is simple:
@optimize
The
@protominor
keyword is used so that a single Goc header file may serve multiple versions of a Goc library. When creating a new version of an existing library, use the
@protominor
keyword to declare new messages and variable data fields for a class. Suppose your original class declaration looked like so:
@class MyClass, SuperClass; @message void MSG_M_DO_THIS(void); @vardata void TEMP_M_DONE_FLAG; @endc
Having released this version of your class, you wished to release another version in which this class handled another message. You wanted to specify that this new message would only work with this new version of the library. This would be set up like so:
@class MyClass, SuperClass; @message void MSG_M_DO_THIS(void); @vardata void TEMP_M_DONE_FLAG; @protominor MyVersion20 @message void MSG_M_DO_THAT(void); @endc
This keyword has the syntax:
@protominor <name>
incminor
entry in the geode's .gp file.
To do the equivalent version control with routines, use the
incminor
.gp file directive.
GEOS is almost entirely object-oriented. Its object system supports true object-oriented principles such as encapsulation, inheritance, and message dispatching through the kernel.
The following section describes the class and object structures of GEOS, how to declare and define classes and objects, and how the messaging system and the kernel's message dispatcher work.
Though you should be familiar with general object-oriented programming terms, there are quite a few for which the meaning is slightly different in GEOS, and there are others which are entirely new to GEOS. This section is divided into four categories: General Terms, Class Terms, Object Terms, and Messaging Terms.
fptr An fptr is a "far pointer"--a 32-bit pointer to a specific location of memory. It is a standard C pointer.
object block
An object block is a specific type of Local Memory block that contains object chunks.
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 .
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 .
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.
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.
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
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
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
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
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
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
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
).
hasDefault
.
master
.
variant
(all variants must also be declared as masters).
discardOnSave
.
neverSaved
.
hasReloc
.
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.
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.)
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.
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 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.
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.
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.
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.  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.
Because objects are independent entities, they must have some means of communicating with other objects in the system. As shown in the example of the calculator and requestor objects in the Overview chapter, communication is implemented through the use of messages and methods.
When an object needs to notify another object of some event, retrieve data from another object, or send data to another object, it sends a message to that object. Sending a message is similar to calling a function in C in that the message can take parameters (including pointers) and give return values. However, messages are also quite different from function calls in the multithreaded environment of GEOS.
Each object block in the system is associated with a single thread of execution. Each thread that runs objects (some run only procedural code) has an event queue , a queue in which messages are stored until they can be handled. Every message sent to an object from another thread gets put in the object's thread's event queue. (Messages sent between objects within the same thread are not handled via the queue unless forced that way.) Thus, a single thread's event queue can have messages for many different objects. For most single-thread applications, the programmer will not have to worry about synchronization issues.
The sender of a message has to be aware of synchronization issues raised by having multiple threads in the system. Essentially, you can send a message two ways: The first, "calling" the message, allows the use of return values and acts almost exactly like a function call in C. This places the message in the recipient's event queue and then halts the sender until the return values are received. The sender "goes to sleep" until the message has been processed and is then awoken by the kernel, thus ensuring the message is handled before the sender executes another line of code. The call option should also be used when passing pointers; otherwise, the item pointed to may move before the message can be handled, invalidating the pointer.
The second, "sending" the message, is used primarily when synchronization is not an issue. For example, if the message merely notifies the recipient of some condition or event, or if it sends data with no expectation of a return value, the send option can be used. Essentially, the sender will send off the message and then forget about it, continuing on with its own business. (The exception to this is an object sending a message to another object in the same thread; then the message is handled immediately, as if it had been called.)
When an object sends a message, the message actually first gets passed to the kernel (this is all automatic). The kernel will put the message into the proper thread's event queue and, if necessary, put the sender's thread temporarily to sleep. When the recipient's thread is ready to handle the message, the kernel pulls it from the event queue.
The kernel then locates and loads the recipient object into memory (if necessary). The recipient's object block will be loaded and locked, and the object will be locked while processing the event. (Note, however, that it is possible for the object to move if the recipient makes a call to
LMemAlloc()
or does something else that can cause shuffling of the object chunks.) The kernel will follow the object's class pointer to its class and will scan the method table. If the class can handle the message, the proper method code will be executed. If the class can not handle the message, the kernel will follow the superclass pointer and check the method table there. The message will continue up the class tree like this until either it is handled or it reaches the root and returns unprocessed.
After the method code has been executed, the kernel collects any return values and wakes up the caller thread again if necessary. To the caller, it's as if the message were handled instantaneously (with the call option). Senders are never blocked; only messages called (with the call option) may block the caller's thread. If a message is sent to an object in the same thread, however, it will be executed as a call and will be handled immediately, unless the sender explicitly states that it should go through the message queue.
Be careful, though, if you are writing code in multiple threads (for example, if you subclass UI objects and write new method code for them). You have to make sure that two threads never call each other; this can lead to deadlock if the calls happen to overlap. The easiest way to deal with this is to have one thread always send a message requesting a return message with any needed return values. The other thread will then send off a return message with the data. For example, a UI object may require information from an application's object. The UI object sends
MSG_REQUEST_INFORMATION
(or something similar). The application's object then receives that message and responds with a
MSG_RETURNING_REQUESTED_INFORMATION
(or something similar). With this scheme, the application's object is free to use call whenever it wants, but the UI object must always use send.
A message is simply a 16-bit number determined at compile time. Specifically, it is an enumerated type--this ensures that no two messages in the same class can have the same number.
An event is an opaque structure containing the message number and information about the recipient, the sender, parameters, and return values. When an object sends a message, the kernel automatically builds out the event structure (generally stored in the handle table for speed and efficiency). You will never have to know the structure of an event.
The previous sections dealt with the internals of the GEOS object system. This section describes how you can create classes and objects and manage them during execution using Goc keywords and kernel routines. Almost all Goc keywords begin with "
@
" (one notable exception is
gcnList
).
All the most useful keywords available in Goc are shown in Goc Keywords . This display is for initial reference; all the keywords are detailed in depth in the following sections of this chapter and in the Routines Book.
/* Including .goh files */ @include <fname>;
/* Defining New Classes and Subclasses */ @class <cname>, <super> [, master [, variant]]; @endc
/* Declaring a class */ @classdecl <cname> [, <cflags>];
/* Defining messages for a class */ @message <retType> <mname>([@stack] <param>*); @reserveMessages <num>; @exportMessages <expName>, <num>; @importMessage <expName>, <messageDef>; @alias(<protoMsg>) <messageDef>; @prototype <messageDef>;
/* Defining instance data fields for a class */ @instance <insType> <iname> [ = <default>]; gcnList(<manufID>, <ltype>) = <oname> [, <oname>]*; @instance @composite <iname> [ = <linkName>]; @instance @link <iname> [ = <default>]; @instance @visMoniker <iname> [ = <default>]; @instance @kbdAccelerator <iname> [ = <default>]; @reloc <iname>, [ (<count>, <struct>), ] <ptrType>; @noreloc <iname>; @default <iname> = <default>;
/* Defining vardata fields for a class */ @vardata <type> <vname>; @vardataAlias (<origName>) <newType> <newName>; @reloc <vname>, <fn>, [ (<count>, <struct>), ] <ptrType>;
/* Defining methods (message handlers) */ @method [ <hname>, ] <cname>[, <mname>+]; @method [ <hname>, ] <cname>, _reloc;
/* Defining library code */ @optimize @deflib <libname> @endlib
/* Defining resources */ @start <segname> [ , <flags> ]; @header <type> [ = <init> ]; @end <segname> @chunk <type> <name> [ = <init> ]; @localize <string>; @localize { <string> <length>}; @localize { <string> <minLength>-<maxLength> }; @localize not; @chunkArray <stype> <aname> [ = {<init>} ]; @elementArray <stype> <aname> [ = {<init>} ]; @extern <type> <name>; @gstring <name> = {[<command> [, <command>]+]} ;
/* Declaring an object */ @object <class> <name> <flags>* = { [<fieldName> = <init>];* [<varName> [ = <init> ]]*; }
/* Accessing an object's instance data from one of its methods */ @self-><iname>
/* Sending and calling messages */ @send [<flags>,+] <obj>::[{<cast>}] <msg>(<params>*); <ret> = @call [<flags>,+] [{<cast>}] <obj>::[{<cast2>}]<msg>(<params>*); @callsuper(); @callsuper <obj>::<class>::<msg>(<params>*) [<flags>,]; <event> = @record <obj>::<msg>(<params>*); @dispatch [noFree] <nObj>::<nMsg>::<event>; <ret> = @dispatchcall [noFree] [{<cast>}] <nobj>::<nMsg>::<event>;
/* Using conditional code */ @if <cond> @if defined(<item>) @ifdef <cond> @ifndef <cond>
/* Creating Goc macros */ @define <mname> <macro>
/* Esoteric Source-Code Management */ @optimize @protominor <name>
Defining a New Class or Subclass
@class, @classdecl, @endc, @default, @uses
You can create new classes in GEOS by using the Goc keywords
@class
and
@endc
. These frame the class definition as shown in Defining Classes
; the
@endc
keyword takes no parameters, but
@class
takes the following parameters:
@class <cname>, <super> [, master [, variant]];
Every class must have a class structure (
ClassStruct
) in memory. This is created and filled automatically by Goc and the kernel; however, you must use the
@classdecl
keyword to make sure the structure gets built. Only one
@classdecl
statement may be used for each class, however--Goc will give an error if the class is declared twice. This is also shown in Defining Classes
, and its parameters are as follows:
@classdecl <cname> [, <cflags>];
The optional flags that can be used with a class declaration determine how objects of the class get shut down (see Class_flags
). The flags you can use with
@classdecl
are
neverSaved
discardOnSave
ignoreDirty
instead in the object's declaration (see
@object
, in Declaring an Object
).In addition, any variant class can have a default superclass. The variant is resolved at compile-time to have the default superclass as its superclass. To set a default superclass for a variant class, add a line with the following structure in the class definition:
@default <varRoot> = <super>;
Sometimes a variant class will know that it will be the subclass of a specific class, though it doesn't know (at compile time) just how that ancestry will be traced. You can use the
@uses
directive to let the compiler know this; that way, the variant class can define handlers for the "used" class. For example, if you know that variant class
MyVariantClass
will always be resolved as a descendant of
MyAncestorClass
, you can put the directive
@uses MyAncestorClass;
in the definition of
MyVariantClass
. The general format is
@uses <class>;
Code Display 5-6 Defining Classes
/* The @class keyword defines a new class or subclass. @endc ends the class * definition, and @classdecl must be put somewhere in the code to make sure Glue * will link the class structure into the compiled geode.*/
@class MyNewClass, VisClass; /* Message declarations would go here. See @message. * Instance data field declarations would go here. See @instance * and @vardata. */ @endc @classdecl MyNewClass, neverSaved;
@class MyTriggerClass, GenTriggerClass; /* New messages for this subclass are defined here. */ /* New instance data fields for this subclass are defined here. */ @endc @classdecl MyTriggerClass;
/* When defining a variant class (which must also be a master class), you can * set a superclass for the variant at compile-time using @default. */
@class MyNewVariantClass, MetaClass, master, variant; @default MyNewVariant = VisClass; @endc MyNewVariantClass @classdecl MyNewVariantClass;
@message, @stack, @reserveMessages, @exportMessages, @importMessage, @alias, @prototype
As discussed in The GEOS Message System , messages are simply 16-bit numbers allocated as an enumerated type. When a new class is defined, a constant is automatically created representing the first message number for the class. This constant is then used as the first number in the enumeration of messages.
The constant is built off the class' superclass.
MetaClass
has the first 16384 messages reserved for its use. Each master level gets 8192, and the first master class of a level gets 2048 of these. All other classes are allocated 512 message spots. Thus, a master class subclassed directly off
MetaClass
would have 2048 messages beginning with number #16384 (since the numbering is zero-based). A subclass of this would have 512 messages beginning with number #18432.
This numbering scheme ensures that no two classes at different levels in the class hierarchy will have the same message number. Specifically, a class will never have the same message number as one of its sub- or superclasses.
New messages must be defined in the class definition between the
@class
and
@endc
keywords (see above). They are defined much like normal function calls and follow the normal C calling conventions (see Defining Messages
for examples). If your class uses messages from its superclass, you do
not
have to declare these messages in your class definition--they are inherited automatically. This is true even if you are subclassing the method to alter its functionality.
To define a new message, use the
@message
keyword. This keyword takes the following parameters:
@message <retType> <mname>(<param>*);
MSG_
followed by a shortened version of the name of the class and then some useful name (e.g.,
MSG_META_INITIALIZE
).NOTE: When defining a function with no parameters, it is best to declare it with "void" between the parentheses. This will make sure Goc gives an error if the function is called with arguments.
The
@stack
keyword indicates that parameters are passed on the stack; it is important to note that because of calling conventions, parameters passed on the stack must be listed in the message definition
in reverse order
from the way the handler pops them from the stack. This keyword is used only when the message may be handled by an assembly language method; its format is shown below:
@message <retType> <mname>(@stack <param>*);
All the parameters shown in the formats are the same as in the normal
@message
format.
Code Display 5-7 Defining Messages
/* Each message is defined for a class within the class definition. */ @class MyTriggerClass, GenTriggerClass; /* All the new messages MyTriggerClass can handle are defined here. */ @message void MSG_MYTRIG_SET_COLOR(colors colorIndex); @message optr MSG_MYTRIG_RETURN_OPTR( void ); @message void MSG_MYTRIG_COLLECT_PARAMS(byte bParam, word wParam, char * string); /* Instance data fields would be defined here. */
@endc @classdecl MyTriggerClass;
As discussed above, message numbers are assigned based on the class' location in the class tree. No message number will ever conflict with messages defined in the class' superclasses. However, the assignment scheme opens up the possibility that classes on the same level in the class tree could have conflicting message numbers, as shown below.
Normally, this is not a problem. If subclasses are to receive the same message and handle it differently, the message can typically be defined in the superclass and simply be intercepted by the subclasses. Sometimes, however, different subclasses will need to have different definitions for the same messages. For example, a class supplied by a library may be used by several applications; if the applications each create a subclass, these subclasses can import particular messages that will be the same for all the subclasses in all the applications.
Goc therefore allows a class to export a range of message numbers which subclasses can import and create specific definitions for. This allows you greater control over what aspects of the class you can define.
To export a range of messages, use the
@exportMessages
keyword. This will set aside several message numbers which can then be imported by subclasses using the
@importMessage
keyword.
Another potential problem is upgrading your program from release to release. If you create classes that may grow in the future, you may want to reserve a number of message spots to ensure that those spots can be filled in later. Nothing is done with the spots; they are simply place holders for future upgrades. You can use the
@reserveMessages
keyword to reserve a range of any size. The parameters of these three keywords are shown below:
@reserveMessages <num>; @exportMessages <expName>, <num>; @importMessage <expName>, <messageDef>;
@message
keyword (though
@message
is left out).Note that you do not need to reserve messages for upgrades; any class can always have messages tacked on to the end of its class definition. If you want to group the messages logically, however, you should reserve ranges where you expect additions to be made.
The
@alias
keyword allows a single message to have more than one pass/return format.
The
@prototype
keyword allows quick, clean, and convenient repetition of a single format for many different messages; it also allows a class to create a prototype so users of a message can have their own messages with the same format.
@alias
is used when a single method takes conditional parameters. For example, a method may take a word value in a certain case and a dword value in another (dependent upon a passed flag). Each condition must be accounted for in its own message format. Rather than create a message and a method for each case, you can create a single assembly-language method for all the different pass/return formats; then, you can use
@alias
to make several messages refer to the same method, each using a different format.
@alias(<protoMsg>) <msgDef>;
@message
keyword (with
@message
left off).
In addition, if you have a single pass/return format for many messages, you can use the
@prototype
keyword as coding shorthand. For example, if an object has ten messages that all take two parameters and return a single value, you can set up the format with the
@prototype
keyword and then use a simpler format for definition of your messages. An example is shown in Aliasing Messages
, and the parameters of this keyword are shown below.
@prototype <msgDef>;
MY_PROTOTYPE
" that you can insert later into the definitions of your real messages. All other parts of the message definition are the same as would be declared with
@message
(with
@message
left off).Code Display 5-8 Aliasing Messages
/* MyClass in this example uses both prototype and aliased messages. */ @class MyClass, MetaClass; /* The following is a normal message declaration. The register that this * parameter uses is specified because the handler (method) is written in * assembly language. */ @message void MSG_MESSAGE_WITH_WORD(byte flag = cl, word value = dx);
/* The following message invokes the same method as the alias above. * It has the same message number but passes a different sized parameter * in different registers. */ @alias(MSG_MESSAGE_WITH_WORD) void MSG_MESSAGE_WITH_DWORD(byte flag = cl, dword value = dx:bp);
/* The following message is not used. Its pass and return values can * be used elsewhere, however, to ensure that all handlers of this message * type are given the same format. */ @prototype int MSG_MYCLASS_PROTO(int a, int b);
/* The following have the same return values and parameters * as the prototype above. */ @message(MSG_MYCLASS_PROTO) MSG_MY_CLASS_ADD; @message(MSG_MYCLASS_PROTO) MSG_MY_CLASS_SUBTRACT; @message(MSG_MYCLASS_PROTO) MSG_MY_CLASS_MULTIPLY;
@endc @classdecl MyClass;
@instance, @composite, @link, @visMoniker, @kbdAccelerator
Instance data fields are all defined with the
@instance
keyword. Other keywords may be included in the
@instance
declaration for special types of data. All instance data definitions must appear between the class'
@class
and
@endc
keywords (see above under class definition).
The
@instance
keyword is used to define normal instance data. If you have data that must be added or removed dynamically (such as hints), use the
@vardata
keyword, described in Defining and Working With Variable Data Fields
. Also, if you have data that requires relocation (such as pointers to fixed data) when the object is loaded, use the
@reloc
keyword.
The format of the
@instance
keyword is as follows:
@instance <insType> <iname> = <default>;
Goc has several special types of instance data fields that you can declare along with
@instance
to make object definition easier. The format for using one of the special types is shown below (with examples in Declaring Instance Data Fields
). Each of the types is also described below.
@instance <specType> <iname>;
The special types are given here:
@composite
@composite
field will actually contain an optr to the first child object in an object tree. Since most objects in object trees are subclassed from
VisClass
or
GenClass
, you will most likely never use the
@composite
keyword. Both
VisCompClass
and
GenClass
have
@composite
fields predefined. The
@composite
type has a special format, shown below:@instance @composite <iname> = <linkName>;
iname
is the name of the instance field and
linkName
is the name of the field designated as
@link
(below). Note that there must be a
@link
field in every class that has a
@composite
field. See Managing Object Trees
for more information on object trees and the composite and link fields.
@link
@composite
field points to the first child, the
@link
field points to the next sibling. If there is no next sibling, this field will point back to the parent object. Since most objects in object trees are subclassed from
VisClass
or
GenClass
, you will most likely never use the
@link
keyword. Both
VisClass
and
GenClass
have
@link
fields predefined.
@visMoniker
GenClass
--see the GenClass chapterfor information on the
GI_visMoniker
field. The moniker or moniker list must be in the same resource as the generic object using that moniker since only the chunk's handle is stored. A moniker list can store full object pointers to its monikers, so monikers referenced by a list need not be in the same resource as that list; thus if an object's moniker is specified via a list, then while the list must be in the same resource as the object, the monikers themselves need not be.
@kbdAccelerator
GenClass
only.Code Display 5-9 Declaring Instance Data Fields
/* GenClass is a good example of many of the different types of fields. */
@class GenClass, VisClass, master, variant;
/* The GenClass messages are defined here. */
@instance @link GI_link; @instance @composite GI_comp = GI_link; @instance @visMoniker GI_visMoniker; @instance @kbdAccelerator GI_kbdAccelerator; @instance byte GI_attrs = 0; @instance byte GI_states = (GS_USABLE|GS_ENABLED);
/* Hints and other variable data fields are defined with @vardata. */
@endc
@default
Recall that when defining an instance data field you can set up a default value for that field. When creating a subclass, you may wish to specify that the subclass should have a different default value for a given field than the superclass does. Use the
@default
keyword to do this:
@default <iname> = <default>;
@default
as part of this value; this @default will be treated as the value of the superclass. (If this seems confusing, try looking at the example.)
For example, a subclass of GenInteraction could set GIV_DIALOG as its default value for the GenInteraction instance field
GII_visibility
:
@default GII_visibility = GIV_DIALOG;
A generic class might want to have the same value for its
GI_states
field as its superclass, except with the GS_USABLE flag turned off:
@default GI_states = @default & ~GS_USABLE;
@vardata, @vardataAlias, ObjVarAddData(), ObjVarDeleteData(), ObjVarDeleteDataAt(), ObjVarScanData(), ObjVarFindData(), ObjVarDerefData(), ObjVarDeleteDataRange(), ObjVarCopyDataRange(), MSG_META_ADD_VAR_DATA, MSG_META_DELETE_VAR_DATA, MSG_META_INITIALIZE_VAR_DATA, MSG_META_GET_VAR_DATA
Most classes will have well-defined instance data fields; each object in the class will have the same data structures, and all the instance chunks will look relatively similar and will be the same size.
Many classes, however, will also use "variable data," or instance fields that may be added or removed dynamically. This allows objects within the same class to have more or less instance data than other objects in the class. One example of variable data is the use of hints in generic UI objects. Because each object in a given class may or may not have hints, the objects can actually have different instance sizes. Variable data instance fields are defined with the use of the
@vardata
keyword.
Using variable data, however, is somewhat more complex than using standard instance data. You must use special kernel routines or messages to get a pointer to the data; then you can use the pointer to access the field.
Variable data is stored together at the end of the instance chunk in "data entries." Each entry consists of a primary word and optional extra data. The primary word represents a data type defined by the keyword
@vardata
. This type is created automatically by Goc when the
@vardata
keyword is used.
Each data entry is associated with a master class level and is considered part of the instance data for that master level (despite being stored at the end of the instance chunk). Thus, when a master part of an object is destroyed, the variable data entries associated with that master class will also be destroyed. For example, when a UI object is set not usable (taken off the screen), its Vis master part is removed from the instance chunk; any variable data entries associated with
VisClass
will also be destroyed.
Variable data may also be tagged for saving to the state file. That is, you can set up individual data entries to be saved to a state file and to be reinstated when the object is loaded from the state file. For more information about state saving, see Saving Object State .
Variable data may be defined in an object's declaration in your .goc file or may be added and removed during execution. This gives the potential for using variable data as temporary storage in an object's instance chunk; however, temporary data used in this manner should be kept small to avoid slowing down the system--constantly resizing instance chunks to add and remove vardata fields makes more work for the memory manager.
To define a variable data type in a given class, use the
@vardata
keyword as follows (an example is given in Defining Variable Data
):
@vardata <type> <vname>;
void
is specified, no extra data will be added to the data entry when it is created. (An instance data field may be declared as an array, just as in standard C.)Code Display 5-10 Examples of Instance Data Declarations
/* These are some data fields for MyDataClass. */
@instance ChunkHandle MDI_aChunk; @instance HelloInfoFlags MDI_flags; @instance byte MDI_lotsOfNumbers[32];
Some vardata types may have varying amounts of extra data. For example, one type may have either a word or a dword of extra data. To allow this, you can set up an alias with the new type attached using the keyword
@vardataAlias
:
@vardataAlias (<origName>) <newType> <newName>;
@vardata
.As noted earlier, the data type field in the data entry has two flags associated with it. These flags are each one bit:
off
explicitly, this flag will be set for every data type defined in a
.goc
or
.goh
file.The bitmask VDF_TYPE is a bitwise OR of VDF_EXTRA_DATA and VDF_SAVE_TO_STATE. You can use it to mask out all but those bits.
Code Display 5-11 Defining Variable Data
/* Hints are defined with the @vardata command, as is shown in GenClass. Only a * small portion of the hints for GenClass are shown here. Those with structures * or data types (not "void") have extra data fields associated with them. */
@class GenClass, VisClass, master, variant; /* Messages are defined here. */ /* Followed by instance data defined with @instance. */ @vardata void HINT_CENTER_MONIKER; @vardata SpecSizeSpec HINT_CUSTOM_CHILD_SPACING; @vardata char[] ATTR_GEN_INIT_FILE_KEY;
/* Relocatable instance fields (see the next section) are defined with * @reloc. This field contains an object pointer that must be resolved * when the GenClass object is loaded. */ @instance @link GI_link; @reloc GI_link, optr; @vardata DestinationClassArgs ATTR_GEN_DESTINATION_CLASS; @reloc ATTR_GEN_DESTINATION_CLASS, 0, optr; @endc
The kernel provides a number of routines an object may use to add, remove, and change its own vardata entries. Note that all these routines must be called from within the object containing the variable data entries; since variable data is instance data, it is against OOP doctrine for one object to alter another object's variable data directly.
Instead,
MetaClass
provides vardata messages that can be sent by one object to another to add, remove, change, or retrieve vardata entries of another object. The kernel routines and
MetaClass
messages are outlined below:
ObjVarAddData()
This routine adds an new entry for the passed data type or replaces the extra data associated with a particular data type entry.
ObjVarDeleteData()
This routine deletes the entry for a particular data type when passed the data type.
ObjVarDeleteDataAt()
This routine deletes a particular data entry when passed the entry's pointer as returned by
ObjVarAddData()
,
ObjVarFindData()
, or
ObjVarDerefData()
.
ObjVarScanData()
This routine causes the kernel to scan all data entries in an object's variable data and call any "handler routines" listed for them. This process is described below.
ObjVarFindData()
This routine searches for and returns (if possible) a pointer to a data entry of the passed data type.
ObjVarDerefData()
This routine returns a pointer to a data entry when passed the object's optr and the data type. If the entry does not exist, this routine will call on the object to create and initialize the entry. Such variable data then behaves much like instance data. The object containing the vardata is responsible for creating the entry and then initializing it upon receipt of a
MSG_META_INITIALIZE_VAR_DATA
, described below.
ObjVarDeleteDataRange()
This routine deletes all data entries with types in the passed range.
ObjVarCopyDataRange()
This routine copies all data entries within the passed range from one object's instance chunk to another`s. If any entries are copied, the destination object will be marked dirty for saving. This routine must be called by the destination object; it is bad policy for one object to alter another object's instance data. This routine is primarily for copying hints from one UI object to another and is not commonly used by applications.
The four messages (in
MetaClass
) that can be used to add, delete, and alter variable data entries remotely are listed below. Classes will never need to intercept and subclass these messages because the proper functionality is implemented in
MetaClass
.
MSG_META_ADD_VAR_DATA
MSG_META_DELETE_VAR_DATA
MSG_META_INITIALIZE_VAR_DATA
MSG_META_GET_VAR_DATA
In addition to supporting variable data structures, GEOS allows you to set up "handlers" for different variable data types. Handlers are routines that process a given data entry; for example, each generic UI object stores a number of hints. Specific UI classes, when attached to the generic object, have a specific routine to handle each hint supported. Some specific UIs do nothing with certain hints; these specific UIs do not have handlers for those hints.
Handlers are associated with data types through the use of a
VarDataCHandler
table. This is a table that you set up in your
.goc
file that contains pairings of routine names with
@vardata
field names. An example of the
VarDataCHandler
table is shown in Variable Data Handlers
.
A handler is simply a normal C routine or function and is defined as such. The handler should be declared as an
_pascal
routine.The table pairs the handler with the
@vardata
data type, and when
ObjVarScanData()
is called, all handlers for all data types are called in order. This is true for the object's class and all its superclasses since variable data is inherited just as normal instance data is. The handler can do almost anything appropriate with the exception of destroying the object or adding or deleting variable data from the object.
Code Display 5-12 Variable Data Handlers
/* This example is taken from the C VarData sample application. */
/* This is a VarDataCHandler. It is called by the ObjVarScanData() routine when * the data type corresponding to this routine in the VarDataCHandlerTable * is encountered. The parameters indicated are passed. * * This particular handler is actually used for several different data types * (see VarDataCHandlerTable below). The data type can be distinguished by * the `dataType' parameter. * * NOTE: VarDataInteractionHintHandler, like any handler used in a * VarDataCHandler structure, must be declared _pascal. */
void _pascal VarDataInteractionHintHandler(MemHandle mh, ChunkHandle chnk, void *data, word dataType, HandlerData *handlerData) {
if (dataType == HINT_ORIENT_CHILDREN_HORIZONTALLY) { handlerData->HD_flags.has_horiz = 1; } else if (dataType == HINT_ORIENT_CHILDREN_VERTICALLY) { handlerData->HD_flags.has_vert = 1; } else if (dataType == HINT_ALLOW_CHILDREN_TO_WRAP) { handlerData->HD_flags.has_allow_wrap = 1; } else if ((dataType == HINT_WRAP_AFTER_CHILD_COUNT) && (((WrapAfterChildCountData *) data)->WACCE_childCount == 2)) { handlerData->HD_flags.has_wrap_after = 1; } }
/* This is the VarDataCHandler Table. It consists of data type/VarDataCHandler * pairs. The VarDataCHandlers are far routines. */
static VarDataCHandler varDataInteractionHandlerTable[] = { {HINT_ORIENT_CHILDREN_HORIZONTALLY, VarDataInteractionHintHandler}, {HINT_ORIENT_CHILDREN_VERTICALLY, VarDataInteractionHintHandler}, {HINT_ALLOW_CHILDREN_TO_WRAP, VarDataInteractionHintHandler}, {HINT_WRAP_AFTER_CHILD_COUNT, VarDataInteractionHintHandler} };
@reloc
Some objects and classes may have instance data fields that must be resolved when the object is loaded and linked at run-time. For example, if the object contains an optr to another object, that optr must be updated when the object is loaded and resolved since the global memory handle can't be known at compile-time.
For some special instance fields, this happens automatically. For example, the
@composite
and
@link
fields as well as optrs are automatically resolved. However, if you add your own instance fields requiring relocation, you will have to set them up with the
@reloc
keyword. This is true for both static and variable data.
This keyword uses two formats. The first listed here is for normal, static instance data, and the second is used with variable data.
@reloc <iname>, [(<count>, <struct>)] <ptrType>;
optr
,
ptr
, or
handle
.@reloc <vname>, <fn>, [(<count>, <struct>)] ptrType;
optr
,
ptr
, or
handle
.@noreloc
To force an instance data field which would normally be relocatable (e.g., an optr) to not be relocatable, use the
@noreloc
keyword. Use this keyword together with the name of the field to be marked non-relocatable directly after defining the instance field itself as shown in Use of the @noreloc Keyword
.
Code Display 5-13 Use of the @noreloc Keyword
@instance optr MCI_ruler; /* Normally MCI_ruler would be reloc... */ @noreloc MCI_ruler; /* ...but now it isn't. */
@method, @extern
Methods are the routines executed when an object receives a message. Each class understands a certain set of messages; each of these has a place in the class' method table and corresponds to one method.
Although methods are class-specific, they are not defined between the
@class
and
@endc
of class definition. Instead, their declaration line links them to a single class and to a specific message. Goc, Glue, and GEOS build each class' method table automatically; you do not have to create the table yourself.
To define a method, use the
@method
keyword. This has the following structure:
@method [<hname>,] <cname>[, <mname>]+;
MyClass
' handler for
MSG_DO_SOMETHING
"MyDO_SOMETHING".
_reloc
, used in place of a message name when writing a method for
MSG_META_RELOC
and
MSG_META_UNRELOC
. A method handling
_reloc
will handle both
MSG_META_RELOC
and
MSG_META_UNRELOC
; the method code can check the value of its message argument to find out which message is being handled.
Note that the name of the method (the handler name) is optional. Parameters and return values are not put in the method declaration--these are defined with
@message
as discussed in The GEOS Message System
.
If you will wish to call the method as a routine occasionally, your compiler will probably require that you provide a prototype for the routine. If your
@message
declaration looks like
@message word MSG_MC_DO_SOMETHING(word thing);
and your
@method
declaration looks like
@method DoSomething, MyClass, MSG_MC_DO_SOMETHING { /* Code Here */ }
Then your protoype should look like
extern word _pascal DoSomething(optr oself, MyMessages message, word thing);
The name of the type
MyMessages
is constructed automatically by taking the name of the class, removing the "Class" suffix, and replacing said suffix with "Messages".
Normally, all of a class' methods will be coded in the same code file in which the class is declared with
@classdecl
. If, however, you find you need to declare a class' methods in a different file, you can use the
@extern
keyword to indicate a method is defined and/or used in a different object file. Goc will give no error if
@extern
is used and the method exists nowhere; Glue, however, will give a linking error in such a case. There is no such requirement, however, if you are putting only the class definition (the definitions between
@class
and
@endc
) in a different file. In this case, you can put the class definition in a
.goh
header file and the method code in the same
.goc
file as the
@classdecl
statement; you must
@include
the
.goh
file, but you won't need to use the
@extern
directive (as long as the method code is in the same file as the
@classdecl
directive).
The format for using
@extern
is as follows:
/* In the file in which the class is declared with * @classdecl: */ @extern method <cname>, <mname>+;
/* In the file containing the method code: */ @extern method <cname>, <mname>+ { ...method code goes here... }
Three parameters are passed automatically with messages and do not have to be declared in the
@message
definition. They are important to know when writing methods, however, because they can greatly simplify your code. These are standard parameters for all classes except
ProcessClass
and its subclasses below:
@self
, instead. Unlike pself,
@self
is always valid.
As mentioned,
ProcessClass
is a special type of class. It has no true instance data because it uses the standard PC structure of an application (idata, udata, etc.). It only has one standard parameter to each of its methods: the message that was sent to it. This is because the "instance data" of
ProcessClass
includes all the global variables of your program. They are accessed automatically, no oself or
@self
is required.
Code Display 5-14 A Class Definition
/* The class ValClass defines four messages that invoke four different methods. The * entire class is shown in this example; it will function properly if coded this * way. Note that the methods have the class name in their declaration line * and thus do not appear within the class definition. */
@class ValClass, MetaClass;
@instance int value; /* instance data value: an uninitialized integer */
/* message declarations * All four messages will be handled by this class. They return * the types shown and take the parameters defined. */ @message int MSG_VAL_GET_VALUE(); @message void MSG_VAL_SET_VALUE(int newValue); @message void MSG_VAL_NEGATE_VALUE(); @message Boolean MSG_VAL_IS_VALUE_BIGGER_THAN(int newValue);
@endc @classdecl ValClass; /* the class structure must be put in memory */
/* Method Declarations * Each of the four methods is a single line of code. Note that the * parameters are automatically defined in the message definition and do * not need to be restated in the method definition. The same is true of * the return type. Note also that the class and message names appear in * the @method line. */
@method ValGetValue, MyClass, MSG_VAL_GET_VALUE { return(@self->value); }
@method ValSetValue, MyClass, MSG_VAL_SET_VALUE { @self->value = newValue; }
@method ValNegateVal, MyClass, MSG_VAL_NEGATE_VALUE { @self->value *= -1; }
@method ValClass, MSG_VAL_IS_VALUE_BIGGER_THAN { /* This handler's name will automatically be created to be * ValVAL_IS_VALUE_BIGGER_THAN. You can use this name as a * C function call from within the same thread. */ return(@self->value > newValue); }
You may sometimes wish to call a method with normal C call-and-return conventions, rather than by sending a message. To do so, you will have to declare the method as a routine as well as a method. The declaration should have the following format:
extern <type> _pascal <MethodName>( optr oself, <TruncatedClassName>Messages message, <type1> <arg1>, <type2> <arg2>)
Code Display 5-15 Declaring a Method As a Routine
@message int MSG_HELLO_COUNTER_RECALCULATE_VALUE( \ HelloPriority priority, \ word randomDatum, \ char aLetter);
extern int _pascal HelloCounterRecalculateValue( optr oself, HelloCounterMessages message, HelloPriority priority, word randomDatum, char aLetter);
@method HelloCounterRecalculate, HelloCounterClass, \ MSG_HELLO_COUNTER_RECALCULATE_VALUE {
/* method code goes here... */
}
In GEOS programs, you can instantiate objects in two ways: You can declare them in your source code with the
@object
keyword, or you can instantiate them and fill in their instance data during execution. In most cases, you will probably do the former, especially with generic UI objects.
Additionally, you can create resources and chunks with the
@start, @end
,
@header
, and
@chunk
keywords. GEOS libraries also need an additional set of declarations in their definition (
.goh
) files; these declarations (
@deflib
and
@endlib
) indicate that the code contained between them is part of the specified library.
@deflib, @endlib
If your geode is a library, it will likely have a number of
.goh
files. Each of these files contains some portion of the library's code and is included by applications that use the library. The library code must be delimited by the two keywords
@deflib
and
@endlib
, which have the following formats:
@deflib <libname> @endlib
@deflib ui /* library code here */ @endlib
Note that these two keywords are only necessary in files that define classes in the library. Files that have just code or data used in the library do not require them (though they are allowed).
@start, @end, @header, @chunk, @gstring, @chunkArray, @elementArray, @extern
There are essentially three types of resources in GEOS: code resources containing routines and methods, object blocks containing object instance chunks (and often data chunks), and data resources containing only data chunks. Code resources are created automatically, and no declaration is required for them (unless you require special segments; then you should use the pragmas appropriate for your C compiler).
Object blocks or other LMem resources are declared with
@start
and
@end
. You can set a special header on a local memory resource with the
@header
keyword. These are described below, and an example of declaring the resource block is given in Declaring Data Resources
. Note that the
@header
keyword must come between the
@start
and
@end
delimiters.
@start <segname> [, <flags>]; @header <type> [= <init>]; @end <segname>
data
is set, the block will be set to a data resource--the default is an object block. If the flag
notDetachable
is set, the resource block will never be saved to a state file.
LMemBlockHeader
(for non object blocks) or
ObjLMemBlockHeader
(for object blocks).
The resource elements (objects or chunks, for example) are also declared
within the
@start
and
@end
delimiters. The
@chunk
keyword declares a data chunk and is shown below. For the
@object
keyword, see the next section.
@chunk <type> <name> [= <init>];
There is a special-case syntax for the declaration of a chunk which contains GString data. A GString is a stored series of graphics commands, discussed in detail in the Graphics Environment chapter. Its syntax may be summarized:
@gstring <name> = { <init> };
@gstring
chunk is set up by means of the
GS...()
macros.
Two other types of resource elements may also be defined, both of which are
array types. The
@chunkArray
keyword declares a chunk array structure,
and the
@elementArray
keyword declares an element array structure. See the Local Memory chapter, for information on the structure and usage of chunk and element arrays. These static arrays must not appear
in a resource marked data
; the resource may be marked
notDetachable
or may be unmarked. In the .gp
file,
the resource should be marked lmem
, optionally
read-only
.
The formats for the keywords are described below:
@chunkArray <stype> <aname> = {<init>};
@elementArray <stype> <aname> [ = {<init>} ];
@chunk ChunkArrayHeader <aname> = {0, sizeof(<stype>), 0, sizeof(ChunkArrayHeader) };
Here is an example showing two statically declared chunk arrays:
#define CELL_DATA_LENGTH 15 typedef char CellDataStruct[CELL_DATA_LENGTH]; @chunkArray CellDataStruct headerDataArray = { "no", "Description", "type", " $/lb" }; @chunkArray CellDataStruct coffeeDataArray = { "0", "House Blend", "PEET", "$ 6.95", "1", "Mocha-Java", "PEET", "$10.75", "2", "Garuda Blend", "PEET", "$ 9.25", "3", "Italian Dark", "DARK", "$ 8.75", "4", "French Roast", "DARK", "$ 8.75", "5", "Sumatra", "PACF", "$ 8.95", "6", "Sulawesi-Kal", "PACF", "$11.55", "7", "Java", "PACF", "$ 8.75", "8", "Aged Java", "PACF", "$ 9.75", "9", "Mexican", "AMER", "$ 7.55" };
One other keyword,
@extern
, allows you to use chunks from a different compilation session. The
@extern
keyword can be used to reference remote (external) chunks, objects, and visual monikers. If the item referenced by
@extern
is not actually defined anywhere, Glue will return an error when it tries to link the item. (Note also that Glue can not link through an external item; only one layer of external linkage is allowed. Thus, one object could not have an
@extern
to a chunk that had an
@extern
to a visMoniker.) Children in object trees may not be defined with
@extern
; Goc will produce an error in this case. The format of this keyword is as follows:
@extern chunk <cname>; @extern object <oname>; @extern visMoniker <vmname>;
Code Display 5-16 Declaring Data Resources
/* This example declares a data block with three different chunks in it and * its own header type. */
typedef struct { LMemBlockHeader MLMBH_meta; /* basic header structure */ int MLMBH_numEntries; /* the number of entries in the block */ } StudentBlockHeader;
@start StudentBlock, data; /* data flag indicates LMem block */ @header StudentBlockHeader = 1; /* initialize new header fields */
/* The three chunks are defined below. Each represents a single field associated * with a single student; that is why the header's MLMBH_numEntries field contains * one (rather than three). */
@chunk char firstName[] = "John"; @chunk char lastName[] = "Student"; @chunk int grade = 6;
@end StudentBlock /* end of resource block */
@object, @default, @specificUI, gcnList
The
@object
keyword is much like the
@chunk
keyword in its use. It allocates a chunk in a resource segment and fills it with initializer data. However, it also does much more in that it creates an object of a specified class and fills it with default data if no initializer data is specified.
The
@object
keyword, like
@chunk
, must be used between the delimiters
@start
and
@end
(see above). However, it must be used in an object block (it can not be used in a data resource segment). Its format is below, and several examples are shown in Declaring Objects with @object
.
@object <class> <name> = <flags>* { <fieldName> = <init>;* <varName> [= <init>];* };
ignoreDirty
. When set, it will cause the object to ignore when changes have been made to its instance data; this means it will always be restored from a state file in the same way as it is defined in the code file. This flag should
not
be set for any generic objects.
@object
declaration or use the keyword
@default
for the initializer. When declaring variable data, though, be aware that if the extra data is a structure, the initializer must be enclosed in curly braces.GEOS supports special instance data fields as described in Defining Instance Data Fields . These are declared as normal fields and are described below. Also, generic hints are implemented as variable data, so they are added to an object in the same way as other variable data types.
All objects declared in a static tree (e.g., your application's generic tree) should be in the same source file. If they are in different files, then they may be joined into a single tree only by dynamically adding objects from one file as children to objects of the other.
Note that if one file contains a tree of objects, then you may incorporate the whole tree by simply dynamically adding the top object in the file to the main tree. You won't have to add each object individually.
If an object declared in one source file will send a message to an object in another source file, you must include an
@extern
line in the source file containing the sending object:
@extern object ReceivingObjectName;
The message itself should be sent in the following manner (with variations possible if you will be using
@call
, passing arguments, or what have you):
optr ROOptr; ROOptr = GeodeGetOptrNS(@ReceivingObjectName); @send ROOptr::MSG_DO_SOMETHING(0, 0);
GeodeGetOptrNS()
returns the object pointer for the object. In the case of a multiply-launchable program, Goc has no way of knowing which program's @ReceivingObjectName should receive the message--by using
GeodeGetOptrNS()
, you ensure that the object getting the message will be in the same geode as the object calling the routine.
If an object is to be part of an object tree, its class (or one of its superclasses)
must
have at least an
@link
instance data field as does
GenClass
. If the object is to be allowed to have children, it must also have a field of type
@composite
. These allow Goc to automatically and correctly link the tree together.
As described in Defining Instance Data Fields
, the
@composite
field points to the first child of an object, and the
@link
field points either to the object's next sibling or back to the parent if there is no next sibling. However, all child declaration is done completely in the composite field when using
@object
. The format is as follows:
<fname> = <childName> [, <childname>]* ;
@composite
in the class definition.
@object
declaration.There are many examples of this in the sample applications. Some simple examples are shown below.
GI_comp = @TicTacPrimary;
GI_comp = @TicTacView, @TicTacGameMenu;
VCI_comp = @TTX1, @TTX2, @TTX3, @TTX4, @TTX5, @TTO1, @TTO2, @TTO3, @TTO4, @TTO5;
For an object to have a visual moniker, it must have an instance data field of type
@visMoniker
as
GenClass
does (see Defining Instance Data Fields
). If you are in fact working with GenClass' GI_visMoniker field, you might want to consult its description in the GenClass chapter.
Visual monikers may take many forms, and the declaration line changes depending on which form you are using. The form ranges from a simple text field to a complex list of possible monikers based on video resolution. Each of these forms and the declaration line for it is given below.
The following form is used for simple text strings (shown with example):
<fname> = <string>; GI_visMoniker = "One";
The following form is used for text strings that have a keyboard navigation character. A keyboard navigation character can be a character enclosed in single quotation marks, a numerical constant, or a text string representing some constant defined with
#define
. If it's a text string, the string is passed on for the C compiler to parse. The form is shown below with an example.
<fname> = <nav>, <string>; GI_visMoniker = 'V', "View";
The following form is used when a list of monikers is required. Most frequently, this form is used when defining an application's icons; one moniker for each potential screen resolution will be included in the list. The form is shown below, along with an example.
<fname> = list { <nameList> }; GI_visMoniker = list { @DocDirButtonSCMoniker, @DocDirButtonSMMoniker, @DocDirButtonSCGAMoniker }
@chunk
or
@visMoniker
keyword.
It is possible when declaring a list of visual monikers to have each moniker within a chunk or to declare each moniker with the
@visMoniker
keyword. If used on its own line, this keyword takes the form
@visMoniker <fname> = <init>;
The fields are the name of the moniker (same as that specified in the moniker list) and the moniker data. The visual moniker will be put in its own chunk just as if the moniker had been declared with the
@chunk
keyword, but using
@visMoniker
often is clearer and easier.
Visual monikers and strings presented by a program's UI are often accompanied by localization information. By placing a
@localize
keyword after a
@visMoniker
or
@chunk
construction, you can provide instructions for whoever uses the ResEdit program to localize your program (i.e., to translate its strings into a foreign language). This keyword can use any syntax from the following list:
@localize { <string> <min>-<max> }; @localize { <string> <length> }; @localize { <string> }; @localize <string>; @localize not;
GEOS employs a "General Change Notification" (GCN) system which allows objects to register for notification of certain types of events. For example, some applications may want to notify for changes in the file system (file deletes, moves, copies, etc.); some objects may require notification each time the selection changes (e.g., a PointSizeControl object needs to know when the user changes a text selection). For further information, see the General Change Notification chapter.
While many objects will add themselves to and remove themselves from GCN lists dynamically, others will need to be placed on certain GCN lists in their definitions. For this, the
gcnList
keyword (the only one not preceded by the marker
@
) is used. Its format is shown below:
gcnList(<manufID>, <ltype>) = <oname>,+;
A keyboard accelerator acts as a "hot key," invoking the object's default action when pressed.
The accelerator character is defined in an instance field declared with
@kbdAccelerator
as shown in Defining Instance Data Fields
. The form of declaration follows.
<fname> = [@specificUI] <mod>* <key>;
@kbdAccelerator
instance data field.
@specificUI
alt
,
control
,
ctrl
, and
shift
.Often an object will be declared with the default values of an instance data field. Other times, especially with generic UI object attributes, an object will have the default values with one or two modifications. In the first case, the instance data field does not need to be addressed at all; the default value will be used if no declaration is made.
In the second case, however, you must use the
@default
keyword to get the default values. If modifications are made and
@default
is not used, all default values will be lost. This is normally used only with bitfield-type attributes, and modifications are made with bitwise operators. The use of
@default
is shown below.
<fname> = @default [<op> [~]<attr>]*;
Code Display 5-17 Declaring Objects with @object
/* This example shows the use of @start, @object, @visMoniker, and @end. * It is taken from the TicTac sample application. */
@start AppResource; /* The AppResource resource block contains the TicTacApp * object and its visual moniker chunk. */
@object GenApplicationClass TicTacApp = { GI_visMoniker = list { @TicTacTextMoniker }; GI_comp = @TicTacPrimary; gcnList(MANUFACTURER_ID_GEOWORKS,GAGCNLT_WINDOWS) = @TicTacPrimary; }
@visMoniker TicTacTextMoniker = "TicTacToe"; @end AppResource
@start Interface; /* The Interface resource declares TicTac's primary window and other UI * gadgetry. Only the GenView from this application is shown. */ @object GenViewClass TicTacView = { GVI_content = @TicTacBoard; /* A relocatable optr field */ GVI_docBounds = {0, 0, BOARD_WIDTH, BOARD_HEIGHT}; /* A Rectangle structure */ GVI_color = { C_BLUE, 0, 0, 0 }; /* A ColorQuad structure */ GVI_horizAttrs = @default | GVDA_NO_LARGER_THAN_CONTENT | GVDA_NO_SMALLER_THAN_CONTENT & ~GVDA_SCROLLABLE; /* The NO_LARGER and NO_SMALLER attributes are set in the * field, and SCROLLABLE is cleared. The SCROLLABLE attribute * is not set by default for the GenView; it is shown here * for illustration. */ GVI_vertAttrs = @default | GVDA_NO_LARGER_THAN_CONTENT | GVDA_NO_SMALLER_THAN_CONTENT; }
@end Interface
@send, @call, @callsuper, @record, @dispatch, @dispatchcall, TravelOption, ObjDuplicateMessage(), ObjFreeMessage(), ObjGetMessageInfo()
Often you will have to send messages to objects throughout the system. You can send messages in several ways, but the two most basic and most frequently used involve the keywords
@call
and
@send
.
If a message is being sent across threads, you must be aware of synchronization issues. If the message does not cross thread boundaries, the kernel will link the code directly as if it were a function call. (This is an implementation difference only; you do not have to specify anything different in your code.)
The
@send
keyword causes the kernel to put the specified message into the recipient's event queue. Messages sent with
@send
may not give return values and may not take pointers to locked memory as arguments. The sender then continues executing without ever knowing whether the message was properly handled or not.
The
@call
keyword is used when the message being sent must return information to the sender. It is also used when the message must be handled immediately, before the sender is allowed to continue executing. In essence, the sender is "put to sleep" until the message has been processed, at which time the sender is woken up and may continue executing. If the message sent with
@call
is not handled (passed up the recipient's class tree and still not handled), it will return as if it had been; no error message will be returned.
The formats for
@send
and
@call
are similar. Use them like function calls. Their format is given below:
@send [,<flags>]+ \ <obj>::[{<cast2>}]<msg>(<params>*);
<ret> = @call [,<flags>]+ [{<cast>}] <obj>::\ [{<cast2>}]<msg>(<params>*);
@call
because
@send
does not return anything.
cast
.
cast2
.The flags allowed for these keywords are listed below. They are rarely used but are available.
forceQueue
@call
.
checkDuplicate
forceQueue
must also be passed. Events are checked from first (next-to-be-processed) to last. If the message is a duplicate, it will be dropped; however the
replace
flag, described below, can change this behavior.
checkLastOnly
checkDuplicate
, causing it to check only the last message in the event queue.
replace
checkDuplicate
and
checkLastOnly
by superseding the duplicate (old) event with the new one. The new event will be put in the duplicate's position in the event queue. If a duplicate is found but the
replace
flag is not passed, the duplicate will be dropped.
insertAtFront
canDiscardIfDesperate
The
@call
command can also be used within an expression in the same way a function call could. For example, the following conditional expression is valid:
if (@call MyObj::MSG_MYOBJ_TEST()) { /* conditional code */ }
The result of the message call will be evaluated in the if statement. Note that this may not be done with
@send
because it returns nothing.
Because of the way Goc processes message calls, it is impossible to nest messages on a single line. For example, this call is illegal:
@send Obj1::MSG_THATS_PASSED_AN_INT(\ @call Obj2::MSG_THAT_RETURNS_INT());
Any such call will generate a compile-time error. Instead, you should use temporary variables to break this up into several lines, e.g.:
int i;
i = @call Obj2::MSG_THAT_RETURNS_INT();
@send Obj2::MSG_THATS_PASSED_AN_INT(i);
Often you may wish to send a message directly to an object's superclass to ensure that default behavior is implemented. Use the
@callsuper
keyword with the following format:
@callsuper <obj>::<class>::<msg>(<pars>*) [<flgs>+];
@call
and
@send
. The object block must already be locked, and must be run by the current thread of execution. (Usually an object uses
@callsuper()
to send a message to itself.)
@call
and
@send
.
@call
and
@send
.
When used on a line by itself (with no parameters or return values),
@callsuper()
passes a received message on to the superclass. This is used quite often when a subclass wants to alter existing behavior rather than replace it.
By encapsulating messages, you can set up events to be sent out at a later time. An encapsulated message can include the message to be sent, the object it should be sent to, and the parameters that should be passed. Using encapsulated messages can sometimes simplify coding.
Messages can be encapsulated with the
@record
keyword and later dispatched with
@dispatch
and
@dispatchcall
. (Though the use of
@record
does not necessitate a later
@dispatch
--there are other uses for an encapsulated event.) In addition, when the event is dispatched, you can override the values set in the encapsulated event to change the destination or the message. You can also cast the return value to another type if necessary. The formats of these three keywords are as follows:
<event> = @record <obj>::[{<cast>}]<msg>(<params>*);
EventHandle
.
null
if the destination is determined when the message is dispatched.
null
if the message is determined when it is dispatched.
@call
and
@send
) that will be sent with the dispatched message.
You may create a copy of a recorded message by means of the
ObjDuplicateMessage()
routine. To free it, call
ObjFreeMessage()
. To discover the Message and destination object associated with a recorded event, call
ObjGetMessageInfo()
.
The
@dispatch
keyword is used to dispatch an encapsulated event to its destination. This is similar to
@send
in that it can have no return values. If the event has return values, use
@dispatchcall
(below).
@dispatch [noFree] \ <nObj>::[{<cast>}]<nMsg>::<event>;
null
.
null
. Any override will be sent with the same parameters as set in the encapsulated event.
The
@dispatchcall
keyword works exactly like the
@dispatch
keyword above except that it allows the use of return values. The sender will be "put to sleep" if necessary while the recipient processes the message and will be "woken up" when the message returns.
<ret> = @dispatchcall [noFree] [{<cast>}] <nObj>::\ <nMsg>::<event>;
@dispatch
.
All message-sending keywords described in the previous sections--
@call
,
@send
,
@record
,
@dispatch
, and
@dispatchcall
--can take expressions in place of a destination object's name. Additionally, the
@dispatch
and
@dispatchcall
keywords can take expressions in place of the message name. However, if an expression is used for the message, you must use a cast type to make sure Goc knows the return and parameter types. Note, however, that casts in this case use curly braces rather than parentheses.
Goc allows you to cast a message's pass and return values. This is best explained by example:
{ int swapInt; char c;
c = @call {MSG_1} object:: {MSG_2} MSG_X(swapInt);
}
In this case, MSG_2 takes an integer argument and MSG_1 returns a char. The casts tell Goc how MSG_X will receive parameters and return results. Goc needs the casts in those cases where MSG_X doesn't appear explicitly (perhaps it has been stored as an integer), and thus Goc would not be able to parse the parameters or return values.
When Goc tries to determine proper parameters and returns, it will look to the following sources when available. When trying to figure out parameters, it will look first for MSG_2, then MSG_X, and MSG_1 last. The first one Goc finds will determine the parameters.
@send
and
@record
don't support return values, but on a
@call
, Goc will figure out return values by looking at MSG_1, MSG_X, and finally MSG_2.
In this case, Goc will pass to fn's method like MSG_CAST_2 but will return values as MSG_CAST_1 does:
Message fn = GetMessageToCall(); c = @call {MSG_CAST_1} myObj:: {MSG_CAST_2} fn(x);
Now we pass to MSG_B like MSG_CAST_2, but return like MSG_B:
c = @call myObj:: {MSG_CAST_2} MSG_B(swapInt);
MSG_META_SEND_CLASSED_EVENT, TravelOption
You do not always need to specifically include an object as a destination for your message. In many cases, you may be able to generically address your message using a
classed event
. A classed event consists of a pre-defined
TravelOption
enumerated type and an EventHandle recorded using the @record Goc construct. The event in this case does not contain a specific object, but rather an object class.
The
TravelOption
acts as a navigator, determining along what path the message should be delivered. The object class within the classed event acts as a filter, determining if the object sent the message is an object of that class. If the object first encountered along the path dictated by the
TravelOption
is not a matching class, the classed event is passed to the next object on that path. When the class finally matches, the message will be handled by that object.
The classed event is originally dispathed from the object by sending MSG_META_SEND_CLASSED_EVENT. This message is usually sent by an object to itself. The travel options available depend on the class(es) of the object receiving the message. That is, the object receiving the message must be a sub-class of one of the following:
MetaClass
(Usable by all GEOS object classes)
TravelOption
indicates that the message should be delivered to the object block's output optr.
ProcessClass
object.
GenClass
(Usable by all generic objects)
VisClass
(Usable by all visible objects)
GenApplicationClass
(Usable by all Application objects)
To send a classed event, you must first record the event, specifying the message to send and the object class to handle the message. Then you must send MSG_META_SEND_CLASSED_EVENT--usually to yourself-- passing that handle and a
TravelOption
.
<event> = @record <objClass>::<msg>
@send self::MSG_META_SEND_CLASSED_EVENT(<event>, <TravelOption>);
Code Display 5-18 Sending a Classed Event
/* * First record the Classed event. In this case, we want an object of class * `MyFooClass' to handle MSG_FOO. We will send this classed event along the * target hierarchy. We want to begin the path at the application object, so we * will use the TO_APP_TARGET TravelOption. */
event = @record MyFooClass::MSG_FOO;
/* * Then dispatch the classed event using MSG_META_SEND_CLASSED_EVENT. */
@send self::MSG_META_SEND_CLASSED_EVENT(event, TO_APP_TARGET);
All messages, when received, contain three basic parameters: the message number (
message
), the optr of the recipient (
oself
), and a far pointer to the recipient's locked instance chunk (
pself
). This allows several shortcuts and shorthand formats for use within methods:
@callsuper;
When used in a method as above, the
@callsuper
keyword passes the received message up to the object's superclass. Use this whenever subclassing a message when the default functionality must be preserved.
<ret> = @call self::<msg>(<params>*);
Any object can send a message to itself using
@call
and "self" as the destination. The remainder of the command is the same as a normal
@call
.
<ret> = @call process::<msg>(<params>*);
Any object can send a message to its Process object with
@call
and "process" as the destination. (The Process object is the object of class
ProcessClass
.) The remainder of the command is the same as a normal
@call
.
<ret> = @call application::<msg>(<params>*);
Any object can send a message to its Application object (of class
GenApplicationClass
) with
@call
and "application" as the destination. The remainder of the command is the same as a normal
@call
.
<ret> = @call @visParent::<msg>(<params>*);
Any object in a visible tree can use
@visParent
as the destination of an
@call
command. The message will be sent to the object's parent in the visible object tree. The remainder of the command is the same as a normal
@call
.
<ret> = @call @genParent::<msg>(<params>*);
Any object in a generic tree can use
@genParent
as the destination of an
@call
command. The message will be sent to the object's parent in the generic object tree. The remainder of the command is the same as a normal
@call
.
@send @visChildren::<msg>(<params>*);
Any composite object in a visible object tree (therefore a subclass of
VisCompClass
) can send a message that will be dispatched at once to all of its children. Any message sent with
@visChildren
as the destination must be dispatched with the
@send
keyword and therefore can have no return value.
@send @genChildren::<msg>(<params>*);
Any composite object in a generic object tree (therefore a subclass of
GenClass
) can send a message that will be dispatched at once to all of its children. Any message sent with
@genChildren
as the destination must be dispatched with the
@send
keyword and therefore can have no return value.
In addition to the above shortcuts, you may also pass the optr of an object using
@<obj>
, where
<obj>
represents the name of the object. This syntax gets translated by Goc into
(optr)&<obj>
; this is similar to using the ampersand (&) to pass a pointer.
In addition to knowing how to declare objects and classes, you need to know how to manage objects during execution. This includes instantiating new objects, deleting objects, saving object state, and moving objects around object trees.
Both the kernel and
MetaClass
(the topmost class in any class hierarchy) have routines and methods to create, manage, and destroy objects. You will probably not have to or want to use all these routines and methods, but understanding what they do and how they work can help you understand the object system as a whole.
ObjDuplicateResource(), ObjInstantiate(), MSG_META_INITIALIZE, MSG_GEN_COPY_TREE
You can introduce objects to the system in four basic ways. Each of these has benefits and drawbacks, and each has an appropriate place and time for use. It is unlikely, however, that you will use all four different means.
This is a common and simple way to introduce objects to the system. The Hello World sample application uses this method of creating and loading objects. Resource blocks are contained in your geode's executable file (the .geo file) and are automatically loaded when accessed. These resources may also be tagged for saving to state files automatically.
Setting up an object resource is simply a matter of defining the resource and using the
@object
keyword to define each object in the resource. The object resource block is automatically created and put in your executable file. Each object defined with
@object
is allocated a chunk and a chunk handle within the resource block. Because both the chunk handle and the handle of the resource block are known, accessing individual objects in the resource is simple. In essence, when you set up an object resource, you don't need to worry about bringing the objects into the system or shutting them down.
Using a resource for objects has limitations. Objects loaded from resources are always loaded with the same characteristics. This can be a problem if you need to have several different copies of a certain set of objects, and each copy can be changed. In this case, you would duplicate the resource (see below) before accessing the objects within it.
For an example of objects defined by means of declaring them with the
@object
keyword within an object resource, see Declaring Objects with @object
.
To define an object resource, you must know what objects you'll require before your geode is launched. Some complex programs will dynamically instantiate individual objects or entire trees without knowing previously what objects will be required. To do this, you'll need to use
ObjInstantiate()
(see below) for instantiating individual objects.
This is another common method employed by both the User Interface and applications. It provides the simplicity of the object resource model (above) while allowing you to have several different copies of the resource. Thus, it's perfect if you want to use templates for your object blocks (this is what the Document Control object does, as shown in the Tutorial application (see APPL\TUTORIAL\MCHRT4\MCHRT.GOC)).
First, you must set up an object resource in your code file with
@start
,
@end
, and
@object
. In the
.gp
file, mark the resource
discard-only
. The objects in such a "template" resource should not be linked to any object outside the block. Generic object branches created in this manner should have their topmost object marked as not usable (~GS_USABLE); this is because it is illegal for a generic object to be usable without having a generic parent. Instead of accessing these objects directly, you should duplicate the resource block. (A resource can not be both duplicated and used directly.)
This is done with
ObjDuplicateResource()
, which allocates a new block on the heap, sets it up properly, and copies the resource directly into it.
 Because all the chunk handles of all the objects will be the same as in the source block, you can easily access any object in the duplicate. Once copied, the duplicate objects may be added to your generic tree and then set GS_USABLE. And, by using
ObjDuplicateResource()
more than once on the same resource, you can have several different, possibly modified versions of the resource at once.
As with using resource blocks, however, you must know the configuration of all your template objects beforehand. You may still need to add new objects to the resource or dynamically create other objects. This is the primary drawback of this method.
Additionally, if you duplicate resource blocks, you should also free them when they're not needed any more. Generic objects in the block should be set not usable and then removed from the tree before the resource is freed. Freeing should be done by sending
MSG_META_BLOCK_FREE
to any object in the block or by calling
ObjFreeObjBlock()
. Use of the kernel routine
ObjFreeDuplicate()
is not recommended as it requires all objects in the block to be thoroughly clean of any ties to the system. (
MSG_META_BLOCK_FREE
and
ObjFreeObjBlock()
ensure that the objects have had adequate time to relinquish these ties first.)
The most complex of these three options, this can provide the flexibility needed for all cases. The actual act of instantiating an object is not difficult or very complex. However, it is time and labor intensive and requires several steps that are not necessary when using object resources. In addition, cleaning up after objects created in this manner is more complex.
To create a new object on the fly, you first must set up a place to put it. To do this, allocate a memory block on the global heap (you can instead use an existing object block, of course) and set it up with the proper flags and header to be an object block. There is no need to lock the chosen block on the heap. The block is now set up to receive the new object.
To actually create the new object, call the kernel routine
ObjInstantiate()
. This will lock the block on the heap, create a chunk in the object block and zero the instance chunk. If the object is of a class with master parts, the instance chunk will remain uninitialized until the first time a class in the master group receives any message. If the class is a direct descendant of
MetaClass
, the instance chunk will be immediately initialized to default values with
MSG_META_INITIALIZE
. If you want to change or add to the default data of this type of object, subclass
MSG_META_INITIALIZE
; be sure to call the superclass first. To initialize any master group of an object, send it a classed event that will be handled by a class in that master level.
After calling
ObjInstantiate()
, the block will be automatically unlocked. An example of instantiating a new object is shown in Instantiating an Object
. Generic objects created this way may then be added to a generic tree and set usable. They may be destroyed using
MSG_GEN_DESTROY
.
Code Display 5-19 Instantiating an Object
/* This sample of code belongs to a sample GenInteraction object (the object would * have to be a subclass of GenInteractionClass). It does several things: * 1. It instantiates a new GenTrigger object. The new * GenTrigger will be added to the same object block * containing the GenInteraction handling the message. * 2. It adds the new GenTrigger as a child of the * SampInteraction (the handling object). * 3. It sets the GenTrigger usable and enabled. */
@method SampInteractionClass, MSG_SAMP_INTERACTION_CREATE_TRIGGER { optr newTrig;
newTrig = ObjInstantiate(OptrToHandle(oself), (ClassStruct *)&GenTriggerClass); /* The two parameters are the handle of an object block and the * pointer to a class definition. The object block is the same * one containing the GenInteraction, whose optr is contained in * the standard oself parameter. The class structure pointer points * to the class definition of GenTriggerClass. */
/* Now set the trigger as the last child of the GenInteraction. */ @call self::MSG_GEN_ADD_CHILD(newTrig, (CCF_MARK_DIRTY | CCF_LAST);
/* Now set the trigger usable and enabled. */ @call newTrig::MSG_GEN_SET_USABLE(VUM_DELAYED_VIA_UI_QUEUE); @call newTrig::MSG_GEN_SET_ENABLED(VUM_NOW); }
The fourth way to create new objects is by using the message
MSG_GEN_COPY_TREE
. This, when sent to a generic object in a generic tree, copies an entire generic tree below and including the object into another, pre-existing object block.
This is an easy way to copy generic trees, one of the more common purposes of creating new objects. However, it only works with the generic objects (with a superclass
GenClass
). Trees created using
MSG_GEN_COPY_TREE
can be destroyed with
MSG_GEN_DESTROY
.
For an example of
MSG_GEN_COPY_TREE
use, see the SDK_C\GENTREE sample application.
ObjIncInUseCount(), ObjDecInUseCount(), ObjLockObjBlock(), ObjFreeObjBlock(), ObjFreeDuplicate(), ObjTestIfObjBlockRunByCurThread(), ObjBlockSetOutput(), ObjBlockGetOutput()
Once you have an object block created, either with
ObjDuplicateResource()
or with the memory routines, there are several things you can do with it. It may be treated as a normal memory block, but there are also several routines for use specifically with object blocks:
ObjIncInUseCount()
and
ObjDecInUseCount()
increment and decrement an object block's in-use count (used to ensure the block can't be freed while an object is still receiving messages).
ObjLockObjBlock()
locks the object block on the global heap.
ObjFreeObjBlock()
frees any object block.
ObjFreeDuplicate()
is the low-level routine which frees an object block created with
ObjDuplicateResource()
.
ObjTestIfObjBlockRunByCurThread()
returns a Boolean value indicating whether the calling thread runs a given object block.
ObjBlockSetOutput()
and
ObjBlockGetOutput()
set and return the optr of the object set to receive output messages (i.e., messages sent with travel option TO_OBJ_BLOCK_OUTPUT) from all the objects within the object block.
ObjIsObjectInClass(), ObjIsClassADescendant(), ObjGetFlags(), ObjGetFlagsHandles(), ObjSetFlags(), ObjSetFlagsHandles(), ObjDoRelocation(), ObjDoUnRelocation(), ObjResizeMaster(), ObjInitializeMaster(), ObjInitializePart()
The kernel supplies several routines for working with and modifying individual object chunks and object data. These are all described fully in the Routine Reference Book; most are not commonly used by applications.
ObjIsObjectInClass()
takes a class and an optr and returns whether the object is a member of the class.
A related message,
ObjIsClassADescendant()
determines whether a given class is a subclass of another.
ObjGetFlags()
returns the object flags for a given object instance chunk;
ObjSetFlags()
sets the flags to passed values.
ObjGetFlagsHandles()
and
ObjSetFlagsHandles()
refer to objects by means of handles instead of optrs.
ObjDoRelocation()
processes any passed instance data fields in the object declared as relocatable;
ObjDoUnRelocation()
returns the passed relocatable fields to their index values.
ObjInitializeMaster()
causes the system to build out a particular master group's instance data for an object.
ObjInitializePart()
causes the system to build all master groups above and including the passed level. (This will also resolve variant classes.)
ObjResizeMaster()
resizes a given master part of the instance chunk, causing the chunk to be resized.
ObjLinkFindParent(), ObjCompAddChild(), ObjCompRemoveChild(), ObjCompMoveChild(), ObjCompFindChildByOptr(), ObjCompFindChildByNumber(), ObjCompProcessChildren()
Many objects will be part of object trees. Nearly all generic UI and visible objects exist as members of trees for organizational purposes. Object trees can be useful, powerful, and convenient mechanisms for organizing your objects.
An object tree is made up of "composite" objects--objects which may or may not have children. The distinguishing characteristic of a composite object is that it has one instance data field declared with the
@composite
keyword and another declared with the
@link
keyword. The
@composite
field contains a pointer to the object's first child in the tree, and the
@link
field contains a pointer to the object's next sibling.