
By Rick Thomas
MacTech Quarterly
Spring 1989 – Page 18
A key word in Apple’s vision for software development is “multi-tasking”. In the future, applications simply will not work with new versions of the Mac OS unless they are designed from the beginning to work cooperatively in a multi-application environment. Rumor has it that beginning with System 8.0 (due to arrive sometime in 1990), the Mac will no longer support DA’s. It’ll be “MultiFinder or Bust!” for Macintosh developers. In this article, Rick Thomas (a veteran programmer and author of a well-known bibliographic database called Pro-Cite) discusses the bottom line on making your application MultiFinder friendly.
— Editor (AH)
Introduction
MultiFinder is Apple’s newest System Software wrinkle for the Macintosh. The major new functionality available under MultiFinder is the ability to run multiple applications at once, all of them sharing the same screen or display world. MultiFinder thus provides the closest thing to multitasking available yet on the Mac, except perhaps for A/UX (Apple’s own UNIX for the Mac) which is just getting off the ground.
Apple seems to have done a great job in introducing this new functionality without greatly altering either the familiar Macintosh environment for the user nor the programming model for the developer. Indeed, many applications run correctly under MultiFinder, but many do not. Moreover, as of this writing only a few applications take full advantage of MultiFinder’s background support and are fully “MultiFinder Aware”.
If an application’s developer has been good and followed the rules laid down at various times and in various fashions by Apple, chances are that the application will run correctly under MultiFinder and that it may be modified relatively easily to take full advantage of MultiFinder. The most common features an application developer may want to utilize include proper window display when suspended, background processing, temporary memory allocation, and new goodies such as the Notification Manager. The Notification Manager will be discussed briefly at the end of this article, being relatively new. Temporary memory allocation has not been utilized by this author, and so it will be mentioned only in passing. However, the first two items mentioned, which are in themselves “noble aspirations”, are relatively easy to accomplish, and should be considered minimum requirements for “MultiFinder Awareness”, if an application is already “MultiFinder Friendly.”
But sometimes a programmer will take an undocumented and/or unapproved shortcut. Some examples of these include fussing with various low memory variables, using CopyBits to save and restore a portion of the screen while a dialog or alert is up, drawing directly to the screen rather than in windows or other grafports, messing too much with the trap table, changing status bits in memory blocks without using the proper Memory Manager calls, and assuming the size and location of the application and system heaps. Now, if you’ve done some of these so-called “bad things”, you may still be ok. In fact, if your application is already compatible with Switcher (which is no longer supported by Apple), it is probably reasonably MultiFinder friendly, since the application environment differences between Switcher and MultiFinder are minimal.
Some General Rules of the Game
First, let’s discuss some of the do’s and don’ts of being “MultiFinder friendly”. Then, I’ll describe some of the new things (WaitNextEvent, temporary memory allocation, and the new version of the SIZE -1 resource), after which I’ll present some of the techniques I used in Pro-Cite as an example of how one might provide some level of “MultiFinder Awareness”. The rules fall into one of four categories: General/Global (pun intended), Window Manager, Event Manager, and Memory Manager. I will not present an exhaustive list, but just hit the big ones. For a complete set of guidelines, you may want to acquire the MultiFinder Development Package from Apple.
General/Global Rules
• Don’t access low memory more than you have to. Things which are clearly documented in Inside Macintosh or in Technical Notes are probably ok, but Apple could decide to change them in the future, too!
• Don’t change the Apple menu except through the proper ToolBox calls. MultiFinder basically feels it owns the Apple menu and could get nasty if you change it directly rather than using more “normal” methods.
• I’ve always felt that patching traps was an ignoble thing, but maybe that’s just due to my own limited experience! Anyway, if you’re going to engage in this activity, use the SetTrapAddress calls rather than writing into the dispatch table in low memory and place your patch receiving routines in your application heap, not in the System heap. You’ll be glad you did. Also, remember to unplug all your patched traps before exiting your application, in case you’re not running under MultiFinder. MultiFinder keeps a separate trap table for each application and switches the patches for you, so it really shouldn’t be necessary to unplug your patches on suspend events.
• Don’t access your global data from within an interrupt handler, a patch receiving routine, an I/O completion routine, or a VBL task. It isn’t even safe to presume that the value in CURRENTA5 is correct! Instead, save a copy of your A5 in some data structure you’re sure you can find (such as a parameter block that’s passed in) when your routine is called. See Technical Note #180 for an example of how to do this best.
• Use the Scrap Manager to access the scrap, rather than manipulating either the low memory scrap data or the Clipboard file itself directly.
• Don’t assume that at exit time (when you call ExitToShell, or otherwise fall off the end of your program) anything goes. Don’t clear the screen with a second call to InitWindows or with PaintRgn. Don’t blow away system data structures like the WindowList before exiting.
Window Manager Rules
• Don’t modify certain Window Manager data structures (such as the visRgn, updateRgn and other Window Manager-owned fields, plus any low memory goodies) directly, rather use the proper Window Manager ToolBox calls. You can do whatever you want to with off-screen GrafPorts.
• Do all drawing to the screen within the bounds of a window your application has created with appropriate ToolBox (QuickDraw) calls. But you can do whatever you want with GrafPorts for off-screen drawing. Most game programs are notorious for not doing this. (Try Crystal Quest, V. 2.00, under MultiFinder… first click, and you’re switched out with only “some” of the Finder display surviving!)
• Don’t bypass the proper techniques for updating windows obscured by a dialog or alert after it’s dismissed. Some applications use CopyBits to do this, but under MultiFinder they might just end up putting back “old” bits from the Finder display or from another running application.
• Consider the Window Manager port to be off-limits, or at least read-only. Don’t draw on the desktop (at least, not in a destructive manner… zoom effect lines and such seem to be ok, for now). See Technical Note 194, “WMgrPortability” for the gospel on this.
Event Manager Rules (it certainly does!)
• The most important thing to do is handle update events correctly. When an application is running in the background (or at least switched out/suspended: even those applications which do not support background processing will often find themselves in this state), it will receive update events whenever the foreground application has obscured and then exposed one of its windows. Applications should not implement deferred window updating schemes but should respond (in other words, DRAW) directly upon receiving an update event.
• Null events are used by MultiFinder to provide time for background applications (assuming the canBackground bit is set in the SIZE resource.) Hence, time consuming activities such as garbage collection should not be performed on every null event received. Use TickCount to arbitrate use of null events, except for cursor tracking.
• Don’t call SystemTask on every (null) event, but rather call WaitNextEvent, which handles functions previously supported by SystemTask. Do continue to call SystemTask and GetNextEvent for non-MultiFinder situations. See the Pro-Cite code examples.
• Support suspend/resume events. They can eliminate most of the time required for a switch, since MultiFinder performs the same desk accessory charade that Switcher did, if the application doesn’t indicate (via the SIZE resource) that it supports suspend/resume events. Besides, supporting these events is the only way you can keep track of whether or not your application is suspended (and you will want to do that!).
Memory Manager Rules
• Don’t make assumptions regarding application or system heap size or location. In fact, your application should be written so that it doesn’t care where the heap is or what size it is (except when it runs out of memory, of course!). Use GetApplLimit to get at the maximum size of your heap and use SetApplLimit to resize your stack.
• Allocate additional heaps within your original heap as non-relocatable blocks or else within your stack. Consider your application’s available memory to be the application heap and stack only.
• Don’t assume into which heap a particular resource is loaded, unless it is a resource you loaded from a file you opened directly. MultiFinder dynamically loads resources from the System file and from printer resource files into structures that it maintains in the system heap.
• Try to use MultiFinder’s temporary block allocation calls for unusual needs such as copy buffers, and thus keep the normal memory requirements of your application smaller.
• Support the size (SIZE -1) resource to describe your application’s memory requirements and capabilities to MultiFinder. The minimum size should be sufficient for the application to perform some useful work while the maximum size should be no larger than that which the application uses when exercised under normal circumstances. Let the user set the size larger if needed. A preferred size of 2 megabytes is probably excessive. Maybe the FullWrite programmers would disagree…
New Wrinkles from MultiFinder
MultiFinder introduces some new system functionality in the form of background processing and temporary memory allocation and enhances existing functionality with the WaitNext-Event ToolBox call. This section is also a good time to cover the new version of the SIZE -1 resource.
WaitNextEvent
By far the most important change, and the focus of providing real MultiFinder support in an application, is the WaitNextEvent trap. An application can perform properly under MultiFinder by just continuing to use GetNextEvent, but true background task support can only really be achieved with WaitNextEvent. The prototype for this trap is:
FUNCTION WaitNextEvent(VAR theEvent : EventRecord;
theMask : EventMask;
YieldTime : INTEGER;
MouseRgn : RgnHandle) : BOOLEAN; INLINE $A860;
The first two parameters (the event record and event mask) are identical to those in the familiar GetNextEvent call. The YieldTime parameter (also called the “sleep” parameter) indicates how much time to “give up” to any background applications. It may be thought of as how many ticks for the Event Manager to “go away and visit other applications” before returning a null event to allow this application to do garbage collection type activities or possibly background processing. A value of zero will cause the Event Manager to “return” immediately after still providing some minimal time to any other processes currently active; this is essentially equivalent to the GetNextEvent case. When the time specified by YieldTime elapses, WaitNextEvent returns a null event and a return value of FALSE. If a “real” event (e.g., update event if in the background or anything else including an update event if foreground) occurs before the YieldTime value elapses, WaitNextEvent returns the event immediately, along with a return value of TRUE.
The last parameter, MouseRgn, is a handle to a region that describes the area in which the cursor may maintain its current setting, as desired by the application. If this parameter is not NIL, MultiFinder will generate a special type of an event, called a “mouse-moved” event, whenever the cursor has been moved outside of the given region. The application may then change the cursor and generate a new MouseRgn to pass to WaitNextEvent. I found that some of the regions I needed to describe were rather complex and thus I passed NIL for the MouseRgn parameter. This meant that no mouse-moved events would be generated. While the purpose of the mouse-moved events is to improve performance by allowing a larger value of yieldTime for background applications, I found that Pro-Cite’s cursor-tracking routine was simple enough that it could be executed on each event provided by MultiFinder (except when suspended, of course) and still provide adequate performance to other processes.
Background Processing
MultiFinder allows for a new class of applications called background tasks. A background task is an otherwise normal program that can use null event processing time to perform useful activity, for example sorting a database or recalculating a spreadsheet. There are even “background only” applications (such as the Backgrounder, which provides background LaserWriter printing in System 6.0 and later) which have no real user interface but exist only to perform some regular task “behind the scenes.” To truly understand how this might work, we must digress and discuss the MultiFinder programming model in a little more detail.
Without MultiFinder, most applications spend much of their time receiving null events, “waiting for something to happen.” Under MultiFinder, only the currently running (foreground) application receives all user events. When the foreground application calls GetNextEvent or (more preferably) WaitNextEvent, it can be switched out “temporarily” to provide time for other running applications or “permanently” (well, suspended) when the user wants to switch to another application, as indicated by clicking on another applications’s window or via the menu bar or Apple menu.
Be aware that there are two exceptions to “permanent” switching (i.e. suspension):
- If an application has put up a modal dialog window (dBoxproc), it will not be suspended, although it can be temporarily switched out to provide time for background applications, and
- an application will not be switched out even “temporarily” (i.e., to give time to background tasks) if the file system is busy, for example, in the case of pending asynchronous file system requests.
Device manager calls do not delay task switching, however. Task switching with regard to the file manager and device manager is subject to future change.
Applications that are running in the background will not receive user events, but only update events as they become necessary when foreground activity uncovers portions of the background application’s windows. But instead of just feeding null events to the foreground application, MultiFinder can feed processing time to any background applications, as long as the foreground application has no events pending and no unprocessed window updates. However, since there is currently no provision for “preemptive multitasking”, the background application must be sure to call GetNextEvent or WaitNextEvent at regular intervals so that the foreground task can keep up a responsive “feel” for the user. User events for the foreground application will not be handled until the background task calls GetNextEvent or WaitNextEvent.
The foreground task is not obligated to return the favor to background tasks: the needs of supporting the user are most important. Thus a background task can not expect to receive any processor time at all! This fact should be considered when deciding candidate activities for an application to perform in the background.
Temporary Memory Allocation
MultiFinder provides a temporary memory allocation service to help reduce the memory requirements for an application’s heap. It provides the ability to allocate and release blocks and to lock and unlock handles to blocks within a special MultiFinder heap zone. This service would be particularly useful for graphics or animation buffers or for disk/file or resource copying buffers. In fact, the Finder now uses these temporary memory calls for copy buffer space during file copy operations. In Pro-Cite, I found little need for this service and so did not make use of it. See the MultiFinder Development Package for full details on the temporary memory allocation features of MultiFinder.
The New SIZE -1 Resource
To be truly MultiFinder Aware, an application must include a SIZE -1 resource, as introduced in the time of Switcher. The SIZE resource both indicates an application’s memory requirements as well as its degree of MultiFinder compatibility. The format of this resource is:
resource ‘SIZE’ (-1) {
boolean saveScreen (“reserved”, for Switcher compatibility),
boolean acceptSuspendResumeEvents,
boolean enableOptionSwitch (“reserved”, for Switcher compat.),
boolean canBackground,
boolean multiFinderAware,
boolean onlyBackground,
boolean getFrontClicks,
unsigned bitstring[9] = 0 (“reserved”)
unsigned longint (Preferred Size),
unsigned longint (Minimum Size)
};
We’ll just ignore the bits marked “reserved”. Bit 11 indicates that the application is aware of MultiFinder, if set. Some current applications may function properly only when this bit is off. If this bit is set, MultiFinder will not generate activate/deactivate events for the frontmost window at resume/suspend event times, but will expect the application to do this itself, which is the most efficient way. Bit 14 indicates support for suspend/resume events if set (an application will not receive suspend/resume events unless this bit is set), while Bit 12 indicates the capability of supporting background null events for background processing (remember, all applications should be capable of supporting update events at all times!). Most applications should eventually provide support for suspend/resume events while only a few initially will be capable of background processing under MultiFinder.
Two other switches, onlyBackground and getFrontClicks, are relatively new and are discussed in Technical Note 205. Set onlyBackground TRUE (along with canBackground) for a “faceless” background task that has no user interface, like the printing Backgrounder. Set getFrontClicks TRUE if you want to receive the mouseUp and mouseDown events which occur when an application is switched to the foreground. The Finder has this switch set TRUE, so that when you click on a Finder window or icon to switch the Finder to the foreground, the object clicked on becomes the active selection. I personally don’t like this behavior for my application since it might change the current text selection.
The preferred memory size is no easier to determine under MultiFinder than it was for Switcher. It is something best determined by “inspection” (a euphemism for “trial and error.”) The minimum size should be chosen such that the application could provide the user with a minimal amount of useful work and should never (well, almost never) give a “system error”. The preferred size should be chosen such that 90% of the application’s functionality may be utilized without memory problems. The application in any case should never be too greedy with its memory requirement. Remember that the application may have to exist in harmony with a number of other applications at the same time. An application with a large preferred size such as 1024K will be looked down upon by users and developers of other applications alike, unless it’s a development system (or perhaps it’s just FullWrite.) Provide a SIZE -1 resource in your application, but not a SIZE 0 resource. The Finder will create a SIZE 0 resource by copying the SIZE -1 resource the first time a user makes changes to the Application Memory Size with the Get Info dialog.
Pro-Cite has the three options multiFinderAware, acceptSuspendResume, and canBackground all set to TRUE, a preferred memory size of 384K (which actually can be considered an operational minimum) and a minimum memory size of 224K (which is really left over from earlier support for Switcher).
Pro-Cite® — A (Mostly) MultiFinder Aware Application
Pro-Cite®, the new replacement for the Professional Bibliographic System (PBS) for the Macintosh, is a vertical market application and a bibliographic database program which has some aspirations to being a word-processor besides. When I was well along in the design and implementation of Pro-Cite in the spring of 1987, Apple made Personal Bibliographic Software a beta test site for a new version of the Macintosh System then called “Juggler” (rumor had been rampant about it anyway). Fortunately, the Professional Bibliographic System had been designed and implemented in a Macintosh standard and Switcher-friendly way so that only a few problems had to be ironed out to make it compatible with MultiFinder. As it turned out, most of the PBS processing tasks had also been written in loops that regularly called SystemTask to support desk accessories, so that MultiFinder background processing support was not all that difficult to provide. For the remainder of this article, I shall try to describe some of the code and techniques which allow Pro-Cite to be more or less fully MultiFinder Aware, and I’ll also describe support for the new Notification Manager for System 6.0, which Apple gave me the opportunity to test in early 1988. Refer to the accompanying MPW Pascal code listings to augment the descriptions as necessary. In many places, I have left out irrelevant statements to try to make the code clearer and easier to read, while in other cases I’ve left certain items in so that application developers can identify certain “common” areas of a Macintosh application. To get started, here are some “global variables” (see Listing 1) that are used in the example code listings. (Please note that this is MPW 2.0 code: the Notification Manager glue will eventually be included with the interface files for MPW 3.0, as will constants for all of the trap numbers.)
The first task before the MultiFinder programmer is to determine if MultiFinder is running in the first place, since the user can turn MultiFinder off, and with the shortage of 1 meg SIMMs currently, will probably often want to. We don’t want to write a MultiFinder-only application just yet, do we? Using the technique described in Technical Note 158, the Setup routine (see Listing 2), which is called when Pro-Cite first starts up, uses SysEnvirons to see if the 128K ROM is present, since it is required for MultiFinder, and NGetTrapAddress to see if WaitNextEvent is implemented, placing the result in the BOOLEAN variable “WNEisImplemented” (well-named, I think).
Based on the results of this check, the setup code also sets the initial value of the sleep parameter “YieldTime” to 3 as well as the foreground value for this, “frontYieldTime”, and the background value “backYieldTime” to 10. These were determined by trial and error and will be used to change the value of “YieldTime” on suspend/resume events. In System 6.0 and later, it is most desirable for the application to set the sleep parameter to something like “maxint” (32767), but for compatibility with System 4.2 and MultiFinder 1.0, in which a bug caused MultiFinder to hang when large values of sleep were used, I’ve chosen these values and limited the maximum value on sleep to 50. The user can also change these values through Pro-Cite’s Configure option (in an undocumented manner), but not all applications are expected to provide such a feature, I would think.
Finally, Pro-Cite also initializes the BOOLEAN “suspended” to FALSE, of course. The remainder of the Setup code sample initializes support for the Notification Manager, which I will describe at the end of this section.
After the initial release of Pro-Cite, I also added the BOOLEAN variable “MultiFinderUp.” Under System 6.0 and later, the flag “WNEisImplemented” would actually always be true, even if MultiFinder is currently not running. While many friends at Apple have indicated that it should not be necessary to tell if MultiFinder is actually running, it turns out that there are arguably some situations in which the running program may actually want to know. The first case that comes to mind is the default window size provided by the application when a window is first created. I prefer to leave room for the disk and Trash icons on the right side of the screen under MultiFinder, but to create a window which completely fills the screen when MultiFinder is not running. Be aware if you also decide to test for the MultiFinder trap that the test suggested above may fail in a future system release. In most cases discussed here, “WNEisImplemented” is tested, rather than “MultiFinderUp.”
The main saving grace of Pro-Cite in providing MultiFinder support is the centralization of all event-getting in the procedure “MyGetNextEvent” (see Listing 3). Actually, many Macintosh applications probably centralize event retrieval, and it certainly makes the job easier. In “MyGetNextEvent”, the setting of “WNEisImplemented” is tested: if TRUE, we’re running under MultiFinder so WaitNextEvent can safely (and should) be called; if FALSE, then we’re not running under MultiFinder so SystemTask and possibly GetNextEvent is called. As mentioned earlier, SystemTask may not be needed, but it was left in place in case a future version of Pro-Cite which does not require System 4.1 is implemented. More importantly, a BOOLEAN value “callGetNextEvent” is passed in so that background processing may be supported as described below. The result of either WaitNextEvent or GetNextEvent is placed in the BOOLEAN “haveEvent”, some other processing is done, and “MyGetNextEvent” returns.
Refer to the abbreviated sample of Pro-Cite’s MainEventLoop (see Listing 4) to see how MyGetNextEvent is used. The MainEventLoop is much as one would expect: MyGetNextEvent is called to get an event near the top and the code proceeds through a CASE statement to dispatch activity depending on the type of the event. Notice a few interesting features of the MainEventLoop with regard to MultiFinder. If Pro-Cite is suspended (the variable “suspended” is TRUE), then neither HiLiteMenu nor the CursorAdjust routine is called, since these might affect the display of the foreground application. However, CheckScrap (which makes sure that the desk scrap and internal scraps are the same as far as Pro-Cite is concerned) is called each time through the maineventloop. This really doesn’t take too long and thus scrap coercion/conversion becomes unnecessary on suspend/resume events, although others may prefer to perform scrap coercion on suspend/resume events. Notice also that all suspend/resume events (which are implemented as application-defined event type 4, or “app4Evt”) are dispatched to a routine called “DoSuspendResume”, even in the case where IsDialogEvent returns true: if you have modeless dialogs, be sure to handle suspend/resume events yourself since the Dialog Manager probably won’t do much with them. “DoSuspendResume” is the heart of Pro-Cite’s MultiFinder support (see Listing 5).
“DoSuspendResume” is called, as one might expect, whenever Pro-Cite receives a suspend event or a resume event. It first checks which window is frontmost via FrontWindow, placing the windowkind of the frontmost window into a global “docType” that is used literally everywhere. It then sets “suspended” based on whether or not the message field of the event is odd: if it’s odd, it’s a resume event so we’re no longer suspended; if not, it’s a suspend event so we’re about to be suspended. (The temporary setting of suspended to FALSE is merely to allow SetHourGlass to turn on Pro-Cite’s hourglass cursor.) If it’s a suspend event, then we fake up a deactivate event for the frontmost window, set our YieldTime to the background value, call our activate routine (“MyActivate”), and if one of “our” windows, we also cause an update event just to make sure things are clean (and immediately process it by calling “DrawWindow”). If it’s a resume event we do much the same thing, except we fake up an activate event, we set our YieldTime to its foreground value, and we also do a DrawMenuBar (mostly for Switcher). Notice that when handling the activate/deactivate events, we need to pass them to IsDialogEvent (and maybe DialogSelect) if the window is a modeless dialog or to SystemEvent if the frontwindow is a desk accessory which the user has opened in our application’s heap instead of the DA layer.
Background processing in Pro-Cite is implemented by the “GetAnEvent” procedure (refer to Listing 4) which is built on top off “MyGetNextEvent”. It is merely a small version of the MainEventLoop that only supports suspend/resume events as well as activate and update events. From a process which Pro-Cite wants to support in the background (such as sorting or formatting records), “GetAnEvent” is merely called regularly with the “callGetNextEvent” FALSE (since if we’re not under MultiFinder we just want to call SystemTask) and with the event mask for only app4Evt + keydownEvt, the latter to allow Command-period to interrupt the process. The process will continue during null events, plus any windows obscured and then exposed by the foreground task will continue to be updated, and finally a resume event will be processed when the user “clicks back” into Pro-Cite. “MiniMainEvent” is a similar sort of thing, called by Pro-Cite to bring windows to the front when necessary and to combat “display anomalies” in certain places. It is also called by setup right after FlushEvents to make sure that initial events from MultiFinder at startup are processed immediately: if this isn’t done, one could find any dialog or “splash screen” initially produced by the application coming up BEHIND the layer (usually Finder) from which the application launched!
Finally, a few words about the Notification Manager, which is available in 6.0 and later releases of the Macintosh System Software. The Notification Manager is not really an implementation of inter-process or inter-application communication: that will probably come in some form in System 7.0 or later. It is, however, a means by which an application running in the background can let the user interacting with the current foreground application know that the background application requires attention by a sound, a flashing small icon alternating with the Apple menu icon, a diamond mark on the application’s name in the Apple menu, or an alert, or any combination of these. Pro-Cite provides for either sound or the flashing small icon, and it also provides a means for the user to enable or disable these (an idea to keep in mind, if you’re going to provide Notification Manager support.) Notification Manager support is actually trivial to provide, if you’ve accomplished “MultiFinder Aware” support.
Referring as needed to Listing 6, notice that a record type “NMRec” has been declared for Notification Manager support, and the accompanying variable is “nmforProCite”. It is just a standard Macintosh Operating System queue element. This record is initialized in the Setup procedure. The qType is just set to ORD(nmType) which is 8 (in fact, Pro-Cite uses this variable to indicate if a Pro-Cite Notification Manager task is currently active.) The nmMark value is set to 1 to provide a diamond mark in the Apple menu: Pro-Cite always does this and this behavior isn’t user-configurable. The nmStr pointer is set to NIL: it provides for a string to be displayed in an alert and Pro-Cite doesn’t support this feature. The notification response procedure pointer, nmResp, is set to @NMResponse. This procedure does absolutely nothing in Pro-Cite as yet, but must be present (or the response procedure parameter must be set to NIL) else the flashing icon and menu bar mark will go away immediately after the notification sound occurs! Developers probably will find more for it to do in future applications.
Pro-Cite sets up a Notification Manager task by calling its routine “MyNotify” whenever it has completed a process in the background, such as formatting or sorting records, or when it has encountered an error condition while processing in the background and wants to bring up an error message alert. Pro-Cite could just use the Notification Manager to bring up the alert immediately in the foreground layer, but I think the way I’ve done it is much less obtrusive. Pro-Cite merely sets up the task with “MyNotify” (checking the variable “notifylevel” to see if the user wanted sound and/or icon, loading the small icon if necessary and setting the nmSIcon handle to it), calls NMInstall to install the queue element, and then hangs around in the MainEventLoop or with MiniMainEvent, whichever is convenient, until the user switches back in. When the user does come back in, Pro-Cite kills the Notification Manager task when processing the resume event by just calling NMRemove in “DoSuspendResume.” And that’s all there is to it!
A Brief Good-Bye, and Good Luck!
In this article I’ve tried to present what I feel are important points to remember when trying to provide MultiFinder support within an application, as disclosed to me by Apple during testing of various versions of MultiFinder, through the documentation that is available, and as discovered by me when trying to accomplish the darn task in the first place. This is by no means an exhaustive list, nor may the techniques described herein be entirely accurate or sufficient for the needs of any given application (there are so many Macintosh applications!) Moreover, as more and more applications become “MultiFinder Aware” and as new versions of MultiFinder are released, some of the techniques for MultiFinder support described here may no longer be sufficient. I would refer you to both present and future versions of Inside Macintosh, the MultiFinder Development Package, plus the Technical Notes (especially 158, Frequently Asked MultiFinder Questions; 180, MultiFinder Miscellanea; 184, Notification Manager; 194, WMgrPortability; and, 205, MultiFinder Revisited, The 6.0 System Release), for more information. Be sure to also consult any Technical Notes listed under the “Compatibility” subject in Technical Note 0, “About Macintosh Technical Notes.”
I hope that I have given you an idea of what it’s like to provide MultiFinder support in an application and that I have convinced you to go ahead and do it. I think it’s a good idea that all applications strive to support MultiFinder as much as possible so as to provide as rich an environment as we can for the Macintosh user. At least until the next major Macintosh System release. Good luck.
Rick Thomas fell in love with programming computers during a graduate school program that involved environmental simulation. Before the Mac was introduced he had seven years of experience in programming the Apple II in Pascal, Basic, and 6502 assembly language. Since 1975 he has been Manager of Software Development for Personal Bibliographic Software (recently upgraded to Corporate Visionary).
Listing 1
{Global Stuff:}
CONST
WNETrapNum = $60;
MFTrapNum = $8F;
UnImplTrapNum = $9F;
CurrentA5 = $904; {low memory global}
TYPE
NMRec = RECORD {for Notification Manager in System 6.0 or higher}
qLink : QElemPtr;
qType : INTEGER;
nmFlags : INTEGER;
nmPrivate : LongInt;
nmReserved : INTEGER;
nmMark : INTEGER;
nmSIcon : Handle;
nmSound : Handle;
nmStr : StringPtr;
nmResp : ProcPtr;
nmRefCon : LongInt
END;
VAR
theWorld : SysEnvRec; { system info. from
SysEnvirons call in
Setup }
MyEvent : EventRecord; { returned by
GetNextEvent }
docType, { windowKind of
currently active
window }
YieldTime, { yieldtime ticks
for WaitNextEvent }
frontYieldTime, { foreground value
for YieldTime }
backYieldTime : INTEGER; { background value
for YieldTime }
WNEisImplemented, { TRUE if
WaitNextEvent is available }
MultiFinderUp, { TRUE if running
under MultiFinder }
suspended, {are we suspended
(under MultiFinder)?
doneFlag, { set when the user
quits the program }
haveEvent { well, do we have
an event? }
: BOOLEAN;
nmforProCite : NMRec; { Notification
Manager record
(System 6.0 and
higher) }
{temporary glue for Notification Manager routines (System 6.0 and higher)}
FUNCTION NMInstall (nmReqPtr : QElemPtr) : OSErr;
INLINE $205F, $A05E, $3E80;
FUNCTION NMRemove (nmReqPtr : QElemPtr) : OSErr;
INLINE $205F, $A05F, $3E80;
Listing 2
PROCEDURE SetUp;
…
BEGIN
This code is ‘discarded’ after it is executed by an
UnLoadSeg.}
FlushEvents(everyEvent,0); {start with a clean
slate}
MiniMainEvent; {for pending MultiFinder stuff}
InitFonts; {I need fonts}
TEInit; {I need TextEdit}
InitDialogs(NIL); {and I need dialogs, even when
finderprinting}
InitMenus; { initialize Menu Manager }
…
suspended := FALSE;
WITH theWorld DO
BEGIN
io := SysEnvirons(1, theWorld);
IF (io<>noErr) OR (systemVersion<$0410) THEN
{Can’t run!}
BEGIN
{Error message: requires version 4.1}
ExitToShell
END;
unimptrapnum := NGetTrapAddress(UnImplTrapNum,ToolTrap);
WNEIsImplemented := (machineType>=0) AND {>= 128K
ROMs}
(NGetTrapAddress(WNETrapNum,ToolTrap) <>
unimptrapnum);
MultiFinderUp := (machineType>=0) AND {>= 128K ROMs}
(NGetTrapAddress(MFTrapNum,ToolTrap) <>
unimptrapnum);
YieldTime := 3;
frontYieldTime := 3;
backYieldTime := 10;
WITH nmforProCite DO {set up Notification Manager
record}
BEGIN
qType := 0; {will be set to ORD(nmType)=8
when active}
nmMark := 1;
nmStr := NIL;
nmResp := @NMResponse;
IF MultiFinderUp THEN
IF systemVersion>=$0600 THEN notifylevel :=
3
ELSE notifylevel := 1
ELSE notifylevel := 0
END;
END;
Listing 3
PROCEDURE MyGetNextEvent (EventMask : INTEGER;
callGetNextEvent : BOOLEAN);
BEGIN
IF WNEisImplemented THEN
haveEvent := WaitNextEvent(EventMask, MyEvent,
YieldTime, NIL)
ELSE
BEGIN
SystemTask;
IF callGetNextEvent THEN
haveEvent := GetNextEvent(EventMask, MyEvent)
ELSE haveEvent := FALSE
END;
END;
Listing 4
{——————————————————————————}
PROCEDURE GetAnEvent {(EventMask : INTEGER;
callGetNextEvent : BOOLEAN)};
VAR tempwindow : WindowPtr; itemHit : INTEGER;
{called by MiniMainEvent; called elsewhere to get an
event during a process (especially important for
background processing under MultiFinder)}
BEGIN
MyGetNextEvent(EventMask, callGetNextEvent);
IF haveEvent THEN
WITH MyEvent DO
IF what=app4Evt THEN DoSuspendResume
ELSE
BEGIN
IF IsDialogEvent(MyEvent) THEN {Find/Replace
Dialog event?}
BEGIN
IF DialogSelect(MyEvent, tempwindow,
itemHit) THEN;
IF (what=updateEvt) OR
(tempwindow<>FRDialog)
OR (FRDialog=NIL) THEN what := nullEvent
END;
IF what=activateEvt THEN MyActivate
ELSE IF what=updateEvt THEN DrawWindow;
UnloadSeg(@DrawWindow)
END
END;
{————————————————————————————————}
PROCEDURE MiniMainEvent;
CONST MyEventMask = activMask + updateMask + app4Mask;
{called by MakeBib and FormatFrame to bring bibwindow
to front if it isn’t
already or upon creating it. Called from other
places to combat “anomalies”}
BEGIN
haveEvent := TRUE;
WHILE haveEvent DO GetAnEvent(MyEventMask, TRUE)
END;
{——————————————————————————}
PROCEDURE MainEventLoop;
WITH MyEvent DO
REPEAT
IF printflag THEN {call printing segment}
IF formflag THEN {format selected records}
BEGIN
FormatFrame;
UnloadSeg(@FormatFrame);
…
IF suspended THEN MyNotify
END;
IF NOT (doneflag OR suspended) THEN
BEGIN
HiLiteMenu(0);
CursorAdjust {check and maybe set the cursor}
END;
IF CheckScrap THEN {nothing}; {makes sure scrap is
current}
MyGetNextEvent(EveryEvent, TRUE);
IF IsDialogEvent(MyEvent) THEN {Find/Replace Dialog
event?}
IF (FRDialog<>NIL) AND (what<>app4Evt) AND
(what<>diskEvt) THEN
BEGIN
…
haveEvent := FALSE
END;
IF haveEvent THEN
CASE what OF
mouseDown: …
keyDown, autoKey: …
activateEvt: MyActivate;
updateEvt: DrawWindow;
diskEvt: …
app4Evt : DoSuspendResume
END; { CASE what OF }
…
{UnloadSegs}
UNTIL (doneFlag AND (FrontWindow=NIL)) OR printing;
END;
Listing 5
PROCEDURE DoSuspendResume;
{Handle suspend and resume events from
MultiFinder/Switcher}
VAR tempwindow : WindowPtr; err : INTEGER;
BEGIN
WITH MyEvent DO
BEGIN
tempwindow := FrontWindow;
IF tempwindow=NIL THEN docType := 0
ELSE docType := WindowPeek(tempwindow)^.windowKind;
suspended := FALSE;
SetHourGlass; { = SetCursor(watchhdl^^)}
suspended := NOT ODD(message);
doneflag := FALSE;
closeall := FALSE;
IF suspended THEN {suspend event}
BEGIN
YieldTime := backYieldTime;
BitClr(@modifiers, 15); {DEactivate event}
…{turn off Pro-Cite’s database window buttons
here!}
END
ELSE {resume event}
BEGIN
WITH nmforProCite DO
IF qType>0 THEN
BEGIN
err := NMRemove(@nmforProCite);
IF notifylevel>1 THEN
ReleaseResource(nmSIcon);
qType := 0
END;
DrawMenuBar; {in case screen save is off}
YieldTime := frontYieldTime;
BitSet(@modifiers, 15) {activate event}
END;
IF docType>0 THEN
BEGIN
message := LongInt(tempwindow);
what := activateEvt;
MyActivate;
IF docType>=ClipBoard THEN {it’s one of
Pro-Cite’s windows}
BEGIN
InvalRect(tempwindow^.portRect);
what := updateEvt;
DrawWindow
END
ELSE IF docType=dialogKind THEN
IF IsDialogEvent(MyEvent) THEN
IF DialogSelect(MyEvent, tempwindow,
button) THEN; {nothing}
END
ELSE IF SystemEvent(MyEvent) THEN; {nothing else,
it’s a DeskAcc}
IF suspended THEN
BEGIN
KillHourGlass;
InitCursor
END
END
END;
Listing 6
PROCEDURE NMResponse(MynmReqPtr : QElemPtr);
{response procedure for Notification Manager (System
6.0 or higher)}
BEGIN
{does nothing currently}
END;
PROCEDURE MyNotify; {notification procedure to use
Notification Manager}
VAR i : INTEGER;
BEGIN
IF theWorld.systemVersion<$0600 THEN {not
available}
BEGIN
IF notifylevel>0 THEN SysBeep(10)
END
ELSE
WITH nmforProCite DO
BEGIN
IF notifylevel>1 THEN nmSIcon :=
GetResource(‘SICN’, 1024)
ELSE nmSIcon := NIL;
IF ODD(notifylevel) THEN nmSound :=
Handle(ORD4(-1))
ELSE nmSound := NIL;
qType := {ORD(nmType)} 8;
i := NMInstall(@nmforProCite)
END
END;
(* To use MyNotify:
{before bringing up alert/error message/finish
process message/etc.:}
IF suspended THEN
BEGIN
MyNotify;
WHILE suspended DO MiniMainEvent
END; *)