OOP Architectures 2
|Column Tag:||OOP Architectures
MacApp and THINK Class Library
By Larry Rosenstein, Apple Computer, Inc.; Joseph S. Terry, Jr., Ajalon Corporation
Object-oriented Design (OOD) ...
Design is the most difficult and important task of Object Programming (OP) and very few professional software engineers do it well.
That sentence began the first of this two-part(read long) article and it bears repeating. Goods tools in and of themselves do not produce reliable, well designed products, well trained software engineers do.
In this second part we will explore the execution of commands which are mostly menu choices or button clicks, look at general flow of control from one class to another, clear up some confusion about exactly what does go on under low memory conditions (and doesnt), take a look at printing, using background tasks or chores, error handling, and a summary of the things to remember when programming with any class library.
The last seven sections are the ones were covering in this second part. The first five sections were covered in the January 1991 MacTutor.
1 - BIT O HISTORY
2 - ARCHITECTURAL OVERVIEW
3 - HOW THINGS WORK TOGETHER: An Example Message Trail
4 - STORING INFORMATION
A) APPLICATION->DOCUMENT->FILE RELATIONSHIPS
B) IN MEMORY DATA STRUCTURES
5 - DISPLAYING INFORMATION
A) APPLICATION->WINDOW->VIEW RELATIONSHIPS
6 - EXECUTING COMMANDS
A) CONTROLLING THE CHAIN OF COMMAND
B) TO UNDO OR NOT TO UNDO
7 - FLOW OF CONTROL
8 - LOW MEMORY CONDITIONS
9 - PRINTING
10 - BACKGROUND TASKS
11 - ERROR HANDLING
12 - SUMMARY
Apple Computer, Inc.
A large part of an applications code is devoted to processing commands. In MacApp, commands are represented by the TCommand class. TCommand is not only used to factor out commands from the rest of the application, but also to encapsulate the information necessary to undo the command (if the need arises).
There are 4 methods of TCommand related to performing actions: DoIt, UndoIt, RedoIt, and Commit. When the command is first performed, MacApp calls the DoIt method. DoIt is responsible for saving information in case the command is undone and performing the command.
UndoIt is called if the user selects the Undo menu item. RedoIt is called if the user selects Undo again. DoIt and RedoIt are similar and often share code. If the user selects Undo a third time, then MacApp calls UndoIt again, and so on.
The purpose of the Commit method isnt as obvious. It is called when the command can no longer be undone (e.g., the next command is about to be performed) and the command hasnt been undone. This gives the command object one last chance to affect the document before it is freed.
Commit is used in cases where the command isnt easily reversible. In a drawing program, moving shapes is easily reversible. The command object simply remembers the offset and moves the shapes by the same amount in the opposite direction. A command that changes the fill color, however, isnt easily reversible. Reversing the command requires that the program remember the old color of each affected shape. This could take almost as much memory as the entire document.
In this case, the command could be implemented using a filter. When the user changes the fill color of one or more shapes, the color stored in the shape objects isnt changed immediately. Instead the program remembers which shapes were affected, and what color they were changed to. When drawing the affected shapes, the program uses the color in the filter. To the user the shapes have changed color.
Undoing the command is now easy, because we simply remove the filter. The shape objects are then drawn with their original color. Eventually, however, the shapes colors must be permanently changed; this would be done in the Commit method.
Now that we know how command objects work, where do they come from? A MacApp application creates command objects in response to user actions. The 3 main cases are menu selections, keystrokes, and mouse clicks.
Menu selections and keystrokes are handled in a similar manner. MacApp takes case of handling the menu or extracting the character, and calls the DoMenuCommand or DoKeyCommand method. The target event handler gets the first chance at handling the event; control passes along the target chain until it reaches the object responsible for handling the event. At that point, the event handler object creates the appropriate command object and returns it to MacApp.
In the case of a mouse click, MacApp locates the view in the hierarchy on which the user clicked, and calls the DoMouseCommand method of that view. DoMouseCommand creates a command object and again returns it to MacApp. This command object tracks the mouse, and (usually) performs the command when the user releases the mouse button.
There are 3 method of TCommand involved with mouse tracking:
TrackMouse, which receives the current position of the mouse and processes it,
TrackFeedback, which draws and erases the temporary feedback while tracking,
TrackConstrain, which modifies the mouse position before the other methods see it, in order to implement constraints (e.g., constraining a shape to be a perfect square).
MacApp calls these methods as the user moves the mouse. The tracker doesnt have to worry about automatic scrolling (if the user moves outside the window). TrackMouse returns a command object, which becomes the new tracker. (In most cases, the command object returns itself, but it is possible for one tracker to hand off to another.)
When the user releases the mouse, TrackMouse returns the command object that executes the mouse command. For example, if the user is sketching a new shape, this command object is responsible for adding the shape to the document. (Normally, the same object performs the command as tracks the mouse.)
Not all menu commands or mouse clicks result in significant changes to the document. In those cases, MacApp provides a global command object (gNoChanges) that the application can return. It is also possible to create a custom command object and mark it as one that doesnt change the document. (One advantage of the latter approach is that the command will be performed from the main event loop, rather than from a point several procedure calls deep. This may be preferable from a memory management standpoint.)
FLOW OF CONTROL
LOW MEMORY CONDITIONS
Memory management is one of the more difficult aspects of Macintosh programming. Applications are only given a limited amount of memory to work with, so it is always possible to run out. If this happens while trying to load a code segment, for example, the result is the dreaded System Error alert.
MacApp cannot handle all the details of memory management. It does provide a framework that you can use. One goal of the memory management framework is to prevent a System Error because a code segment or other resource cant be loaded. Another goal is to ensure that the user can always save the document and quit.
MacApps memory management framework is based on reserving RAM space so that it can be used at critical times. MacApp maintains 2 reserves, one for memory associated with the document (permanent memory) and one for temporary memory. MacApp maintains a global variable to indicate whether memory being allocated is temporary or permanent.
There is no distinction between permanent and temporary memory as far as the Macintosh Memory Manager goes. The difference is only significant in what happens when memory is low. When memory is low, MacApp will release the appropriate (permanent or temporary) reserve in order to allow the request to succeed. This is done by installing a procedure that the Memory Manager calls when an allocation request cant be immediately satisfied.
Requests for temporary memory must always succeed, since the alternative is likely to be a System Error. Permanent requests are allowed to fail; MacApp assumes that the programmer will check the result of these allocations and fail gracefully if one fails.
Most of MacApps memory management framework involves managing the reserves. It is important to have sufficient reserve available, so that the application doesnt crash, but it is also important not to monopolize that memory unnecessarily, so that it can be used.
The size of the reserves is normally set based on resources (although you can set them programmatically as well). The permanent reserve is generally small, since it isnt intended to satisfy every request. The temporary reserve, on the other hand, must be large enough to cover the maximum amount of temporary memory in use at any one time.
In practice, this number ends up being the maximum size of the code segments locked down at any one time. (With proper segmentation, this number is only a fraction of the size of all the code segments.) Sometimes you have to include memory used by defprocs, packages, print drivers, etc.
The MacApp debugger provides utilities for determining the maximum code usage. You can have the debugger break when each segment is loaded, or when a new maximum usage is reached. Then you can list the names of the loaded code segments and enter then into a seg! resource in your application.
When the application starts up, MacApp adds up the sizes of all the segments listed in all the seg! resources and adds that value to the temporary reserve. In addition, MacApp examines all the resources of type mem!. Each of these resources contains values that are added to the temporary reserve, the permanent reserve, and the stack size.
Once you have specified the sizes of the reserves, MacApp takes care of ensuring that the reserves are available. For the permanent reserve, MacApp allocates a handle of the required size. If a permanent allocation cannot be satisfied, then MacApp releases the entire permanent reserve.
You can test to see if the permanent reserve has been released by calling the function MemSpaceIsLow. MacApp does this during its idle processing, and calls the method TApplication.SpaceIsLow is the reserve is gone. By default, this method displays an alert. In addition, MacApp disables certain commands (New , Open , etc.) if memory space is low; you are responsible for doing a similar thing for the commands that you handle.
Managing the temporary reserve is more complicated. MacApp could allocate a large handle the size of the reserve. Unfortunately, this would often waste memory. For example, suppose your application needs a temporary reserve of 100K to load code segments A, B, and C. If none of the segments is in memory, then MacApp would have to allocate a 100K handle to make up the reserve. If segment A is already in memory, however, then the size of the handle only needs to be the total of the sizes of B and C.
In its implementation, MacApp total up the sizes of code segments, packages, and defprocs that are already in memory, and subtracts this sum from the desired temporary reserve. If the result is greater than 0, then MacApp allocates a handle to make up the difference.
MacApp doesnt keep track of every resource as it comes and goes. If you think about it a minute, the integrity of the temporary reserve is only critical when a permanent request is made. Thats because you dont want to satisfy the permanent request at the expense of reducing the temporary reserve. So MacApp doesnt have to check the temporary reserve until the temporary/permanent flag is set to permanent.
This memory management framework is a good example of how MacApp can embody accumulated Macintosh programming knowledge, and make it available to other programmers. The basic approach of maintain reserves and managing them was first implemented in Apples Macintosh Basic, and re-implemented in the first version of MacApp.
Printing is another feature that can be difficult to implement on the Macintosh. It requires that you interact with the Printing Manager, and that you follow a definite series of steps. This makes it ideal for an object-oriented approach such as MacApps.
In a MacApp program, you rarely have to write any code to implement printing in your application. Once you have defined a view object that can draw on the screen, MacApp uses the same view object to draw on the printer. MacApp takes care of the various printer dialogs for you.
All the printing code is encapsulated in the TStdPrintHandler class. When creating your view object, you can make it printable by associating it with an instance of TStdPrintHandler.
Although most application dont need to modify MacApps printing behavior, TStdPrintHandler provides many opportunities to do so. A simple change is to set the page margins. This is done with a call to TStdPrintHandler.InstallMargins.
Another common change is to modify the way MacApp breaks the view into pages. By default, MacApp computes the part of the view that can be displayed on each page, based on the desired margins and the printers characteristics. In then divides the view into chunks of that size. If you are implementing a text processor or spreadsheet, however, you would want to break the pages between lines of text or spreadsheet cells.
Before finalizing a page break, MacApp calls the view method DoBreakFollowing. You can override this method and move the page break back to a convenient boundary. You can also implement manual page breaks by overriding the same method. It is also possible to change the way in which page breaks are drawn on screen by overriding TView.DoDrawPageBreak.
A final printing variation is to add elements to the page that arent drawn on screen. By default, MacApp reproduces the entire view on the printed page. If you wanted to draw page numbers or headers, however, you could override the method TStdPrintHandler.AdornPage. This method is called once for each printed page, after the main part of the page has been drawn.
MacApp provides a simple framework for performing tasks in the background. This takes the form of the TEvtHandler.DoIdle method, which MacApp calls repeatedly when there are no user events pending. The TApplication, TDocument, and TView classes are all subclasses of TEvtHandler, so any instance of those classes can receive idle time. Additionally, you can define a special subclass of TEvtHandler to do other idle processing.
Instances of TEvtHandler can be linked into chains. One is the target chain, which normally starts with a view object, and continues up the view hierarchy to the window, document, and application. As mentioned before, this chain also handles keystrokes and menu commands. The other chain is known as the co-handler chain. This is a separate chain into which you can install your own TEvtHandler object.
Not every instance of TEvtHandler is given idle time (by calling its DoIdle method). Each instance of TEvtHandler contains the field fIdleFreq, which indicates the minimum interval between calls to DoIdle. If that field is the constant kMaxIdleTime, then MacApp does not give the object any idle time.
Actually, MacApps idle processing is a bit more elaborate. The DoIdle method has 1 parameter, which is the idle phase. MacApp defines 3 phases: idleBegin, idleContinue, and idleEnd.
When MacApp starts its idle processing, it calls DoIdle with the idleBegin phase. It then calls DoIdle with the idleContinue phase, subject to the value of the fIdleFreq field. When idle processing completes (i.e., the next user event is available) MacApp calls DoIdle with the idleEnd phase.
In order to use this idle mechanism, you need to implement your background task so that it can do a small amount of processing each time DoIdle is called. Since the idle processing is done by an object, you can maintain state between calls in the state of the object.
Error handling is a very important application feature. In designing the error handling framework for MacApp we wanted to provide an easy way for programmers to catch and recover from errors, and a clear way of reporting errors to the user.
To help catch and recover from errors, we implemented an exception handling mechanism in MacApp. MacApp maintains a stack of exception handling routines. You push a handle on the stack with a call to CatchFailures, passing a handler procedure, and a FailInfo record.
The record is initialized by MacApp with information necessary to call the handler (e.g., current stack pointer, a link to the next handler record). The handler procedure is declared with two parameters: a standard Macintosh error code, and a 4-byte message. The message is used to describe what operation failed, and the error code gives the reason for the failure. Normally the handler is a nested procedure in Pascal so that it has access to the local variables of the enclosing procedure.
There are two ways to pop a handler off the stack. The first is by calling Success. Success just pops the handler off the stack. You must do this before exiting the procedure that established the handler.
The second is by calling Failure. Failure can be called by any routine, not just from within the routine that set up the handler. When you call Failure, MacApp pops the top handler off the stack, restores the registers and other state, and calls the handler with the error code and message passed to Failure. (Note that the process of restoring the registers restores the stack pointer to its original position.)
A failure handler normally does some clean up (e.g., free allocated memory) and returns. MacApp then propagates the failure by calling the next handler on the stack. Eventually, control reaches a handler in the main event loop, which was established by MacApp. This handler displays an alert reporting the error, and the branches to continue handling events.
In order to make things easier for you, MacApp provides a few utility routines. For example, the procedure FailOSErr takes an error code and calls Failure is it is not noErr (0). This makes it easy to write code such as: FailOSErr(FSRead(...)); Similarly, there are utility procedures that check for a NIL return from the Memory Manager, a missing resource, etc.
Catching errors is still your responsibility; MacApp doesnt try to second-guess what you are trying to do. But if you do use the MacApp failure handling mechanism, then MacApp will automatically put up the appropriate error alerts for you.
MacApp error messages have the general form:
Could not <operation> because <reason>. <Recovery>
The alert has 3 blanks that MacApp fills in based on the error and message values passed through the failure handlers. In the simplest case, the error code is looked up in 2 tables to produce an explanation of the error and a way of recovering from it. The message code is looked up in another table to produce a description of the operation that failed.
An example of a filled-in alert might be:
Could not save test doc because the file is locked. Use the Get Info
command in the Finder to unlock the file.
The alert occurs if the user tries to save to a locked file. MacApp automatically generates this alert (including substituting the name of the document), provided you test for File Manager errors in your saving code.
If you look at all the possible Macintosh error codes, you will see that there are only a handful of errors that can be meaningfully reported to users. Also, many error codes can be described with the same message.
MacApp uses an errs resource to map between an error code and an index into a list of strings. The resource consists of a list of entries, each of which contains an error code range, and the appropriate string index. (Special entries in the errs resource indicate the resource ID of the string list.)
There are a couple of variations on the basic error reporting mechanism. For example, if the failure occurs while processing a command, MacApp will use the command number of the command to retrieve the command name from the menu, and display an alert such as:
Could not complete the Copy command because there is not enough memory.
The failure handling mechanism also comes in handy for other things besides errors. For example, when the user closes a document that has been changed, MacApp puts up the standard Save Changes... alert. If the user clicks Cancel, the program should abort closing the document.
MacApp handles this case by signaling a failure if the user clicks Cancel. In this case, the error code passed is noErr. In the main event loop, this error code is handled specially, and MacApp does not display an alert. The end result is that the action is cleanly canceled, without the intermediate procedures having to handle this case explicitly.
At the start of this series, Joe Terry asked a couple of questions, that I can now answer.
How does MacApp make programming the Macintosh easier?
There are a number of ways that MacApp helps Macintosh programmers. First, it handles the common features that every application must have (menus, windows, scrolling, printing, etc.). This means that there is less code for you to write, which leaves more time for you to write application-specific code (e.g., new features).
Consequently, there is no need for you to understand all of Inside Macintosh, before you can start writing your application. Even though MacApp itself is large, you dont need to understand very much of it before you can get something up and running. Spending a month learning about MacApp is a much better time investment than spending a month learning the Macintosh Toolbox. At the end of that time, your MacApp application will support scrolling, printing, Undo, etc.
MacApp is also beneficial even after you have learned its basics. MacApp provides a well-tested framework into which you add your application-specific code. This makes your application much easier to extend later. You will also end up building your own class libraries, which will make the second and later MacApp programs that much easier to produce.
Finally, MacApp encapsulates many programmer-years of Macintosh programming experience. Apple has been working on MacApp since 1985, and programmers who use MacApp gain the experience and testing that went into it.
What are some of the design decisions that are easily reversed and some that are not so easily reversed?
Most of MacApps design decisions are easily reversed because MacApp is provided in source form and is linked with each application. Changes to MacApp dont affect existing applications, unless they are recompiled. Of course, existing applications dont get the benefit of any new features or bug fixes. (This is different from the Macintosh ROM, which is shared by all applications.)
In the past, developers using MacApp have favored improving it even at the expense of changing the programming interface. For example, MacApp 2.0 completely changed the view model, compared to MacApp 1.0. Developers had to spend some time upgrading from 1.x to 2.0, but they were not forced to do so until it was convenient.
MacApp 3.0 is changing the way documents and disk files are handled to make this area more general. Previously, TDocument combined the storage for a document with its I/O aspects; these functions are being separated.
MacApp 3.0 will also provide support for System 7 features (AppleEvents, Edition Manager, etc.). Although I havent looked at these MacApp changes, Im certain that developing applications that support System 7 will be much easier if youre using MacApp.
What kind of applications is MacApp best for?
MacApp is intended for writing commercial quality applications. It isnt intended for DAs or standalone code resources. Those kinds of programs could take advantage of an object-oriented framework, but such a framework would be different from an application framework (i.e. MacApp).
So far, MacApp has proven to be suitable for a variety of applications. There are MacApp applications already shipping that deal with 32-bit color graphics, text processing, sound & multimedia, and programming. MacApp was oriented more towards full applications, as opposed to small utilities. You can still write small utilities, but you wont benefit as much from MacApps standard features.
Joseph S. Terry, Jr.
CONTROLLING THE CHAIN OF COMMAND TO UNDO OR NOT TO UNDO
In TCL the concept of a chain of command is really a way of reducing the message passing overhead you would experience in a free form approach to message resolution. Rather than ask every object if a message applies to them, TCL has a specific line of objects that can respond to direct commands. At the head of the line is an object called the gopher. The gopher is actually a global variable called gGopher with a reference to the object that should be first in line to receive direct commands. The programmer is responsible for setting the global variable gGopher when you want a particular object to be at the head of the line such as a TextEdit field in a database application. When the user hits tab then you would make the next edit field in line the gopher.
Figure 1. Be a gGopher
Also you should note that the supervisor of the Pane that is the current gopher is likely to be the Document that is responsible for the window, rather than its immediate superpane, that the Pane in question is contained in.
Objects in the chain of command are decedents of the CBureaucrat class. This provides a common command handling behavior that all objects in line understand. Every CBureaucrat has a supervisor. If an object cannot handle or doesnt understand a message then that message is passed on to its supervisor. CBureaucrat actually has an instance variable named itsSupervisor. This variable is initialized by the default initialization routine, as long as you provide an object as the supervisor.
To implement Undo-able actions youll want to use the class CTask. It is designed specifically to for this purpose. Undo-ing on the Macintosh is a central difference in the user interface approach from previous icon/pointer driven environments. But many people quickly realized that you only have one shot, that undo although wonderful was one level deep. If you did anything else after your mistake you could no longer undo the bad thing. The explanation of CTask in the TCL manual is short but complete. Remember to a subclass of CTask and store your own instance variables that will contain enough information to undo the action.
If its text changes that your undo-ing undoes then save the whole text. Trying to figure out what piece of localized text youre undo-ing changes to, at undo time, is rather a ridiculous waste of brain power, use memory instead.
The really exciting news is that there is no excuse for a program written in TCL to not have much more sophisticated undo/redo scenarios. Here is where the class library begins to synergize solutions for us. With CTask and CList, I can build an unlimited undo for a simple action such as the font example in the manual in a page of code. Yes, unlimited undo/redo! I say a page when it could probably be done in a half a page if I didnt include comments.
And the whole thing would make more sense if I had multiple inheritance at my disposal. Thats another article. What I would do is override the CDocument class Notify() method. What it does now is maintain the lastTask in an instance variable named lastTask. And on entry if there is a lastTask already then that is simply disposed of. I would continue to add any undo/redo task onto the current document task list in entry order and allow the user to set a limit on how many of these are kept around. As a new task entered the queue the oldest would be disposed of if the queue were filled.
If you did an example of undo-able programming try CMouseTask. Its not very clear from the manual so print out the source code and read it carefully. Youll see that there are no instance variables defined in the .h portion of the source and the .c source includes only empty methods. This is the proverbial exercise for the reader. Look at the code for a mouseTask in Gregory H. Dows (GHD) CPaintTask.c in the project Art Class. This is very nicely done. Its not optimal in speed of execution, but remember that in programming the meta machine of a class library it is more important to make efficient use of the the class library.
FLOW OF CONTROL
In MacApp there is a concept called the target. The target is the object instance upon which some command will act.1 In, TCL there is the gGopher. Further, there are three conceptual target chains which are the paths along which message with travel in search of a handler. There is the command chain, the click chain, and the cohandler chain. These are roughly equivalent to the chain of command and visual hierarchy concepts in TCL.
Note the similarity between handling a mouse click and menu command between MacApp and TCL. Since a mouse click involves a position in the window, it is handled by going down the view hierarchy in MacApp. Similarly, in TCL a mouse click in a particular position in a window goes first down the visual hierarchy DeskTop->Window->SubView->SubView->TargetSubView. A menu selection has no positional information and is handled by starting at the target/gopher and working upward from SubView->SubView->Document->Application. Most menu commands are normally handled in the Application Object. The only classical exceptions are the Apple, File, Font, Size and Edit menus which have some default behavior built-in.
Architecturally speaking, menus are a very, very old user interface concept and I expect some young bucks to come up with new more powerful and more ... human oriented, ways of making choices. Menus have proven useful, but as applications become more complex and the range of choices become larger, in my opinion, user interface designers are failing to create intuitive menu selection arrangements and it may not be their lack of talent or creativity, but a fundamental limitation in the conceptual framework or design school they are working from. For instance in the January 1991 MacTutor there is an article about Pie Menus by Boyd and Andrea Hays of Boulder, CO. This is the kind of article we all need to see more. This piece is based on an article called An Empirical Comparison of Pie vs. Linear Menus, by J. Callahan, D. Hopkins, M. Weiser, and B. Shneiderman[sic], in the ACMs SIGCHI 1988 Conference Proceedings. The general conclusion of the ACM paper was that users could select items from pie menus, in certain instances, faster than from linear menus.
Figure 2. Pie Menu
It is commonly known in the clinical psychology literature that we humans are able to juggle about 7 to 12 conceptual frameworks, items, numbers, letters, etc. at the same time. Thats it. Creating a menu with 20 or thirty choices and further depth in hierarchical menus seems ludicrous if your aim is ease of use AND recall.
I would like to see a limit place on the items in menus. After that you must move to a different platform/Dialog Box/Choicer to see other choices. Now this does smell of the old nemesis mode. As a theoretical user interface component mode has been taking a bad rap for over twenty years. There was even a T-Shirt (I love T-Shirts) that had the slogan Dont Mode Me In. Cute ..., but the concept of context is terribly important to the fast recognition in the normal operation of the human mind. Call it mode or context or frame of reference, but, if you present too many choices too far apart in space, it is very difficult to achieve a state of fluidity with the software that approaches what is possible with the incredible user interface of a paint brush in the hand of a Picasso or Da Vinci. That is the ultimate user interface of any tool for the hand of man.( Ok, maybe a piano in the hands of a Tchaikovsky, or a blues song in the throat of a Holiday or James).
Dont think these issues dont apply to your CURRENT project. They do ... even if you dont realize it.
LOW MEMORY CONDITIONS
This is a very technical subject and I hope that the highlights and gloss over job I do here will not offend those who know more that than I do or those that expect this to be the definitive How to avoid ID -25 in ten easy lessons. Lets concentrate on using our machine efficiently. Its rather simple. When you initialize an Application object it requires three parameters. All three are related to low memory conditions. The first one is the extraMasters number. This number determines how many Master Pointer Blocks you want for your application to run. Each Block usually contains 64 master pointers and this is the number of relocatable handles that your application can create WITHOUT creating another Master Pointer Block.
The key is this after observing your heap zones behavior you determine that you only need 5 Master Pointer Blocks throughout a run of your program. Then it is much better for you to call MoreMasters or in this case send five (5) as the extraMasters value when initializing an Application object. That way the memory manager will not create them whenever it needs them, possibly fragmenting your heap very badly. This is the first and most important Macintosh memory management technique. When the memory manager allocates a Master Pointer Block it just locates free memory and plops one down there ... locked in place forever. The new memory manager is getting smarter, but their are a few bugs...
That phrase observing your heap zones behavior describes something you can do with MacsBug or TMON. Basically, you can look for the number of non-relocatable blocks that are 264 bytes long (64 pointers times 4 bytes each plus an 8 bytes for the block header). Add 5 and use that number to initialize your Application objects.
The next parameters are the aRainyDayFund and the aCreditLimit. The description in the manual is adequate (page 230). These are different than extraMasters because extraMasters is a direct Mac ToolBox issue. These parameters require that we again use the machine/class library the way it was intended. There is a special logic that must be followed. Lets say that you allocate 50K for the rainy day fund and set your credit limit at 10K. This means that when the Mac memory manager cannot fill a memory request then the GrowMemory() message is sent to the Application object by way of the GrowZoneFunc in the CError Class. Actually, the global variable gApplication is sent the message. You MIGHT have someone, an object, in gApplication that responded to this message and passed all other messages to the ACTUAL application, but what a hack... The default GrowMemory message handlers in the Application object will first ask if there are temporary buffers that can be eliminated by sending itself the message MemoryShortage(). If that doesnt work then the object performs thusly:
ADoes the Rainy Day fund still exist?
1 If yes, then can I borrow from rainy day (Is loanApproved true?)
is what Im asking for within my creditLimit (loanApproved can be
false in that case)
A if yes, then proceed to determine if my request will leave me with
a MINIMUM_BALANCE. MINIMUM_BALANCE is a constant with a normal value
of 2048 bytes.
1 if yes, then give us everything NOT just the requested amount. Holding
the minimum in reserve.
2 if no, then can I satisfy the request and leave NO reserve.
A If yes, then do it you fool. Liquidate the fund. Cash in the chips,
etc. The user is so innocently unaware.(he he he)
B If no, then Do (A) and hope for the best. Watch Out Below. Theres
likely to be a crash. Save the Documents and Disk files first.
B if no, then loanApproved is false and Im asking for something over
my credit limit. (Bad boy) So, now I resort to (B) below.
2 if no, then I do (B) below.
BIf no, rainy day fund is gone then if this request is OK to fail then
I just return with that information, otherwise I try one last resort
the message handler OutOfMemory().
This message handler is more of an bourgeois apologist than a real blue collar proletariat. It flashes some kind of conciliatory message about being out of memory and then waits for another event. What a wimp!
Ok. Whew! Now you know everything you need to know about low memory conditions. If you dont like what youve just read then override it. The whole point of all these games with rainy day fund and credit limit is to establish a problem domain lexicon or a set of jargons and objects that you can manipulate and think about as you establish the low memory policies for your application. If you build on and enhance the reality of the established lexicon. Loan Memory to short-lived objects, Establish interest on especially hoggy processes (read - tell the user that this is inadvisable unless they want to burn memory or wait forever). Build on the illusion and you will be able to share your code with your colleagues and talk about concrete things (and charge your enemies obscene interest).
Printing is one of the most complex tasks on the Macintosh and I will provide just an advance on the territory. What TCL provides is a very simple model. Every printer is a Quickdraw device with a border and a margin and dot density. The dot density can be determined along the horizontal or the vertical axis and allows for a measure of proportional control to rest in the application, rather than for instance trusting a printer to print your circle perfectly circularly; you can adjust drawing routines.
In the CPrinter class the access method GetPageInfo provides this information and is the only general routine for interrogation of the state of the printing engine. You would only call this routine early in the print cycle or before printing actually began. Calling this routine during printing will confuse the Macintosh printer drivers.
So we start out by having a document that is active that we want to print. When initializing this document we would have indicated if it was Printable. If so we would have initialize the instance variable itsPrinter, pageWidth, and pageHeight. Having done this the document is ready to print.
We select the Print command from a menu or in some other way indicate that we want to print this document. Normally a direct command cmdPrint will then be handled by the DoCommand message handler of a document and will send the message DoPrint() to the printer object in the itsPrinter instance variable of a document object. Every document can have a different way of responding to the Print command by having a different kind of object or subclass of CPrinter in its itsPrinter instance variable.
DoPrints normal behavior is to display the standard PrJobDialog as shown in the figure.
Figure 3. PrJobDialog Imagewriter
If the user clicks OK then DoPrint sends the message PrintPageRange to itself. PrintPageRange is the workhorse that does the actual printing. It opens a print Document (toolbox kind not TCL kind) and sends the message PrintPageOfDoc with a page number for every page to be printed. Either 1 or 1 2 3 or 1 2 3 4 5.
Your Documents PrintPageOfDoc routine must then draw on this printer page just as any quickdraw page/screen. Actually, it sends, by default a message PrintPage() to its main pane. This decouples printing between the Document/Window and the Panes within them.
The PrintPage message handler in CPane is very primitive by itself. It just draws the entire pane using standard quickdraw calls. These calls are bracketed by calls to PrOpenPage and PrClosePage with the Macintosh PrintPort as the only parameter. This is a toolbox level detail that I dont want to dive into, but at this stage you would have to override PrintPage in CPane to make many interesting things happen like multi-page printing. To do that you would just have to actually use the parameter pageNum in PrintPage. With that parameter you would interrogate your internal data structures and draw the pageNum page.
This is not in my opinion an advance in the state of printing abstractions, and I would say that here, in this programming domain, that TCL is the weakest in terms of the available conceptual tools. This does seem to be a weak area in MacApp as well. Data structures and printing. These two areas need more attention from the OP community.
With the advent of Multifinder, it has become more important that an application make active use of the time when it is not directly interacting with the user. Sometimes this is as simple as saying Hey operating system, heres some time I dont need. Cooperative Multitasking is much like volunteer fire departments. It works in low pressure, low use situations. When applications are well behaved and courteous to one another everything is fine. When they are nasty and only execute GetNextEvent, then the whole system is at risk. The solution to this dilemma is known as Preemptive Multitasking. That would not allow an application to hog the CPU any longer than necessary, else they would be swapped out and allow someone else to run by force. Couple that with hardware memory protection and youve got a real ball game on your hands. I cant wait.
TCL allows two types of things to be scheduled for regular attention. One type is the idleChore these are arranged in a list and perform their function during the time when there are no Macintosh system events directed at the application. (User is thinking, not typing).
The other is the UrgentChore which is arranged in a cluster and is performed after every event (even idles), if and only if the urgentsToDo Application object instance variable is true. After urgents are done then the urgentsToDo variable is set to false.
The default Application Idle message handler is the seed of much activity in TCL. First it checks to see if the rainyDayFund is used up. If so, it tries to replenish the fund, and if it is not successful gives the user an unpleasant warning/feeling.
Next it does something that is quite neat. It gives each Bureaucrat in the chain of command a chance to do something by sending a dawdle message up the chain, starting at the gopher. If you remember your chain of command, this is an very nice time to do perhaps user timers. The Application object gets a dawdle message too.
Last but not least, each object in the IdleChore list is asked to perform. That is that each object is sent the perform message and a parameter which represent the largest signed long integer possible. I dont know why. Of course the classical use of the idleChore is to flash the cursor in a TextEdit field. The default dawdle for TextEdit is in CEditText.
One last thing about idle chores. Since they are in a CList class data structure they can be ordered in a variety of interesting ways. (By Need?, By Kind?, By Appropriateness?) This is a glimpse of the power of flexible data structures.
Urgent chores are more mysterious. These chores are not arranged in a list (CList) per se. They are arranged in a cluster (CCluster). The difference is very subtle. Urgent chores are done once, and then have to be scheduled again by some object. The speed of access may be one explanation for the CList vs. CCluster difference, frankly, I dont know. One thing is for sure, this is a powerful way of adding things to the computational sequence without actually performing the operation yourself.
Any object can in effect say I want this to be done as soon as it is practicable. The TCL decides when that time comes and does it. Very Nice. Imagine a lot of calculations you want done but you want the user to be able to continue to select new ones. Build a list and execute them en masse? Or just send the message AssignUrgentChore() for each calculation to the Application object which is in the global gApplication at all times. Simple.
I really dont have much else to say on this subject. Architecturally, Urgent chores and Idle chores are extra pipe in the walls for cables you might want to lay in the future. Inter Application Communication (IAC) in System 7.0 should make features of this nature more used and abused. I hope its not a nightmare. Is this parallel processing? Not quite, but the flavor is very interesting on the programming palette.
Error Handling is the last subject we shall cover and the pundits are already saying that error handling should be the FIRST consideration. Well, in the old days, of procedural programming that MAY have been true. In the present day of OP programming error handling, as opposed to logical errors and bad pointers, is rather trivial. One of the advantages of OP is that only certain kinds of errors are possible. Of course we need to remember the strict distinction between the world inside the application, but outside methods/routines/handlers. In this in-between world there is order and orderly progression. This is the machine that passes messages and responds to them, broadcasts them and eats them. The world inside methods/routines is the old free for all that were quite used to. A stray pointer can bring down the whole party.
The first concept is to separate your error handling schemes for the world inside methods/routines and the world outside. Once that is done youll feel better. Next lets take a look at the CError class. It is three pages in the manual. I wont repeat those pages here (pages 313, 314, 315). Basically, to really do sophisticated error handling you will have to create a subclass of this class. The functions provided are good solid building blocks.
When the Application object is initialized an instance of CError is created and stored in the global gError. The manual says you can create a subclass of CError for more sophisticated error handling and Im saying you must do so.
The only problem here is that the default Application initialization routine takes on so much that at this point (needing sophisticated error handling) you have to replace/override the entire routine. You cant just do the neat stuff and then call the inherited method. Why? Because it then proceeds to fill GLOBAL variables with stuff, like gError.
I dont know what the solution is here, but this aint it. Again separate your error handling for the two worlds and you will find very few errors in the final product can possibly slip through.
When you read and XYZ sends a message to JKL you may say to yourself Why use all this fancy language, when its plain that one function calls another function. Well, the answer is more shadow than substance at the moment. Even for those that claim great successes with OP, these are largely research projects or projects that are under similar kinds of performance pressures. The pressures of the real world, business and defense programming are slowly using many of these techniques, but most organizations are still just at the talking stage.
Also the technology is somewhat limiting. OP without multiple inheritance (MI) which neither MacApp or TCL have (They have Single Inheritance (SI) just as Smalltalk does) is not unanimously considered the most complete model for OP. Larry feels differently, and I acknowledge that there is nothing you can accomplish with MI that you cannot accomplish with SI. A similar argument can be made for assembly language vs. C or Pascal.
In fairness, Larry can be ever so persuasive in person and in writings. The real story is unfolding here in your hands. YOU ... yes YOU ... All of those who read this article are now right-smack-dab in the middle of what I believe to be one of the great historical times in the saga of artificial computation. Read everything you can get your hands on and hold on ... Its the Century of OP.
The summary is really a moral.
Use what you got.
Learn the class library of your choice. Learn how to change and improve the behavior from within the design framework. If you seriously disagree with the structures or arrangement of MacApp or TCL ... AND you try to do something about it; you will quickly find yourself on the BLEEDING edge and looking for a high slippery ledge. Another way of saying this is, if you dont get the program, class library concept, dont attempt to use it.
Too much overriding is hazardous to the health of your programs.
Another thing ... If you are developing classes that are generally useful in MacApp or TCL and you dont release them to the public, either for a fee or gratis, you will be hunted down and fed to the great blue programming dragon. The age of OP is one of giving, sharing and building on Other Peoples Work (OPW). Just do it.
It will come back to you a hundred fold. I know this sounds ... well sappy ... but it does work. Lets all stop working so hard ... OK??? Where are those entrepreneurs who steal ... I mean merchandise honest programmers work and sell it for Distribution Charges. Why dont we have a Reusable Code CD-ROM? $59.00 Cash or Visa/MC. Source code is better than an application any day of the week.
Count those cycles ... I mean messages.
1 Introduction to MacApp 2.0 and Object-oriented programming, Apple Computer, Inc.