The Macintosh Operating System, the Finder, and several other system software components work together to provide a multitasking environment in which a user can have multiple applications open at once and can switch between open applications as desired. To run in this environment, however, your application must follow certain rules governing its use of the available system resources. Because the smooth operation of all applications depends on their cooperation, this environment is known as a cooperative multitasking environment.
Although a number of documents and applications can be open at the same time, only one application is the active application. The active application is the application currently interacting with the user; its icon appears at the right side of the menu bar. The active application displays its menu bar and is responsible for highlighting the controls of its frontmost window.
The Operating System schedules the processing of all applications and desk accessories, known collectively as processes. When a user opens an application, the Operating System loads the application code into memory and schedules the application to run at the next available opportunity, usually when the current process relinquishes the CPU. In most cases, the application runs immediately (or so it appears to the user).
When your application is first launched, it is the foreground process. Usually the foreground process has control of the CPU and other system resources, but it can agree to relinquish control of the CPU if there are no events (other than null events) pending for it. A process that is open but that isn’t currently the foreground process is said to be a background process.
A background process can receive processing time when the foreground process makes an event call (that is, calls WaitNextEvent or EventAvail) and there are no events pending for that foreground process. The Process Manager sends a null event to the background process, thereby informing it that it is now the current process and can perform whatever background processing it desires. The background process should make an event call periodically in order to relinquish the CPU and ensure a timely return to foreground processing when necessary.
The CPU is available only to the current application, whether it is running in the foreground or the background. The application can be interrupted only by hardware interrupts, which are transparent to the application. However, to give processing time to background applications and to allow the user to interact with your application and others, you must periodically call the Event Manager’s WaitNextEvent or EventAvail function to allow your application to relinquish control of the CPU for short periods. By using these event routines in your application, you allow the user to interact not only with your application but also with other applications.
The method by which the available processing time is distributed among multiple processes is known as context switching (or just switching). All switching occurs at a well-defined time, namely, when an application calls WaitNextEvent. When a context switch occurs, the Process Manager allocates processing time to a process other than the one that had been receiving processing time. Two types of context switching may occur: major and minor.
A major switch is a complete context switch: an application’s windows are moved from the back to the front, or vice versa. In a major switch, two applications are involved, the one being switched to the foreground and the one being switched to the background. The Process Manager switches the A5 worlds of both applications, as well as the relevant low-memory environments. If those applications can handle suspend and resume events, they are so notified at the time that a major switch occurs.
A minor switch occurs when the Process Manager gives time to a background process without bringing the background process to the front. The two processes involved in a minor switch can be two background processes or a foreground process and a background process. As in a major switch, the Process Manager switches the A5 worlds and the low-memory environments of the two processes. However, the order of windows is not switched, and neither process receives either suspend or resume events.
When the frontmost window is an alert box or modal dialog box, major switching does not occur, although minor switching can. To determine whether major switching can occur, the Operating System checks (among other things) whether the window definition procedure of the frontmost window is dBoxProc, because the type dBoxProc is specifically reserved for alert boxes and modal dialog boxes. (If the frontmost window is a movable modal dialog box, major switching can still occur.)
Specifying Processing Options
To take full advantage of the cooperative multitasking environment provided by the Macintosh system software, you need to inform the Operating System about the processing capabilities and requirements of your application. You need to indicate, for example, the partition size your application needs in order to execute most effectively. You also need to indicate whether your application can do any processing while it is in the background. If it cannot do any background processing, there’s no use in having the Process Manager give your application access to the CPU while it’s in the background.
You specify these and other processing options to the Operating System by including in your application’s resource fork a resource of type 'SIZE', known as its size resource. The size resource contains several long integers and many flag bits, which together give the Process Manager the information it needs to launch your application and control its processing.
A 'SIZE' resource consists of a 16-bit flags field, followed by two 32-bit size fields. The flags field specifies operating characteristics of your application, and the size fields indicate the minimum and preferred partition sizes for your application. The minimum partition size is the actual limit below which your application will not run. The preferred partition size is the memory size at which your application can run most effectively. The Operating System attempts to secure this preferred amount of memory when your application is launched. If that amount of memory is unavailable, your application is placed into the largest contiguous block available, provided that it is larger than the specified minimum size.
When you define a 'SIZE' resource, you should give it a resource ID of –1. A user can modify the preferred size in the Finder’s information window for your application. If the user does alter the partition size, the Operating System creates a new 'SIZE' resource having a resource ID of 0 in your application’s resource fork. At application launch time, the Process Manager looks for a 'SIZE' resource with ID 0; if this resource is not found, the Process Manager uses your original 'SIZE' resource (with ID –1). This new 'SIZE' resource is also created when the user modifies any of the other settings in the resource.
The numbers you specify as your application’s preferred and minimum partition sizes depend on the particular memory requirements of your application. Your application’s memory requirements depend in turn on the size of your application’s A5 world, heap, and stack.
You can usually make a fairly reliable estimate of the size of your application’s A5 world by determining the size of your application’s global variables and its jump table (whose size you can determine by looking at the size of your compiled application’s 'CODE' resource with ID 0). You can also make a good guess about the size of your application’s static heap objects—objects that are always present during the execution of your application (for example, code segments, Toolbox data structures for window records, and so on).
It’s a little bit more work to determine the amount of space you’ll need to reserve for dynamic heap objects. These include objects created on a per-document basis (which may vary in size proportionally with the document itself) and objects required for specific commands or functions. Perhaps the best advice to follow in determining your application’s minimum and preferred partition sizes is to experiment with reasonable values and make sure that there is always enough memory to meet reasonable requests from the user. You can also use tools such as MacsBug’s heap-exploring commands to help empirically determine your application’s dynamic memory requirements.
Handling Suspend and Resume Events
Your application receives suspend and resume events as a result of changes in its processing status. When your application is in the foreground and the Process Manager wants to switch it into the background, the Process Manager sends it a suspend event. This is a signal to your application to prepare to be switched out. Your application isn’t actually switched out immediately. Instead, the Process Manager gives your application a chance to handle the suspend event. Your application is switched out at the next event call it makes. Similarly, the application that is about to be switched into the foreground is sent a resume event once it’s actually switched. The resume event is a signal to that application that it can resume normal foreground processing.
Upon receiving a suspend event, your application should deactivate the front window, remove the highlighting from any selections, and hide any floating windows. Your application should also convert any private scrap into the global scrap, if necessary. If your application shows a window that displays the Clipboard contents, you should hide this window also, because the user might change the contents of the Clipboard before returning to your application. Your application can also do anything else necessary to get ready for a major switch. Then your application should call WaitNextEvent to relinquish the processor and allow the Operating System to schedule other processes for execution.
Upon receiving a resume event, your application should activate the front window and restore any windows to the state the user left them in at the time of the previous suspend event. For example, your application should show scroll bars, restore any selections that were previously in effect, and show any floating windows. Your application should copy the contents of the Clipboard and convert the data back to its private scrap, if necessary. If your application shows a window that displays the Clipboard contents, you can update the contents of the window after reading in the scrap. Your application can then resume interacting with the user.
Responding to a suspend or resume event usually involves activating or deactivating windows. If you set the acceptSuspendResumeEvents flag and the doesActivateOnFGSwitch flag in your application’s 'SIZE' resource, your application is responsible for activating or deactivating its windows when it handles suspend and resume events.
The procedure DoOSEvent is called by the main event loop whenever the what field of an event record contains the constant osEvt. You need to inspect the message field of that event record to determine what kind of operating-system event you’ve received.
If the event is a suspend or resume event, you then need to examine bit 0 to determine whether that event is a suspend or resume event. (Bits 0 and 1 are meaningful only if bits 24–31 indicate that the event is a suspend or resume event.) You can use the resumeFlag constant to determine whether the event is a suspend or resume event. If the event is a resume event, you can use the convertClipboardFlag constant to determine whether Clipboard conversion from the Clipboard to your application’s scrap is required.
If the event is a mouse-moved event, DoOSEvent ignores the event, treating it like a null event. If the event is a suspend or resume event, DoOSEvent then activates or deactivates the front window, depending on whether the event is a resume or a suspend event.
Handling Null Events
Recall that the Event Manager sends your application a null event when there are no other events to report. The WaitNextEvent function reports a null event by returning a function result of FALSE and by setting the what field of the event record to nullEvt.
When your application receives a null event, it can perform idle processing. Your application should do only minimal processing in response to a null event, so that other processes can use the CPU and so that the foreground process (or your application, when it is in the foreground) can respond promptly to the user. For example, if your application is in the foreground when it receives a null event, you can make the insertion point blink in the active window (if your application supports text entry).
If your application receives a null event in the background, it can perform tasks or do other processing while in the background. However, your application should not perform any tasks that would slow down the responsiveness of the foreground process. Your application also should not interact with the user if it is in the background.