GEOS SDK TechDocs
|
|
4.5 Sending Messages
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.
You are returned a handle to the new object block, which you can then modify any way you like. 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.
If you set up an object resource block containing composite objects, it's very easy to set up an object tree. Your generic UI objects are declared in a tree with the GenApplication object at its head. Additionally, it's easy to alter an object tree once it's been created. The kernel provides several routines, and
MetaClass
uses several messages for adding, removing, and moving objects to, from, and within trees.
ObjLinkFindParent()
This routine finds the optr of the calling object's direct parent. The kernel traverses the link fields until it returns to the parent object.
ObjCompFindChildByOptr()
This routine returns the number of the child (first, second, third, etc.) whose optr is passed. The child must exist and must be a child of the calling object.
ObjCompFindChildByNumber()
This routine returns the optr of the child whose number (first, second, etc.) is passed.
ObjCompAddChild()
This routine takes an object's optr and adds it to the caller's list of children. Depending on the flags passed, the child may be inserted in any child position (first, second, etc.).
ObjCompMoveChild()
This routine takes a child object and moves it to a new position. However, it will still remain a child of the calling object. If you want to move the child to be a child of a different object, you must first remove it from the tree altogether and then add it to the other parent.
ObjCompRemoveChild()
This routine removes a child object from the object tree.
ObjCompProcessChildren()
This routine calls a callback routine for each child of the calling object in turn. The callback routine may do virtually anything (except destroy the object or free its chunk or something similar).
By using the above routines, you can fully manipulate any object tree and the objects within it.
MSG_META_DETACH, MSG_META_DETACH_COMPLETE, MSG_META_ACK, MSG_META_OBJ_FLUSH_INPUT_QUEUE, MSG_META_OBJ_FREE, MSG_META_FINAL_OBJ_FREE, ObjInitDetach(), ObjIncDetach(), ObjEnableDetach(), ObjFreeChunk(), ObjFreeChunkHandles()
While creating objects is actually quite simple, detaching and destroying them can be quite involved. For this reason, GEOS does most of the work for you, and in most cases you won't have to worry about what happens when your application is shut down or saved to a state file. However, if you instantiate individual objects of your own classes, you should be very careful about how your objects are detached from the system and destroyed.
Detaching objects involves severing all of the object's ties with the rest of the system. Destruction of an object entails actually removing the object's instance chunk and related structures, making sure that it has handled all its waiting messages.
Throughout its life, an object is likely to become involved with a number of other things--other objects, drivers, files, streams, the memory manager, the kernel--and each of these things may send the object messages from time to time. The task, when detaching an object from the system, is to sever all the object's ties with the outside world, to make sure that no other entity will ever try to contact the object again.
To those unfamiliar with these problems, they can be overwhelming. However, GEOS takes care of them for you in most situations. All generic and visible objects, all objects in object trees, and all objects that maintain an "active list" will automatically (in nearly all cases) have the detach functionality built in by
MetaClass
.
When an object receives
MSG_META_DETACH
, it begins the detach sequence to sever all its ties with other entities in the system. It must first notify all its children and all the objects on its various notification lists that it will be going away (most often, all its children, by association, will also be detached). It then must clear its message queues. Finally, it must acknowledge its detachment to the object that originally sent
MSG_META_DETACH
. Each of these phases is described in detail below and is implemented by
MetaClass
. You have to do none of this work unless your object is truly a special case.
Detaching in conjunction with destruction is somewhat intricate because not only must the object notify all other interested parties, but it must also receive acknowledgment of the notice. Otherwise, the notification and some other message could be sent at the same time, and the object could be destroyed before the notification can be handled. (Destruction is discussed in the following section.)
Because any object may be put in the position of being detached and then immediately destroyed, it must send out notification and then wait until all the notices have been acknowledged before continuing with other tasks. The kernel and
MetaClass
implement a mechanism for this using four kernel routines. Again, you do not need to do this since all classes have
MetaClass
as their root.
First the object being detached (in its
MSG_META_DETACH
handler) calls the routine
ObjInitDetach()
. This tells the kernel that the object is initiating a detach sequence and that the acknowledgment mechanism must be set up. The kernel will allocate a variable data entry to hold a count of the number of notices sent and acknowledgments received.
After this, the object must send a
MSG_META_DETACH
or its equivalent to each of its children and each of the objects on its "active list." With each notice sent, the object
must
call
ObjIncDetach()
, which increments the notice count.
After sending all the notices, the object then calls the kernel routine
ObjEnableDetach()
. This notifies the kernel that all the notices have been sent and that the object is waiting for the acknowledgments.
Acknowledgment comes in the form of
MSG_META_ACK
and is received by the object being detached.
MSG_META_ACK
is handled by
MetaClass
and will decrement the notice count, essentially saying there are one fewer notices left to be received.
When the final
MSG_META_ACK
is received (setting the notice count to zero) and
ObjEnableDetach()
has
also
been called, the kernel will automatically send a
MSG_META_DETACH_COMPLETE
to the object. This assures the object that it will never receive another message from another entity in the system.
The final step in the detach sequence is acknowledging that the object has been detached. In its
MSG_META_DETACH_COMPLETE
handler, the object should send a
MSG_META_ACK
to the object that originated the detach sequence. This will allow that object to continue with its detach sequence if it was involved in one; without this step, only leaves of object trees could ever be detached. This final step is provided in default handlers in
MetaClass
and is inherited by all objects.
To free a chunk, perhaps a chunk used to store an object, use the
ObjFreeChunk()
routine. Normally this will free the chunk; however if the chunk is storing an object from a resource, the chunk will instead be dirtied and resized to zero. To free a chunk referenced by handles, call
ObjFreeChunkHandles()
instead.
The destruction sequence must be initiated from outside and will begin when the object receives a
MSG_META_OBJ_FREE
. Often, the
MSG_META_OBJ_FREE
will be sent by the object to itself.
The destruction sequence consists of three steps: First, the object must clear out its message queues; even though it is detached and can not receive new messages, there may be some left over in the queue (an error if it occurs). Second, it must finish executing its code and working with its instance data. Third, it must free its instance chunk. Each of these steps is described below.
Even though the object has notified the rest of the system that it is going away, it still must flush its message queues of any unhandled messages. These messages could have been received between the time the original
MSG_META_OBJ_FREE
was received and notification was sent out (due to interrupts or context switching).
To clear its message queues, the object must send itself a
MSG_META_OBJ_FLUSH_INPUT_QUEUE
, which will ensure that any messages in the queues are handled appropriately before the object shuts down. This step is handled automatically by the
MSG_META_OBJ_FREE
handler in
MetaClass
. You should never have to send this message, and indeed its use is discouraged.
To the outside world, the second and third steps seem like a single step. However,
MSG_META_OBJ_FREE
can not simply free the instance chunk after the queues are cleared; it must be able to access the instance chunk until all the method code has been executed.
So,
MSG_META_OBJ_FREE
sends the final message to the object,
MSG_META_FINAL_OBJ_FREE
, and then exits.
MSG_META_FINAL_OBJ_FREE
waits a short while and then frees the object's chunk. This ensures that
MSG_META_OBJ_FREE
has finished and the chunk is not being used by any code.
If you're not careful, you can cause the detach mechanism to fail by instantiating an object on the fly and saving that object's optr. If the object is then detached and you don't account for the saved optr, you could later send a message to a nonexistent object. This has undefined results and can be nearly impossible to track down.
Note that objects created within resources and by
ObjDuplicateResource()
will almost always automatically be taken care of by the detach mechanism. Objects you create with
ObjInstantiate()
are the ones to be careful with.
ObjSaveBlock(), ObjMarkDirty(), ObjMapSavedToState(), ObjMapStateToSaved()
Object state saving is almost entirely contained within the system. For the most part, only UI objects are saved to state files; however, you can mark other object blocks for saving. State saving is described in full in the Applications and Geodes chapter.
GEOS SDK TechDocs
|
|
4.5 Sending Messages