5 Data Structures and UI

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.

5.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.

5.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.

  • 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.

  • 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.

  • 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.

  • 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.

  • 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.

5.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.

5.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.

5.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.

5.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;

5.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 Listing


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 */ 

otype 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. */

The Primary Window <–    table of contents    –> Views and Visual Objects