Figure 4-0

Display 4-0

SwatDisplay 4-0

Table 4-0
4Data Structures and UI Gadgetry

In this chapter we add some UI gadgetry to the application. By creating some 
objects and writing some procedural code, we will construct a cluster of 
gadgetry which enables the user to maintain a list of data.

We will explore the application's new code, both examining blocks of the 
source code to see what they do and going through a debugging session 
during which we set some breakpoints and do some stepping through code.

	4.1	Editing the Application

At the end of this chapter you will find a complete code listing for this stage 
of the application. Pieces of code which have been added or changed since the 
previous stage have been marked with lines like that next to this paragraph.

You should add the new parts by means of a text editor. When you are done, 
invoke pmake to compile your program. You may remember that in the 
previous chapter, you had to use mkmf and pmake depends. However, the 
files generated by those commands will still work for us; we need only do a 
pmake now.

Once you've compiled the application send it down to the target machine as 
before: if you still have Swat attached, then use the send Swat command; 
otherwise, make sure that the pccom tool is running on the target machine 
and invoke pcs on the host machine.

	4.2	The Application so Far

Our application has sprouted a scrolling list, three buttons, and a place to 
input a numerical value. The buttons allow the user to add, remove, and 
change the values of items in the list. By typing a number in the place 
provided, the user can determine the value which will appear in the list. By 
clicking on the list, the user can determine which list item should be removed 
or changed, or where a new item should be added.

u	The buttons are the instigators of all change. The user can click 
anywhere in the window, but the contents of the data list won't change 
unless the user interacts with the buttons.

u	The underlying data structure which will represent the data list is 
managed by the process thread. All user interaction is handled, 
appropriately enough, by the UI thread. The user will interact with the 
buttons, which are managed by the UI thread. This will result in a 
message sent to the process thread, specifically to the process object. The 
process object will update the data structure representing the list, and 
then send messages to those UI objects which need to update themselves 
in light of the new data.

u	Our underlying data structure will consist of a linked list of chunks. You 
may wonder what a "chunk" is. You may recall that the memory heap is 
organized into blocks of memory which are referenced by handles. The 
memory manager controls these blocks. This makes for a convenient 
system, but if we were to allocate a separate block of memory for each 
new item in our linked list, it would get wasteful-there is a a certain 
amount of overhead for each block of memory. We will take one block of 
memory and treat it as a sort of mini-heap organized into mini-blocks. 
These "mini-blocks" are known as chunks. The "mini-heap" we will call a 
local memory block, and we will access its contents by means of LMem 
routines.

u	The local memory block will reside within a virtual memory file. This 
means that our program will create a file and the linked list data will be 
stored there. Whenever we need to work with the data, we will load it into 
a block of memory and lock that block of memory into place. Actually, 
since we're erasing our list data between runs each time, there's not 
much point to using VM routines, but this can serve as a quick lesson of 
basic VM usage.

u	The program is no longer multi-launchable. This makes sense, as we 
hard-coded the name of the file that we were using for VM storage, and 
copies of our application shouldn't have to share. Also, being 
single-launchable allows us to do some things in our program in a more 
simple way, not having to make clear to the linker which application's 
resources we will work with each time. Since our application will 
eventually support multiple documents, we may never bother to do the 
cleaning up necessary to make it multi-launchable.

	4.3	MCHRT.GP Change

We changed one line of the MCHRT.GP file to let glue know that our 
application is now single-launchable instead of multi-launchable.

type appl, process, single

Our new addition, the "single" keyword, signals that the application is 
single-launchable.

	4.4	MCHRT.GOC: Data & 
Structures

Our additions to MCHRT.GOC fall into three basic categories. First, we will 
declare global variables and set up prototypes for routines and messages. 
Next, we will declare some new objects and place them into our Generic UI 
tree. Then we will add some procedural code by which our objects will 
respond to some of the messages which will be sent around. We'll look at the 
variables and prototypes first.

Let's take a look at the new lines of code. We won't look at the new comments.

#define FAKE_LIST_ITEM 0

This is a normal C #define statement. When working with the list display, 
we will often need to treat its first item, the "Data:" line, as a special case. We 
set up this constant so that when we check to see if we're dealing with this 
special case we can say "if (variable == FAKE_LIST_ITEM)," which is arguable 
easier to understand than "if (variable == 0)." In Goc, as in any programming 
language, making your code easy to read is a good thing.

@class MCProcessClass, GenProcessClass; 
	@message (GEN_DYNAMIC_LIST_QUERY_MSG) MSG_MCP_SET_DATA_ITEM_MONIKER;
	@message void MSG_MCP_DELETE_DATA_ITEM();
	@message void MSG_MCP_INSERT_DATA_ITEM();
	@message void MSG_MCP_SET_DATA_ITEM();
@endc /* end of MCProcessClass definition */ 

You may recall that in the last stage of our program MCProcessClass was 
a subclass of GenProcessClass, but that we hadn't actually made it behave 
any differently than GenProcessClass. Now we're introducing some 
customizations: MCProcessClass will define some of its own messages that 
it's prepared to receive. Here we define the messages, specifying their pass 
and return values by means of the @message keyword. The last three 
messages take no arguments and return nothing. If we were defining a 
message MSG_MCP_GET_DATA_ITEM which took a string and returned an 
integer, its definition might look like

@message int MSG_MCP_GET_DATA_ITEM(char *theString);

For our first message, MSG_MCP_SET_DATA_ITEM_MONIKER, we're defining 
our pass and return values in terms of the 
GEN_DYNAMIC_LIST_QUERY_MSG message prototype. This is a prototype 
defined with the GenDynamicListClass; objects of this class will send out 
queries every so often, and that query will have a certain set of pass and 
return values. Whichever object will receive this message must be prepared 
to handle it, and by using the appropriate prototype we know we'll have the 
correct pass and return values.

You can see the GEN_DYNAMIC_LIST_QUERY_MSG message prototype in the 
INCLUDE\OBJECTS\GDLISTC.GOH include file:

@prototype void GEN_DYNAMIC_LIST_QUERY_MSG(
						optr list,
						word item);

From this we know that our message definition is equivalent to having 
defined it as follows

@message void MSG_MCP_SET_DATA_ITEM_MONIKER(
						optr list,
						word item);

Using the message prototype makes the message's purpose clearer. For more 
information about message prototypes, see "GEOS Programming," Chapter 5 
of the Concepts book.

An optr is an "object pointer," a unique identifier for an object. A word is an 
unsigned 16-bit number, a type used in many places throughout the system. 
In this case, the object identified by the optr is the gadget which will display 
our data list and the word will tell our process which of the list items it should 
work with.

extern word _pascal MCListGetDataItem(word ordinal); 

This is the prototype for a routine. This is as it would be in a normal C 
program.

typedef struct {
	word		LN_data;
	ChunkHandle 		LN_next;
} ListNode; 

VMFileHandle 	dataFile; 	/* File which will hold our data */
VMBlockHandle 	dataFileBlock; /* Block within dataFile */
MemHandle dataListBlock; 			 /* Block of memory which will hold our linked list. */
MemHandle *dataListBlockPtr = &dataListBlock; /* Pointer to above handle */
ChunkHandle dataListHead = 0; /* Chunk containing head of linked list. */
ChunkHandle tempListItem; /*Chunk handle we will use when traversing lists */
ListNode *tempNode; /* List item which we will use when traversing lists. */ 

Here we're declaring a structure and a number of global variables, a 
straightforward enough operation, except that you're probably unfamiliar 
with the types involved.

A ChunkHandle is a handle which we will use to reference a chunk, one of 
those mini-blocks of memory within a local memory block. We will store one 
ListNode structure in each of our chunks, and we will use a ChunkHandle 
to reference the "next" item for each member of our linked list. We will also 
use a ChunkHandle to reference the first item of our list.

The VMFileHandle will reference the file in which our list resides, and the 
VMBlockHandle will reference the block within the file which will act as 
our local memory block, the block which will act as the "mini-heap". We will 
use the MemHandle to access the local memory block while that block is 
loaded into memory. 

	4.5	MCHRT.GOC: New Objects

@object GenPrimaryClass MCPrimary = { 	
	GI_comp = @MCDataList, @MCAddTrigger,
				 @MCDeleteTrigger, @MCChangeTrigger, @MCValue; 
} 

Our primary window now has some child objects, the new Generic UI gadgets 
which we have added. 

@object GenDynamicListClass MCDataList = {
	GIGI_selection = FAKE_LIST_ITEM; 
	GIGI_numSelections = 1; 
	GIGI_destination = process;
	GDLI_numItems = 1;
	GDLI_queryMsg = MSG_MCP_SET_DATA_ITEM_MONIKER;
	HINT_ITEM_GROUP_SCROLLABLE;
} 

Our first new object is MCDataList, the scrolling list which displays the 
data. This object doesn't keep track of the data list itself. Whenever it needs 
to redraw one of the items in the list, it will query the process object to find 
out what it should draw. The only thing the list really keeps track of is how 
many items it's displaying and which item the user has selected.

The GIGI_selection and GIGI_numSelections fields keep track of the user's 
selection, that is the list item which the user has most recently clicked upon. 
The "GIGI" prefix stands for GenItemGroup Instance. 
GenDynamicListClass takes advantage of its superclass' instance fields to 
store its collection.

The GIGI_destination line determines which object in our application will be 
responsible for telling the list what to draw when the list needs to redraw a 
given item. Our process object is somewhat unusual in that it is 
automatically created for us. The "process" keyword will automatically be 
filled in with the object pointer of our process object. 

The GDLI_numItems field keeps track of how many items we have in our 
dynamic list. We start with one such item, our "Data:" item.

The GDLI_queryMsg field determines what message MCDataList will send 
when it needs to know how to draw one of its items. It will send this message, 
MSG_MCP_SET_DATA_ITEM_MONIKER to our process object. You may recall 
that we set MSG_MCP_SET_DATA_ITEM_MONIKER up to take pass and 
return values according to a message prototype; we did this so that it would 
be able to fill the role of a dynamic list's query message.

We finish our object declaration with a hint: 
HINT_ITEM_GROUP_SCROLLABLE. The Generic UI uses hints to keep track 
of facets of a gadget's behavior which may or may not be supported under 
various UIs. We are providing a hint that our list should be allowed to scroll. 
Under the Open Look specific UI, we get a scrolling list. Under a specific UI 
which did not support scrolling lists, we wouldn't. From its name, we can 
determine that this hint is defined by GenItemGroupClass (hints are a 
Generic UI mechanism, thus the names of hints normally omit the GEN from 
the class name).

For more general information about objects used to display lists, see "The 
List Objects," Chapter 11 of the Object Reference Book.

@visMoniker FakeItemMoniker = "Data:";
@localize "This string will appear at the head of the list";

Here we declare a visual moniker which we will use for the "Data:" item in 
our dynamic list. Though this list item will be a generic object, it is not 
explicitly defined here; it will be created and managed automatically by 
MCDataList. We will set the item's moniker by hand later on; we will use 
this visual moniker then.

The @localize directive provides some information about the moniker. If we 
decide to translate our application to a foreign language, the text of this 
@localize directive will be presented to the translator when they use the 
ResEdit translation utility, providing some contextual information.

@object GenTriggerClass MCAddTrigger = {
	GI_visMoniker = "Add";
	GTI_destination = process;
	GTI_actionMsg = MSG_MCP_INSERT_DATA_ITEM;
} 
@object GenTriggerClass MCChangeTrigger = {
	GI_visMoniker = "Change";
	GTI_destination = process;
	GTI_actionMsg = MSG_MCP_SET_DATA_ITEM;
} 
@object GenTriggerClass MCDeleteTrigger = {
	GI_visMoniker = "Delete";
	GTI_destination = process;
	GTI_actionMsg = MSG_MCP_DELETE_DATA_ITEM;
}

You may recall from the previous chapter that GenTrigger objects manifest 
themselves as buttons in the Open Look specific UI. We are explicitly 
declaring three GenTrigger objects here, which will appear as our "Add," 
"Change," and "Delete" buttons. These objects are called triggers because the 
user will use them to trigger actions. In each case, this action will consist of 
the sending of a message. The message in the GTI_actionMsg field will be 
sent to the object specified in the GTI_destination field. All of these triggers 
send their message to the process object-MCAddTrigger sends 
MSG_MCP_INSERT_DATA_ITEM when activated; MCChangeTrigger sends 
MSG_MCP_SET_DATA_ITEM; and MCDeleteTrigger sends 
MSG_MCP_DELETE_DATA_ITEM.

For more information about trigger objects, see "GenTrigger," Chapter 5 of 
the Object Reference Book.

Here we see another way to declare the visual moniker for a generic object: 
we're just providing a simple string for each object.

@object GenValueClass MCValue = {
	GVLI_minimum = MakeWWFixed(0);
	GVLI_maximum = MakeWWFixed(0x7fff);
	GVLI_value = MakeWWFixed(123);
} 

MCValue allows the user to specify the value of each item in the data list. 
We're setting the minimum and maximum values it will allow the user to 
enter, as well as the number it will begin with.

The GenValue class will store the user's chosen number using a WWFixed 
number. This is a 32-bit length signed number with 16 bits representing the 
integer portion and 16 bits representing the fraction. We will only be 
interested in the integer portion of this number, and will only accept positive 
numbers.

Note that the GenValue's instance fields have the prefix "GVLI," an exception 
to the usual way of constructing the prefix for the names of instance data 
fields. GenValue uses this prefix instead of "GVI" to differentiate it from the 
GenView, which we have yet to encounter but will eventually.

The MakeWWFixed() macro will construct WWFixed structures 
automatically.

For more information about GenValue objects, see "GenValue," Chapter 8 of 
the Object Reference Book.

	4.6	MCHRT.GOC: Procedural Code

The last part of our additions is procedural code which we will use to manage 
our data structures and coordinate updates between objects.

@method MCProcessClass, MSG_MCP_SET_DATA_ITEM_MONIKER {

We're about to give the procedural code by which objects of class 
MCProcessClass will handle the message 
MSG_MCP_SET_DATA_ITEM_MONIKER. We announce this by means of the 
@method keyword, and specify the class whose behavior we're describing 
and the message which the code will be called in response to.

Our process object will receive a MSG_MCP_SET_DATA_ITEM_MONIKER 
when the list object needs to know how to draw one of its items. We are 
supposed to reply to the message by sending a message back which will 
supply a moniker to draw.

	if (item==FAKE_LIST_ITEM) {
		optr moniker;
		moniker = ConstructOptr(OptrToHandle(list),
				OptrToChunk(@FakeItemMoniker));
		@send list::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_MONIKER_OPTR(
				FAKE_LIST_ITEM, moniker);}


We begin by handling the special case in which the list is asking for the 
moniker of the "Data:" item. The if statement checks to see if we are dealing 
with this case by comparing the "item" argument.

We will identify the new moniker by means of an optr, or object pointer. You 
may recall that an optr is used as a unique identifier for an object. It can also 
be used to identify an object, loose moniker, or loose chunk in an object or 
local memory block. In this case, we're going to use the moniker optr to 
reference the FakeItemMoniker moniker.

We construct the optr by using the ConstructOptr() macro. This allows us 
to construct the object pointer corresponding to a memory block and a chunk. 
We know that FakeItemMoniker will be in the same object memory block 
as our dynamic list since they are in the same resource. We get the chunk 
part of our constructed optr from the chunk part of @FakeItemMoniker, 
which is itself an optr. 

At this point you may be wondering why we're going to all of this trouble to 
construct an optr to reference FakeItemMoniker if @FakeItemMoniker 
is an optr. In fact, there is no good reason now. This whole block of code could 
have read

if (item==FAKE_LIST_ITEM) {
 	@send 
list::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_MONIKER_OPTR(
		 FAKE_LIST_ITEM, @FakeItemMoniker);

The only reason we went to all of this trouble of constructing an object pointer 
is in expectation of a time when we support multiple documents. As it 
happens, we're going to end up duplicating the resource containing our 
dynamic list, with one duplicate for each document we have open. Then we 
won't be able to refer to @FakeItemMoniker, because GEOS won't be sure 
of which one of the duplicate @FakeItemMonikers we want. By 
constructing the optr in this way, we're ready for this case.

The @send statement sends a message. Specifically, we are sending 
MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_MONIKER_OPTR to 
MCDataList, whose optr we have from the list argument to our method.

To find out more about 
MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_MONIKER_OPTR, see "The List 
Objects," Chapter 11 of the Object Reference Book or its entry in 
\PCGEOS\INCLUDE\OBJECTS\GDListC.GOH:

@message void \
MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_MONIKER_OPTR(
	word item, optr moniker);

The arguments we're passing with the message specify that we are setting 
the moniker of the zeroth item of the list and supply the moniker to use.

	else /* item > FAKE_LIST_ITEM */ {
		char monikerString[LOCAL_DISTANCE_BUFFER_SIZE];
		word data;

		data = MCListGetDataItem(item);

		LocalFixedToAscii(monikerString, MakeWWFixed(data), 0);
		@call list::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_TEXT(
						 item, monikerString);
	}
} /* end of MSG_MCP_SET_DATA_ITEM_MONIKER */

Next we handle the case in which we will supply a moniker for the string 
based upon a number stored in our linked list.

After setting up some local variables, we call MCListGetDataItem(), a 
routine which we have set up elsewhere in the file to extract the appropriate 
number from the linked list.

Next we call LocalFixedToAscii(), a system routine which takes a 
WWFixed number and constructs an ASCII string describing that number. 
From the name of the routine we know that it is from the localization part of 
the kernel, and that it has been set up to work well in other countries (i.e. 
with other DOS code pages and foreign alphabets). After calling this routine, 
monikerString will be filled with an appropriate string.

To find out about LocalFixedToAscii(), see "Localization," Chapter 8 of the 
Concepts book, its entry in the Routines reference, and/or the file 
\PCGEOS\INCLUDE\Localize.h which contains the header:

extern void _pascal LocalFixedToAscii(
	char *buffer,
	WWFixedAsDWord value,
	word fracDig);

The final thing we do when handling this message is send a 
MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_TEXT message to the 
MCDataList object. This message is similar to the 
MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_MONIKER_OPTR that we used 
above, but takes a string instead of the optr of a visual moniker. 

Note that we send the message using the @call keyword instead of @send. 
When you use @call, the present handler won't continue until the @call'd 
message finishes processing. When you need to get a return value back from 
the message, you must use @call-if you use @send, the sent message will 
just be placed on the recipient object's queue, and your code will just continue 
on, though the recipient object may not get around to processing the message 
for quite a while.

Here we use an @call because we're passing a pointer to some data stored in 
our handler's local variable. If we were to @send the message instead, our 
handler might have finished before the data list got around to handling the 
message. At that time it would try to use monikerString, which would no 
longer point to anything useful. Using @call ensures that our local variables 
will still be there at the vital time.

extern word _pascal MCListGetDataItem(word ordinal) {
	word data;

Next we'll provide the code for the MCListGetDataItem() routine. We begin 
with a fairly ordinary C header for the routine.

	VMLock(dataFile, dataFileBlock, dataListBlockPtr);

Next we lock a block of our VM file into memory. This is the block which we 
are using to store our linked list. The VMLock() routine updates 
dataListBlock (the MemHandle pointed to by dataListBlockPtr) so that 
it will act as a handle to the block of memory as stored on the heap. For more 
information about VMLock(), see "Virtual Memory," Chapter 18 of the 
Concepts book, its entry in the appropriate reference book, and/or its entry 
in \PCGEOS\INCLUDE\VM.h:

extern void * _pascal VMLock(					VMFileHandle file,
	 				VMBlockHandle block, 
	 				MemHandle *mh);

Now that our block of memory is locked into place on the heap, we'll be able 
to use pointers to it. We have previously set up dataListHead to be a 
ChunkHandle which references the first item in our list (we will see this in 
a later message handler).

	for(tempNode = LMemDerefHandles(dataListBlock, dataListHead);
			 ordinal > 1;
			 --ordinal)
	 {
		tempListItem = tempNode->LN_next;
		tempNode = LMemDerefHandles(dataListBlock, tempListItem);
	 }

Here we traverse the linked list until we reach the Nth item, where N is 
specified by the ordinal argument to MCListGetDataItem().

The LMemDerefHandles() macro takes a memory block handle and a 
chunk handle and returns a pointer to the referenced chunk. We use this to 
access the ListNode structure within each chunk. For more information 
about this and other local memory routines and macros, see the Concepts 
manual and the appropriate reference.

	data = tempNode->LN_data;
	VMUnlock(dataListBlock);
	return data;
} 

Having procured a pointer to the structure containing the number we want, 
we retrieve its data.

Next we unlock the VM block by means of the VMUnlock() routine. At this 
point, the memory manager may move or discard the copy that was brought 
into memory. Because the memory manager may move or discard the block, 
all pointers to the block are now unreliable.

We must call VMUnlock()-the block must be unlocked once for every time 
it's locked. Also it's a bad idea to leave blocks locked on the heap-it would 
hinder the memory manager. 

The fact that we have to unlock the block before exiting the handler explains 
why we have to store our return value in a variable as we did instead of just 
returning the value directly from the pointed-at structure:

VMUnlock(dataListBlock);
return tempNode->LN_data;

After we call VMUnlock(), the memory manager is free to move or discard 
our memory block, and we can't rely on pointers to structures within that 
block. Thus we had to extract the data before unlocking the block.

@method MCListInsertDataItem, MCProcessClass, MSG_MCP_INSERT_DATA_ITEM {

This method header is slightly different than the header for 
MSG_MCP_SET_DATA_ITEM_MONIKER in that we're including a routine 
name: MCListInsertDataItem(). This isn't especially useful, but it came in 
handy for an intermediate stage of the construction of this application. When 
first testing the linked list code, no triggers had been added yet. Instead, the 
application just added some items automatically when starting up by calling 
this method as a routine. 

This message handler was set up so that it could be called as a routine from 
other parts of the program. There was a routine prototype:

extern void _pascal MCListInsertDataItem(
				optr oself,
				MCProcessMessages message)

The oself and message arguments are arguments that all methods expect, 
but since our method didn't use these parameters, we were safe in passing 
NULL and zero when calling the routine:

MCListInsertDataItem(NULL, 0);

Our application doesn't ever call this message as a routine any more, so we 
should probably remove the routine name to make this header more 
consistent with the others. Actually, we only left it in as an excuse to 
demonstrate how you might call a message handler as a routine.

	ChunkHandle 		newListItem;
	ListNode 		*newNode;
	WWFixedAsDWord		value;
	word 		ordinal;

The WWFixedAsDWord structure is a 32-bit field which contains 
the same information as a WWFixed structure: a number with 16 
bits of integer and 16 bits of fraction.

	ordinal = @call MCDataList::MSG_GEN_ITEM_GROUP_GET_SELECTION();

The first thing we're doing in this message handler is send another message. 
We're using the @call keyword to send the message because we need 
MSG_GEN_ITEM_GROUP_GET_SELECTION's return value.

We are sending this message to ask MCDataList where in the list the user 
wants to insert the new item. We will insert the new item below the item 
where the user has most recently clicked (the current selection). 
MSG_GEN_ITEM_GROUP_GET_SELECTION will return the number of the 
item most recently clicked upon. This value is zero-based. To learn more 
about this message, see its entry in the Objects reference or in 
\PCGEOS\INCLUDE\GItemGC.GOH.

Note that while using "MCDataList" as the recipient of our message is easy, 
it's also a shortcut which forces us to be single-launchable instead of 
multi-launchable. If the user were allowed to have two copies of the 
application running at once, then it wouldn't be clear which application's 
MCDataList object we should send the message to. We would have to call a 
special kernel routine to get the handle of the memory block containing the 
correct resource.

	value = @call MCValue::MSG_GEN_VALUE_GET_VALUE();

Next we use @call to send another message. Again we're using @call because 
we need a return value. This time that return value will be the number that 
the user wants to use as the data for the new data item.

	VMLock(dataFile, dataFileBlock, dataListBlockPtr);
	newListItem = LMemAlloc(dataListBlock, sizeof(ListNode));

After locking down the VM block as we did in MCListGetDataItem(), we 
use LMemAlloc() which allocates a new chunk in our memory block. To 
learn more about the LMemAlloc() routine, see its entry in the Concepts 
book or the Routines reference.

	newNode = LMemDerefHandles(dataListBlock, newListItem);
	newNode->LN_data = WWFixedToInt(value);

Having dereferenced the new chunk's handle, we use the pointer so that we 
can access its LN_data field and fill it in with a number.

	if (ordinal==FAKE_LIST_ITEM)
	 {
		newNode->LN_next = dataListHead;
		dataListHead = newListItem;
	 }

If we're inserting the new item at the head of the list, we only have to update 
the dataListHead global variable and the new node's LN_next field.

	else
		 {
		word count = ordinal;
		for (tempNode = LMemDerefHandles(dataListBlock, dataListHead);
			 count > 1;
			 --count)
		 {
			tempListItem = tempNode->LN_next;
			tempNode = LMemDerefHandles(dataListBlock, tempListItem);
		 }
		newNode->LN_next = tempNode->LN_next;
		tempNode->LN_next = newListItem; 
	 }

Otherwise, we'll traverse the list as before and then insert the new item by 
filling in the LN_next fields of the appropriate nodes.

	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);
	@send MCDataList::MSG_GEN_DYNAMIC_LIST_ADD_ITEMS(ordinal+1, 1);
	@send MCDataList::MSG_GEN_ITEM_GROUP_SET_SINGLE_SELECTION(ordinal+1, 
									FALSE);

} 

Having updated the linked list structure, we alert the GenDynamicList 
that it has gained another item by sending it 
MSG_GEN_DYNAMIC_LIST_ADD_ITEMS, alerting it that we are adding one 
item and telling it where that item appears in the list. 

Next we will change the selection, so that the new item of the list will be 
highlighted. To do this we send MCDataList a 
MSG_GEN_ITEM_GROUP_SET_SINGLE_SELECTION message.

@method MCProcessClass, MSG_MCP_DELETE_DATA_ITEM {
	word 		ordinal;
	word 		count;
	ChunkHandle 		oldItem;
	ListNode		*oldNode;

	ordinal = @call MCDataList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
	if (ordinal==FAKE_LIST_ITEM) return;
	VMLock(dataFile, dataFileBlock, dataListBlockPtr);
	if (ordinal == 1) {
		oldNode = LMemDerefHandles(dataListBlock, dataListHead);
		tempListItem = oldNode->LN_next;
		LMemFreeHandles(dataListBlock, dataListHead);
		dataListHead = tempListItem;
	 }

By this time, you can probably understand most of what this message 
handler is doing. We know that objects of class MCProcessClass will use 
this code to handle MSG_MCP_DELETE_DATA_ITEM messages from the 
header line. You can recognize the @call as a special way to send messages. 
You are familiar with VMLock() and LMemDerefHandles().

In fact the only thing new here is the LMemFreeHandles() macro, which 
will free up the memory which was associated with the chunk which we are 
deleting.

	else {
		for (tempNode=LMemDerefHandles(dataListBlock, dataListHead),
			 count= ordinal;
			 count > 2;
			 --count)
		 {
			tempListItem = tempNode->LN_next;
			tempNode = LMemDerefHandles(dataListBlock, tempListItem);
		 }
		oldItem = tempNode->LN_next;
		oldNode = LMemDerefHandles(dataListBlock, oldItem);
		tempNode->LN_next = oldNode->LN_next;
		LMemFreeHandles(dataListBlock, oldItem);
	 }
	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);

Again, you should be able to figure out what is going on here. We are 
traversing the list, then removing an node from the list by causing the 
previous node's LN_next handle to reference the node after the 
soon-to-be-deleted node. Again, we use the LMemFreeHandles() macro to 
free up the memory we had allocated for the chunk.

	@send MCDataList::MSG_GEN_DYNAMIC_LIST_REMOVE_ITEMS(ordinal, 1);
	@send MCDataList::MSG_GEN_ITEM_GROUP_SET_SINGLE_SELECTION(ordinal-1, 
									FALSE);
} 

We finish the handler by alerting MCDataList that it has lost an item and 
then tell it to update its selection.

@method MCProcessClass, MSG_MCP_SET_DATA_ITEM {
	word 		ordinal;
	WWFixedAsDWord 		value;
	char 		monikerString[LOCAL_DISTANCE_BUFFER_SIZE];
	word 		count;

	ordinal = @call MCDataList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
	if (ordinal == FAKE_LIST_ITEM) return;
	value = @call MCValue::MSG_GEN_VALUE_GET_VALUE();
	VMLock(dataFile, dataFileBlock, dataListBlockPtr);
	for (tempNode = LMemDerefHandles(dataListBlock, dataListHead),
		 count = ordinal - 1;
		 count > 0;
		 --count)
	 {
		tempListItem = tempNode->LN_next;
		tempNode = LMemDerefHandles(dataListBlock, tempListItem);
	 }
	tempNode->LN_data = WWFixedToInt(value);
	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);
	LocalFixedToAscii(monikerString, value, 0);
	@call MCDataList::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_TEXT(
							ordinal, monikerString);
} 

There is probably nothing in this method that you do not recognize from 
others we have looked at in this chapter. Perhaps the only thing worth 
pointing out here is that we had to alert MCDataList that one of its items 
had changed, and that we did so using 
MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_TEXT.

@method MCProcessClass, MSG_GEN_PROCESS_OPEN_APPLICATION { 

If you look back to our class definition, you may note that there is no 
MSG_GEN_PROCESS_OPEN_APPLICATION. As you may have guessed from 
the message's name, MCProcessClass inherits this message from 
GenProcessClass. The definition of this message is in 
INCLUDE\GPROCC.GOH:

@message void MSG_GEN_PROCESS_OPEN_APPLICATION(
			AppAttachFlags			attachFlags,
			MemHandle			launchBlock,
			MemHandle			extraState);

As it happens, we won't be using the parameters to this message, but you 
may find it reassuring to know that they have been defined somewhere.

Our process object will automatically receive this message when the 
application is opening, and we wish to customize our behavior in handling 
this message so that we can initialize our data structures.

Handling a message defined by a superclass in this manner is often referred 
to as "intercepting" the message.

	char fileName[] = "MchtDATA"
	dataFile=VMOpen(fileName, 
			VMAF_FORCE_READ_WRITE | VMAF_FORCE_DENY_WRITE,
			VMO_CREATE_TRUNCATE, 0);

Since our data structure will reside within a VM file, the first thing we must 
do is initialize the file. The VMOpen() routine will open the file, creating the 
file if it did not already exist. We are passing the VM access flags 
VMAF_FORCE_READ_WRITE and VMAF_FORCE_DENY_WRITE so that we 
will be able to read and write to the file while we have it open and to make 
sure that nothing else will try to change the file while we're working with it. 
The VMO_CREATE_TRUNCATE option says that we want to create the file if 
it doesn't exist yet, and also that we want its contents emptied-we don't 
want to preserve the contents of the file from the last time we saved. 

Actually, it should strike you as a bit strange that we aren't preserving the 
values in the VM file. These files are normally used for the express purpose 
of saving documents. Eventually we will want to be able to save our data, but 
in the early stages of constructing this program, our program crashed often, 
leaving behind a corrupt VM file. By truncating our file when opening it, we 
safely ignore the contents of the VM file. Eventually, we should probably stop 
erasing the file's contents so that this file can save something.

	dataFileBlock = VMAllocLMem(dataFile, LMEM_TYPE_GENERAL, 0);

The VMAllocLMem() routine allocates a VM block within our VM file and 
initializes it so that it can act as a local memory block. Local memory is a very 
useful mechanism, and there are several types of specialized memory heaps 
available. By passing LMEM_TYPE_GENERAL we signal that we want the 
most general type of local memory block, storing just chunks of data. 

The final argument to VMAllocLMem() allows us to specify an area at the 
start of the block to act as a header. Since we don't have any header 
information to store, we pass zero here.

	@callsuper();
} 

This @callsuper() statement may appear rather mysterious. You recall that 
MCProcessClass inherits MSG_GEN_PROCESS_OPEN_APPLICATION from 
GenProcessClass. However, we don't know exactly how GenProcessClass 
handles this message. But we do want to make sure that GenProcessClass' 
message handler for this message is called eventually; it probably does 
something important. The @callsuper() statement calls the message 
handler of our superclass; in this case, it will invoke GenProcessClass' 
handler. Note that our arguments will automatically be passed on to the 
superclass' handler. 

@method MCProcessClass, MSG_GEN_PROCESS_CLOSE_APPLICATION {
	@send MCDataList::MSG_GEN_DYNAMIC_LIST_INITIALIZE(1);
	VMClose(dataFile, FALSE);
	@callsuper();
}

We are changing the way our application handles another GenProcess 
message, namely MSG_GEN_PROCESS_CLOSE_APPLICATION. Our process 
object will automatically receive this message when the application is 
shutting down. Looking in INCLUDE\GPROCC.GOH, we can find out this 
message's parameters:

@message MemHandle MSG_GEN_PROCESS_CLOSE_APPLICATION();

First we send a MSG_GEN_DYNAMIC_LIST_INITIALIZE message to 
MCDataList. Since we will be tossing out our list of data when we next open 
the application, we are sending this message so that the list will go back to 
having just one item.

Next we close the VM file. We use the VMClose() routine, specifying that we 
will ignore any errors in closing our file-since we're going to truncate the file 
when we re-open the file, we probably don't care that much about errors now.

Note that though MSG_GEN_PROCESS_CLOSE_APPLICATION is supposed to 
return a MemHandle, we don't have a return statement. Depending on the 
compiler, we may return the value of the @callsuper automatically, just 
because it was the last statement in our handler. However, if we weren't sure 
about the compiler, or if the @callsuper() were not to be the last statement, 
then we would want an explicit return statement:

MemHandle retHandle;
-
retHandle = @callsuper();
return retHandle;

	4.7	Swat: Breakpoints and More

Now that our application has some procedural code, we'll be able to use Swat 
for some of the more traditional uses of a debugger: setting breakpoints and 
stepping through code.

		If You Are Using Windows

Swat should already be attached. Exit the old version your application if you 
are running it. Use the run command to run your new application. The next 
page of documentation doesn't really apply to you; skip down to the heading 
that says "When Swat is Ready".

(If Swat isn't attached, start it by double-clicking on the Swat program item 
in the GEOS SDK program group.)

		If You Are Using Just the DOS Prompt

Invoke Swat as before: have the pccom tool running from the GEOSEC 
directory on the target machine and type swat -r at the DOS prompt on the 
host machine.

Swat version 2.0 (Jan 20 1993 21:06:13).
Using the trunk version of PC/GEOS.
Looking for "loader"...C:\PCGEOS/Loader/LOADEREC.EXE
Sourcing swat.tcl...done
PC Attached
Stopped in LoadGeos, address 0c6ah:0007h
LoadGeos: CLD ;DF=0
(loader:0) 1 => 

As it did before, Swat halts while GEOS is still loading on the host machine 
to give us a chance to enter any early commands. We don't have any special 
commands to enter, so we will continue.

(loader:0) 1 => c
Looking for "geos 					Eker"...C:/PCGEOS/Library/Kernel/geosec.geo
Looking for "ms4 					Eifs"...C:/PCGEOS/Driver/IFS/DOS/MS4/ms4ec.geo
Thread 1 created for patient geos
Thread 2 created for patient geos
Looking for "vidmem 					Edrv"...C:/PCGEOS/Driver/Video/Dumb/VidMem/vidmemec.geo
Looking for "swap 					Elib"...C:/PCGEOS/Library/Swap/swapec.geo
Looking for "xms 					Edrv"...C:/PCGEOS/Driver/Swap/XMS/xmsec.geo
Looking for "disk 					Edrv"...C:/PCGEOS/Driver/Swap/Disk/diskec.geo
Looking for "kbd 					drvr"...C:/PCGEOS/Driver/Keyboard/kbd.geo
Looking for "nimbus 					Edrv"...C:/PCGEOS/Driver/Font/Nimbus/nimbusec.geo
Looking for "stream 					Edrv"...C:\PCGEOS/Driver/Stream/streamec.GEO
Looking for "sound 					Elib"...C:/PCGEOS/Library/Sound/soundec.geo
Looking for "standard					Edrv"...C:/PCGEOS/Driver/Sound/Standard/standard.geo
Looking for "ui 					Elib"...C:/PCGEOS/Library/User/uiec.geo
Thread 0 created for patient ui
Looking for "styles 					Elib"...C:\PCGEOS/Library/Styles/stylesec.GEO
Looking for "color 					Elib"...C:\PCGEOS/Library/Color/colorec.GEO
Looking for "ruler 					Elib"...C:\PCGEOS/Library/Ruler/rulerec.GEO
Looking for "text 					Elib"...C:/PCGEOS/Library/Text/textec.geo
Looking for "motif 					Espu"...C:\PCGEOS/Library/Motif/motifec.GEO
Looking for "vga 					Edrv"...C:/PCGEOS/Driver/Video/VGAlike/VGA/vgaec.geo
Looking for "nonts 					Edrv"...C:/PCGEOS/Driver/Task/NonTS/nontsec.geo
Looking for "spool 					Elib"...C:\PCGEOS/Library/Spool/spoolec.GEO
Thread 0 created for patient spool
Looking for "serial 					Edrv"...C:/PCGEOS/Driver/Stream/Serial/serialec.geo
Looking for "msSer 					Edrv"...C:/PCGEOS/Driver/Mouse/MSSer/msserec.geo
Looking for "welcome					Eapp"...C:/PCGEOS/Appl/Startup/Welcome/welcomee.geo
Thread 0 created for patient welcome

When faced with the Welcome screen on the target, click on the "Advanced" 
trigger, and start up our application, which should be in the OTHER directory.

Looking for "shell 					Elib"...C:/PCGEOS/Library/Shell/shellec.geo
Looking for "manager				Eapp"...C:/PCGEOS/Appl/FileMgrs/GeoManag/managere.geo
Thread 0 created for patient manager
Thread 1 created for patient manager
Looking for "math 					Elib"...C:\PCGEOS/Library/Math/mathec.GEO
Looking for "borlandcE					lib"...C:\PCGEOS/Library/MATH/COMPILER/BORLANDC/BORLANDC.GEO
Looking for "mchrt 					Eapp"...C:\PCGEOS/Appl/Mchrt/mchrtec.GEO
Thread 0 created for patient mchrt
Thread 1 created for patient mchrt

When the application has started up, hit ctrl-c on the host machine to 
interrupt Swat and halt execution on the target machine.

		When Swat Is Ready

PC Halted
Stopped in DOSIdleHook, address 2522h:109dh
DOSIdleHook+16: INT 40 (28h)
(geos:0) 2 => 

We're going to set a breakpoint in our code. Before we do this, we must switch 
to our application's thread so that Swat will recognize our application's 
labels. To do this, we enter the name of our application's thread: mchrt. 

(geos:0) 3 => mchrt
[mchrt:0] 4

Next we will set a breakpoint in the MCListGetDataItem() routine by 
using the stop in Swat command:

[mchrt:0] 4 => stop in MCListGetDataItem
brk5
[mchrt:0] 5 => 

We've just set the breakpoint. Swat should now halt whenever this routine is 
called on the target machine. Note that we get a return value, "brk5." This 
means that this is our fifth breakpoint, and we will be able to manipulate this 
breakpoint later by referring to its number. Note that the other four 
breakpoints have been automatically set up by Swat. We will get a glimpse 
of them later.

To test the breakpoint, continue by typing c on the host machine, then click 
on the "Add" trigger on the target machine.

[mchrt:0] 5 => c
Breakpoint 5
Stopped in MCLISTGETDATAITEM, line 196, "C:\PCGEOS/Appl/Mchrt/MCHRT.GOC"
 VMLock(dataFile, dataFileBlock, dataListBlockPtr);
(mchrt:0) 6 => 

Note that Swat tells us we broke on Breakpoint 5. If we had set several 
breakpoints, this information might have come in handy by letting us know 
which one we had stopped for.

Next, we'll use the srcwin command to display some of our source code 
(fifteen lines of it, in this case).

(mchrt:0) 6 => srcwin 15
(mchrt:0) 7 =>

At this point, the bottom fifteen lines of the display should be taken up by a 
section of program listing. One line should be highlighted; this is the line 
which is about to be executed on the target machine. Now we can get a clearer 
picture of our context within the program. On the left hand side of the srcwin 
window, you can see the line numbers of the lines of source code. These can 
come in handy; if compilation generated warnings on a given line, these 
numbers can help you find it.

To see more of our program, you can use the Page Up, Page Down, 
left arrow, and right arrow keys to scroll around the program file within 
the srcwin window. You can even set breakpoints by clicking on the line 
numbers with the mouse. To turn off a breakpoint set this way, just click on 
the line number again.

Next we will use the source-stepper command sstep to step through our 
source code. (You might think that sstep stands for "single-step," but it in fact 
stands for "source-step," as opposed to our assembly-code stepper.) Each time 
we step, it will present us with a line of code. We can ask to go to the next 
step, to step into a routine being called, or finish the routine.

(mchrt:0) 7 => sstep
Stepping in C:\PCGEOS/Appl/Mchrt/MCHRT.GOC...
 196: VMLock(dataFile, dataFileBlock, dataListBlockPtr);

We are about to execute the VMLock() command. Press the n key.

 198: for(tempNode = LMemDerefHandles(dataListBlock, dataListHead);

Note that sstep presents us with a new line and now this line is highlighted 
within srcwin's window. At this point, the VMLock() has executed and we're 
about to do the initialization step of the for() statement.

Let's continue for another couple of steps and then get out of sstep mode. 
Press n twice and then press q. This will move us another two lines forward 
in the code and then quit out of sstep mode.

 199: ordinal > 1;
 205: data = tempNode->LN_data;
 206: VMUnlock(dataListBlock);
(mchrt:0) 8 =>

Let's fool our application. We can use the assign command to change any of 
our application's variables that are within our present scope. This can be 
useful for testing your handlers in strange cases which might be difficult to 
reproduce. In this case, we'll use it to change the value we're returning.

(mchrt:0) 8 => assign data 234
(mchrt:0) 9 => c

Now look over at the target machine. There should be a new item in the 
scrolling list, and it should be 234. Thus, we know we can use Swat to fool our 
program about what's going on. 

To get a list of the variables local to a routine, use the locals command. To 
get a continuous display of local variables, use the localwin command (try 
"localwin 5") You can use the print command to learn the value of a given 
variable. Note that if a variable is stored in a register, you must pass the 
register name to assign instead of the variable name. The locals command 
will warn you when a given variable is stored in a register.

Next, let's see some more navigation of our program. On the target machine, 
click on the "Add" trigger again.

Breakpoint 5
Stopped in MCLISTGETDATAITEM, line 196, "C:\PCGEOS/Appl/Mchrt/MCHRT.GOC"
 VMLock(dataFile, dataFileBlock, dataListBlockPtr);
(mchrt:0) 10 =>

Again we're stopped at breakpoint 5. The srcwin window shows us where we 
are within our source code. In a more complicated program, it might be 
somewhat difficult to determine how we got here. What if several routines 
and handlers called this routine? How would we know which of them had 
invoked it? Use the where command to get a simple backtrace.

(mchrt:0) 10 => where
* 1: far MCLISTGETDATAITEM(), MCHRT.GOC:193
  2: far MCPROCESSMCP_SET_DATA_ITEM_MONIKER(list = 1e10h:0020h), MCHRT.GOC:176
  4: call mchrt0:0::MSG_MCP_SET_DATA_ITEM_MONIKER (1e10h 0020h 0002h) (@2, ^l3
c50h:0000h)
------------------------------------------------------------------------------
The event queue for "mchrt:0" is empty
==============================================================================
(mchrt:0) 11 => 

From the first line, we can see that this thread is executing 
MCListGetDataItem(). The next line tells us that it is doing so to handle 
MCProcessMCP_SET_DATA_ITEM_MONIKER(). Since this is the name 
of the routine Goc creates to refer to MCProcessClass' handler for 
MSG_MCP_SET_DATA_ITEM_MONIKER, and we know that handler calls 
MCListGetDataItem(), this makes sense.

The next several lines of output tell us which routines and message handlers 
caused MCProcessMCP_SET_DATA_ITEM_MONIKER() to be called.

The asterisk at the head of the first line lets us know that Swat is examining 
MCListGetDataItem() now. To examine another level, use the up 
command.

(mchrt:0) 11 => up
MCPROCESSMCP_SET_DATA_ITEM_MONIKER+71: NOP
(mchrt:0) 12 => 

At this point, the srcwin window should update so that it is displaying our 
handler for MSG_MCP_SET_DATA_ITEM_MONIKER. Note that up doesn't 
change which code the target is executing, just which level swat is looking at. 
When we use the where command again, we can see that the asterisk has 
moved to the second line:

(mchrt:0) 12 => where
  1: far MCLISTGETDATAITEM(), MCHRT.GOC:193
* 2: far MCPROCESSMCP_SET_DATA_ITEM_MONIKER(list = 1e10h:0020h), MCHRT.GOC:176
  4: call mchrt0:0::MSG_MCP_SET_DATA_ITEM_MONIKER (1e10h 0020h 0002h) (@3, ^l3
c50h:0000h)
------------------------------------------------------------------------------
The event queue for "mchrt:0" is empty
==============================================================================
(mchrt:0) 13 => 

There is a corresponding down command to go down a level of execution. For 
now, let's see how to navigate between levels when using sstep. Type sstep 
at the Swat prompt. Press the n key twice.

(mchrt:0) 13 => sstep 
Stepping in C:\PCGEOS/Appl/Mchrt/MCHRT.GOC...
 196: VMLock(dataFile, dataFileBlock, dataListBlockPtr);
 198: for(tempNode = LMemDerefHandles(dataListBlock, dataListHead);
 199: ordinal > 1;

The first thing that happened when we invoked sstep was that Swat 
returned to MCListGetDataItem(). Remember that sstep steps through 
code. Right now the code that we're executing is in MCListGetDataItem(). 
However, perhaps you'd like to finish up this routine to see what happens in 
MCProcessMCP_SET_DATA_ITEM_MONIKER() after 
MCListGetDataItem() is done. To continue to the finish of the present 
routine and continue stepping at the next higher execution level, press f for 
finish. Note that there is also a finish command which you can enter from 
the regular Swat prompt that does the same thing.

 176: data = MCListGetDataItem(item);

Now press n three more times to finish up the handler (or press f to finish it).

 178: LocalFixedToAscii(monikerString, MakeWWFixed(data), 0);
 180: item, monikerString);
 182:}
No source available for 15e8h:9e55h...
CallCHandler+4: AND DI, -1024 (fc00h) ;3f5h

This time when we finished executing the handler and go up a level, our 
srcwin window goes blank and we are warned that we don't have the source 
code for the routine we're stepping through; it's the internal system routine 
which called our handler in response to the incoming message. Press q to get 
out of sstep mode.

Let's set a breakpoint in a message handler. We saw above that Goc creates 
the names of the handler routines by concatenating the first part of the class 
name with the last part of the message name. Thus, to determine the name 
of MCProcessClass' handler for MSG_MCP_DELETE_DATA_ITEM, we 
remove the -Class suffix from MCProcessClass and the MSG_- prefix from 
MSG_MCP_DELETE_DATA_ITEM and concatenate the results; the name 
should be MCProcessMCP_DELETE_DATA_ITEM(). Let's set a 
breakpoint there, by using the stop in command (if our source code was 
showing in the srcwin window, we could just click with the mouse on the 
appropriate line number):

(mchrt:0) 14 => stop in MCProcessMCP_DELETE_DATA_ITEM
brk6
(mchrt:0) 15 => 

We've successfully set a breakpoint; it is number six. To test this breakpoint, 
continue Swat and then click on the "Delete" trigger on the target machine.

(mchrt:0) 15 => c
Breakpoint 6
Stopped in MCPROCESSMCP_DELETE_DATA_ITEM, line 282, "C:\PCGEOS/Appl/Mchrt/MCHRT.
GOC"
 ordinal = @call MCDataList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
(mchrt:0) 16 => 

Now we know how to break at a message handler. Actually, we're getting a 
bit cluttered with breakpoints now. Perhaps we want to turn some of ours off. 
Use the brk list command to list the current breakpoints.

(mchrt:0) 16 => brk list
Num S 		Address 								Patient 		Command/Condition
1 	E loader::kcode::LoaderError 									all 		echo Loader death due to [penum
LoaderStrings [read-reg ax]]
 												expr 1
2 	E geos::kcode::FatalError 									all
 												why
 										assign 		kdata::errorFlag 0
 												expr 1
3 	E geos::kcode::WarningNotice 									all 		why-warning
4 	E geos::kcode::CWARNINGNOTICE 									all 		why-warning
5 	E <RT_TEXT::MCLISTGETDATAITEM+10 									all 		halt
6 	E <PROCESSMCP_DELETE_DATA_ITEM+8 									all 		halt
(mchrt:0) 17 => 

For each listed breakpoint, we can see where they break, and some Swat 
commands which will be executed when the breakpoint is hit. Our 
breakpoints just halt. Note that the first breakpoint doesn't just halt, it will 
also print out an error message whenever we enter a routine called 
LoaderError(). The second breakpoint in the list detects the case where 
execution has entered the GEOS kernel routine FatalError() and executes 
the why Swat command which examines GEOS' exiting error code and 
returns a string helpful for determining why the crash is occurring.

Breakpoints three and four won't halt execution, but will execute the 
why-warning Swat command, which will echo a warning message to the 
Swat screen.

These four breakpoints are set up by Swat automatically. The fifth and six 
breakpoints are those that we've set up. Each of these breakpoints causes 
execution to halt when it hits the specified address.

We can delete and disable these breakpoints by using the brk delete and 
brk disable commands.

(mchrt:0) 17 => brk delete 5
(mchrt:0) 18 => brk disable 6
(mchrt:0) 19 =>

If you do another brk list now, you will notice that breakpoint five has 
disappeared, and that breakpoint six has a "D" in the second column where 
it used to have an "E." Execution will not stop at that breakpoint until we 
re-enable the breakpoint by means of a brk enable.

To set a one-time breakpoint, useful for avoiding breakpoint clutter, use the 
go command. To test it out, type the go command shown below and click on 
the "Add" trigger of the target machine.

(mchrt:0) 19 => go MCListGetDataItem
Interrupt 3: Breakpoint trap
Stopped in MCLISTGETDATAITEM, line 192, "C:\PCGEOS/Appl/Mchrt/MCHRT.GOC"
extern word _pascal MCListGetDataItem(word ordinal) {
(mchrt:0) 20 => 

Suppose you found yourself typing "go MCListGetDataItem" rather often. 
That' quite a bit to type. You can ease the burden by using ctrl-b to scroll up 
to a place where you've already typed it and then cut and paste the text using 
the mouse (capture the text by click-dragging with the left mouse button; 
clicking with the right mouse button will send the captured text to the 
prompt). That might still be a lot of trouble. We can also construct an alias 
using the commands shown below, and then trigger the one-time breakpoint 
created by clicking on the "Add" trigger on the target machine.

(mchrt:0) 20 => alias gogetter {go MCListGetDataItem}
(mchrt:0) 21 => gogetter
Interrupt 3: Breakpoint trap
Stopped in MCLISTGETDATAITEM, line 192, "C:\PCGEOS/Appl/Mchrt/MCHRT.GOC"
extern word _pascal MCListGetDataItem(word ordinal) {
(mchrt:0) 22 => 

These aliases can come in rather handy for defining mini-functions for Swat. 
Later we will see how you can set up a file containing aliases and other Swat 
commands to be run automatically each time you run Swat.

So far, we've been setting breakpoints for portions of code that are rather 
accessible. However, what would we have done if there had been a bug in 
MCProcessClass' handler for MSG_GEN_PROCESS_OPEN_APPLICATION? 
Since this handler will be called only when the application is starting up, if 
there were a crashing bug in the handler, we'd never get a chance to set the 
break-point before it had crashed. To see this, continue Swat and exit the 
application on the target machine.

(mchrt:0) 22 => c
Thread 1 of mchrt exited 0
math exited.
borlandc exited.
mchrt exited.
Thread 0 of mchrt exited 0

Now hit ctrl-c and see if we can set our breakpoint.

PC Halted
Stopped in DOSIdleHook, address 2522h:109bh
DOSIdleHook+14: JLE DOSIdleHook+18 ;Will not jump
(geos:0) 23 => stop in MCProcessGEN_PROCESS_OPEN_APPLICATION
Error: procedure MCProcessGEN_PROCESS_OPEN_APPLICATION not defined
(geos:0) 24 => 

That didn't work so well, did it? The problem is that our application isn't 
loaded into memory, and Swat doesn't have access to the necessary symbolic 
information.

However, there is a solution. The spawn command will alert Swat that it 
should break as soon as possible when starting up the named program. This 
will give us a chance to set breakpoints before any of our handlers have had 
a chance to crash. Enter spawn mchrt, and then start up our application 
again on the target machine.

(geos:0) 24 => spawn mchrt
Re-using patient math
Re-using patient borlandc
Re-using patient mchrt
Thread 0 created for patient mchrt
mchrt spawned
Stopped in GeodeNotifyLibraries, address 15e8h:18d4h
GeodeNotifyLibraries: PUSH AX ;adebh
(mchrt:0) 25 => 

Swat has halted execution, and we can now set our breakpoint. Below, we see 
the effects of setting the breakpoint and continuing. Instead of continuing, 
you may wish to explore the handler.

(mchrt:0) 25 => stop in MCProcessGEN_PROCESS_OPEN_APPLICATION
brk7
(mchrt:0) 26 => c
Thread 1 created for patient mchrt
Breakpoint 7
Stopped in MCPROCESSGEN_PROCESS_OPEN_APPLICATION, line 365, 
"C:\PCGEOS/Appl/Mchrt/MCHRT.GOC"
 char fileName[] = "MChtDATA";
(mchrt:0) 27 => c

Next let's make a common mistake and see how to correct it. You don't need 
to know what objwatch is supposed to do (we'll get to that later). Just note 
that we're about to make a mistake. Hit ctrl-c on the host machine to halt 
execution and enter objwatch MCValue at the Swat prompt.

PC Halted
Stopped in DOSIdleHook, address 2522h:108dh
DOSIdleHook: PUSH BX ;4b9ch
(geos:0) 32 => objwatch MCValue
Error: MCValue undefined

We get this error because we were trying to refer to one of mchrt's labels 
("MCValue") while Swat was in the geos thread. We could fix this by 
switching threads into the mchrt thread (by typing "mchrt"). Instead, we'll 
set up the mchrt thread as our symbolic default, the thread Swat will check 
with as a last-chance attempt to identify a label. Enter sd mchrt to set up 
mchrt as the symbolic default. Enter objwatch MCValue again and notice 
that it works this time, though we're still in the geos thread. When Swat 
couldn't find a MCValue in this thread, it looked in mchrt, and thus found it.

(geos:0) 33 => sd mchrt
(geos:0) 34 => objwatch MCValue
brk8
(geos:0) 35 =>

Perhaps this would be a good time to find out what objwatch does. From its 
return value, you may have guessed correctly that it sets a breakpoint. You 
would have guessed correctly. The objwatch command monitors messages, 
and will alert you whenever it detects that the passed object is receiving a 
message. In the example here, we will be able to see all messages passed to 
MCValue, our number-entry object.

It does this by inserting a breakpoint at the kernel routine which dispatches 
messages. To see how it does this, we can take a look at the list of breakpoints 
and see what breakpoint number eight does. Enter brk list.

[mchrt:0] 35 => brk list
Num 	S Address 									Patient 			Command/Condition
1 	E loader::kcode::LoaderError 									all echo 			Loader death due to [penum
LoaderStrings [read-reg ax]]
 										expr 1
2 	E geos::kcode::FatalError 									all
 													why
 													assign kdata::errorFlag 0
 													expr 1
3 	E geos::kcode::WarningNotice 									all 			why-warning
4 	E geos::kcode::CWARNINGNOTICE 									all 			why-warning
6 	D <PROCESSMCP_DELETE_DATA_ITEM+8 									all 			halt
7 	E <EN_PROCESS_OPEN_APPLICATION+6 									all 			halt
8 	E <os::kcode::ObjCallMethodTable 									all 			si=0030h ds=4ed4h
 print-ow {1533 364 48 {1112312 9404 44}}
[mchrt:0] 37 => 

Next, we can continue Swat and click on the "Add" trigger on the target 
machine and watch messages sent to our value object. When done, hit ctrl-c.

[mchrt:0] 37 => c
MSG_GEN_VALUE_GET_VALUE, ^l48a0h:0030h, GenValueClass
 	cx = cccch, dx = cccch, bp = cccch
MSG_GEN_VALUE_GET_VALUE, ^l48a0h:0030h, GenValueClass
 	cx = cccch, dx = cccch, bp = cccch
MSG_VIS_POSITION_BRANCH, ^l48a0h:0030h, GenValueClass
 	cx = 0009h, dx = 00bdh, bp = 0612h
MSG_VIS_COMP_GET_MARGINS, ^l48a0h:0030h, GenValueClass
 	cx = 0023h, dx = 000ch, bp = 0612h
MSG_VIS_VUP_CREATE_GSTATE, ^l48a0h:0030h, GenValueClass
 	cx = 000dh, dx = 00bfh, bp = 000ch
MSG_VIS_COMP_GET_CHILD_SPACING, ^l48a0h:0030h, GenValueClass
 	cx = 0000h, dx = 00bdh, bp = 0576h
MSG_VIS_COMP_GET_MARGINS, ^l48a0h:0030h, GenValueClass
 	cx = 0005h, dx = 0000h, bp = 0576h
MSG_VIS_COMP_GET_MARGINS, ^l48a0h:0030h, GenValueClass
 	cx = 0005h, dx = 0000h, bp = 0576h
PC Halted
Stopped in Idle, address 15e8h:bab7h
Idle+31: ADD DI, 4 (04h) ;dceh
(geos:0) 38 => 

That's it for this Swat session. If you haven't already, you should probably 
take this opportunity to work with the code in the srcwin window. Use the 
Page Up, Page Down, left and right arrows to navigate the code. Click with 
the mouse on line numbers to set and remove breakpoints. 

Code Display 4-1 MCHRT.GP

name mchrt.app

longname "MyChart"

type appl, process, single

class MCProcessClass

appobj MCApp

tokenchars "MCht"
tokenid 0

library 	geos
library 	ui

resource APPRESOURCE ui-object
resource INTERFACE ui-object 



Code Display 4-2 MCHRT.GOC

/**************************************************************
 *	Copyright (c) GeoWorks 1993 -- All Rights Reserved
 *
 * MChrt is a charting application. It maintains a list of
 * numbers and constructs a bar chart to display them.
 *
 * Our process object is in charge of maintaining the data
 * structure which holds the list of numbers.
 *
 **************************************************************/

@include <stdapp.goh>

/* CONSTANTS */

/* In the list gadget which represents our data, the first item
 * isn't going to represent anything; it's just a place holder.
 * The FAKE_LIST_ITEM constant will be used when checking for this item 
 */
#define FAKE_LIST_ITEM 0

@class MCProcessClass, GenProcessClass;
/* For information about the messages listed below, see the
 * headers for their handlers, later in this file. */
	@message (GEN_DYNAMIC_LIST_QUERY_MSG) 					MSG_MCP_SET_DATA_ITEM_MONIKER;
	@message void 					MSG_MCP_DELETE_DATA_ITEM();
	@message void 					MSG_MCP_INSERT_DATA_ITEM();
	@message void					MSG_MCP_SET_DATA_ITEM();
@endc /* end of MCProcessClass definition */

@classdecl MCProcessClass, neverSaved;

/* MCListGetDataItem():
 * For information about this routine,
 * see its code, later in this file */
extern word _pascal MCListGetDataItem(word ordinal);

/* Global STRUCTURES and VARIABLES */

/* The data points which are to be charted are stored in
 * a linked list of chunks, all of which are contained within
 * a single block of memory. Each element of the list will be
 * stored in a ListNode structure. 
 */
typedef struct {
	word 		LN_data;
	ChunkHandle 		LN_next;
} ListNode;

/* A given piece of data is stored:
 *	In a ListNode							tempNode
 *	referenced by a ChunkHandle							tempListItem
 *	in a memory block referenced by a MemHandle 							dataListBlock
 *	loaded from a VM block referenced by a VMBlockHandle							dataFileBlock
 *	in a file referenced by a VMFileHandle			 				dataFile
 */

VMFileHandle 	dataFile;				/* File which will hold our data */
VMBlockHandle 	dataFileBlock;				/* Block within dataFile */
MemHandle 	dataListBlock;				/* Block of memory holding our data */
MemHandle 	*dataListBlockPtr = &dataListBlock; /* Ptr to above Handle */
ChunkHandle	dataListHead = 0;				/* Chunk containing head of
					 * linked list. */
ChunkHandle	tempListItem; 				/* Chunk handle which we will
					 * use when traversing lists. */
ListNode 	*tempNode;				/* List item which we will use
					 * when traversing lists. */

/* OBJECT Resources */
/* APPRESOURCE will hold the application object and other information
 * which the system will want to load when it wants to find out about
 * the application but doesn't need to run the application. 
 */
@start	AppResource;

@object GenApplicationClass MCApp = {
 GI_visMoniker = list { @MCTextMoniker }
 GI_comp = @MCPrimary;
 gcnList(MANUFACTURER_ID_GEOWORKS,GAGCNLT_WINDOWS) = @MCPrimary;
}

@visMoniker MCTextMoniker = "MyChart Application";

@end	AppResource;

/* The INTERFACE resource holds the bulk of our Generic UI gadgetry. */
@start	Interface;

@object GenPrimaryClass MCPrimary = {
	GI_comp = @MCDataList, @MCAddTrigger, @MCDeleteTrigger,
 		  @MCChangeTrigger, @MCValue;
}

@object GenDynamicListClass MCDataList = {
	GIGI_selection = FAKE_LIST_ITEM;
	GIGI_numSelections = 1;
	GIGI_applyMsg = 0;
	GIGI_destination = process;
	GDLI_numItems = 1;
	GDLI_queryMsg = MSG_MCP_SET_DATA_ITEM_MONIKER;
	HINT_ITEM_GROUP_SCROLLABLE;
}

@visMoniker FakeItemMoniker = "Data:";
@localize "This string will appear at the head of the list";

@object GenTriggerClass MCAddTrigger = {
	GI_visMoniker = "Add";
	GTI_destination = process;
	GTI_actionMsg = MSG_MCP_INSERT_DATA_ITEM;
}

@object GenTriggerClass MCChangeTrigger = {
	GI_visMoniker = "Change";
	GTI_destination = process;
	GTI_actionMsg = MSG_MCP_SET_DATA_ITEM;
}

@object GenTriggerClass MCDeleteTrigger = {
	GI_visMoniker = "Delete";
	GTI_destination = process;
	GTI_actionMsg = MSG_MCP_DELETE_DATA_ITEM;
}

@object GenValueClass MCValue = {
	GVLI_minimum = MakeWWFixed(0);
	GVLI_maximum = MakeWWFixed(0x7ffe);
	GVLI_value = MakeWWFixed(123);
}

@end Interface;

/* CODE for MCProcessClass */

/* MSG_MCP_SET_DATA_ITEM_MONIKER
 *
 *	SYNOPSIS: Set the moniker for one of our Data List's items.
 *	CONTEXT: The Data List will send this message to the process
 *		 whenever it needs to display the moniker of a given
 *		 item. We should respond with one of the
 *		 MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_... messages.
 *	PARAMS: void (optr list, word item)
 */
@method MCProcessClass, MSG_MCP_SET_DATA_ITEM_MONIKER {

/* If we're looking for the moniker of the "Data:" item,
 * just return that moniker. Otherwise, look up the
 * numerical value of the item as stored in the linked list. 
 */
	if (item==FAKE_LIST_ITEM) {
		optr moniker;
		moniker = ConstructOptr(OptrToHandle(list),
				OptrToChunk(@FakeItemMoniker));
		@call list::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_MONIKER_OPTR(
				FAKE_LIST_ITEM, moniker);}
	else /* item > FAKE_LIST_ITEM */ {
		char monikerString[LOCAL_DISTANCE_BUFFER_SIZE];
		word data;

		data = MCListGetDataItem(item);

		LocalFixedToAscii(monikerString, MakeWWFixed(data), 0);
		@call list::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_TEXT(
						 item, monikerString);
	}
} /* end of MSG_MCP_SET_DATA_ITEM_MONIKER */

/* MCListGetDataItem()
 *
 *	SYNOPSIS: Return the Nth piece of data.
 *	CONTEXT: Utility routine.
 *	PARAMS: word (word ordinal)
 */
extern word _pascal MCListGetDataItem(word ordinal) {
	word data;

	VMLock(dataFile, dataFileBlock, dataListBlockPtr);
	for(tempNode = LMemDerefHandles(dataListBlock, dataListHead);
	 ordinal > 1;
	 --ordinal)
		{
		 tempListItem = tempNode->LN_next;
		 tempNode = LMemDerefHandles(dataListBlock, tempListItem);
		}
	data = tempNode->LN_data;
	VMUnlock(dataListBlock);
	return data;
} /* end of MCListGetDataItem() */

/* MSG_MCP_INSERT_DATA_ITEM
 *
 *	SYNOPSIS: Add a new number to our list of data.
 *	CONTEXT: User wants to add a new piece of data.
 *	PARAMS: void(void)
 */
@method MCListInsertDataItem, MCProcessClass, MSG_MCP_INSERT_DATA_ITEM {
	ChunkHandle 		newListItem;
	ListNode 		*newNode;
	WWFixedAsDWord 		value;
	word 		ordinal;

/* Query list and data objects to find out where to insert item
 * and what value to insert there. 
 */
	ordinal = @call MCDataList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
	value = @call MCValue::MSG_GEN_VALUE_GET_VALUE();

/* Lock the data block so we can insert data into the linked list. */
	VMLock(dataFile, dataFileBlock, dataListBlockPtr);

/* Create a new linked list element. */
	newListItem = LMemAlloc(dataListBlock, sizeof(ListNode));
	newNode = LMemDerefHandles(dataListBlock, newListItem);
	newNode->LN_data = WWFixedToInt(value);

/* Check to see if the item we're adding will be the
 * new head of the data list and handle that case. 
 */
	if (ordinal==FAKE_LIST_ITEM)
	 {
		newNode->LN_next = dataListHead;
		dataListHead = newListItem;
	 }
	else
/* We're not adding to the head of the list. Traverse the
 * list using the tempListItem and tempNode variables, then
 * insert the new item. 
 */
	 {
		word count = ordinal;
		for (tempNode = LMemDerefHandles(dataListBlock, dataListHead);
		 count > 1;
		 --count)
		 {
			tempListItem = tempNode->LN_next;
			tempNode = LMemDerefHandles(dataListBlock,
						 tempListItem);
		 }
		newNode->LN_next = tempNode->LN_next;
		tempNode->LN_next = newListItem;
	 }

/* We've changed the data, so before we unlock the block, we mark
 * it dirty. 
 */
	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);

/* Update the data list gadget. */
	@send MCDataList::MSG_GEN_DYNAMIC_LIST_ADD_ITEMS(ordinal+1, 1);
	@send MCDataList::MSG_GEN_ITEM_GROUP_SET_SINGLE_SELECTION(ordinal+1,
								 FALSE);
} /* end of MSG_MCP_INSERT_DATA_ITEM */

/* MSG_MCP_DELETE_DATA_ITEM for MCProcessClass
 *
 *	SYNOPSIS: Destroys one data item.
 *	CONTEXT: User has just clicked on the "Delete" trigger.
 *	PARAMS: void (void)
 */
@method MCProcessClass, MSG_MCP_DELETE_DATA_ITEM {
	word 		ordinal;
	word 		count;
	ChunkHandle 		oldItem;
	ListNode 		*oldNode;

/* Find out which item the user wants to delete. */
	ordinal = @call MCDataList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
	if (ordinal==FAKE_LIST_ITEM) return;

/* We're going to work with the data, so lock the data file. */
	VMLock(dataFile, dataFileBlock, dataListBlockPtr);

/* If we're deleting the first data item, we update the handle of the
 * head of the list. 
 */
	if (ordinal == 1) 
	 {
		oldNode = LMemDerefHandles(dataListBlock, dataListHead);
		tempListItem = oldNode->LN_next;
		LMemFreeHandles(dataListBlock, dataListHead);
		dataListHead = tempListItem;
	 }

/* If we're deleting an element which isn't the first, we find the element
 * that's just before the one we want to delete, and change that element's
 * "next" handle. We also get rid of the item to be deleted. 
 */
	else /* ordinal != 1 */ 
	 {
		for (tempNode=LMemDerefHandles(dataListBlock, dataListHead),
		 count=ordinal;
		 count > 2;
		 --count)
		 {
			tempListItem = tempNode->LN_next;
			tempNode = LMemDerefHandles(dataListBlock,
						 tempListItem);
		 }
		oldItem = tempNode->LN_next;
		oldNode = LMemDerefHandles(dataListBlock, oldItem);

		tempNode->LN_next = oldNode->LN_next;
		LMemFreeHandles(dataListBlock, oldItem);
	 }

/* We've changed the data, so before we lock the block, we mark it dirty. */
	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);

/* Update the list. */
	@send MCDataList::MSG_GEN_DYNAMIC_LIST_REMOVE_ITEMS(ordinal, 1);
	@send MCDataList::MSG_GEN_ITEM_GROUP_SET_SINGLE_SELECTION(ordinal-1,
								 FALSE);
} /* end of MSG_MCP_DELETE_DATA_ITEM */

/* MSG_MCP_SET_DATA_ITEM for MCProcessClass
 *
 *	SYNOPSIS: Change the data number of one item in the data list.
 *	CONTEXT: User has clicked the "Change" button.
 *	PARAMS: void(void)
 */
@method MCProcessClass, MSG_MCP_SET_DATA_ITEM {
	word 		ordinal;
	WWFixedAsDWord 	value;
	char 		monikerString[LOCAL_DISTANCE_BUFFER_SIZE];
	word 		count;

/* Find out which item we're changing. */
	ordinal = @call MCDataList::MSG_GEN_ITEM_GROUP_GET_SELECTION();
	if (ordinal == FAKE_LIST_ITEM) return;

/* Find out what the item's new value should be. */
	value = @call MCValue::MSG_GEN_VALUE_GET_VALUE();

/* Lock the data block so that we can change the data. */
	VMLock(dataFile, dataFileBlock, dataListBlockPtr);

/* Find the appropriate item in the linked list and change its value. */
	for (tempNode = LMemDerefHandles(dataListBlock, dataListHead),
	 count = ordinal -1;
	 count > 0;
	 --count)
	 {
		tempListItem = tempNode->LN_next;
		tempNode = LMemDerefHandles(dataListBlock, tempListItem);
	 }
	tempNode->LN_data = WWFixedToInt(value);

/* We changed the data so mark it dirty before unlocking it. */
	VMDirty(dataListBlock);
	VMUnlock(dataListBlock);

/* Update the data list gadget. */
	LocalFixedToAscii(monikerString, value, 0);
	@call MCDataList::MSG_GEN_DYNAMIC_LIST_REPLACE_ITEM_TEXT(ordinal,
								 monikerString);
} /* end of MSG_MCP_SET_DATA_ITEM */

/* MSG_GEN_PROCESS_OPEN_APPLICATION
 *
 *	SYNOPSIS: Set up application's data structures.
 *	CONTEXT: Application is starting up, either because user
 *		 has started the application or because the whole
 *		 system is re-starting.
 *	PARAMS: void(AppAttachFlags 	attachFlags,
 *		 MemHandle		launchBlock,
 *		 MemHandle 		extraState);
 */
@method MCProcessClass, MSG_GEN_PROCESS_OPEN_APPLICATION {
	char fileName[] = "MChtDATA.vm";

/* Open a temporary file, clearing out the old data. */
	dataFile=VMOpen(fileName,
			VMAF_FORCE_READ_WRITE | VMAF_FORCE_DENY_WRITE,
			VMO_CREATE_TRUNCATE, 0);

/* Allocate a storage block within the file. */
	dataFileBlock = VMAllocLMem(dataFile, LMEM_TYPE_GENERAL, 0);

	@callsuper();
} /* end of MSG_GEN_PROCESS_OPEN_APPLICATION */

/* MSG_GEN_PROCESS_CLOSE_APPLICATION
 *
 *	SYNOPSIS: Free up the memory we allocated. Actually, we could
 *		 probably rely on the system to do this for us.
 *	CONTEXT: Application is shutting down, either because of user
 *		 exit or because whole system is shutting down.
 *	PARAMS: MemHandle(void);
 */
@method MCProcessClass, MSG_GEN_PROCESS_CLOSE_APPLICATION {
/* Tell the data list gadget that it should only have one item */
	@send MCDataList::MSG_GEN_DYNAMIC_LIST_INITIALIZE(1);

/* Close the data file. */
	VMClose(dataFile, FALSE);

	@callsuper();
} /* end of MSG_GEN_PROCESS_CLOSE_APPLICATION */ 
