Programming MultiFinder
Volume Number: 3
Issue Number: 11
Column Tag: Forth Forum
Programming for MultiFinder 
By Jörg Langowski, MacTutor Editorial Board, Grenoble, France
Forth background processing under MultiFinder
As of October, there is a new operating system for the Macintosh which comes
pretty close to real multitasking. The MultiFinder, also known as Juggler, was
introduced; many of you are probably familiar with its operation by now.
This month we’ll see how to interface Mach2 and MacForth applications to the
MultiFinder so that they use as little memory as possible, are friendly to other
applications running in the same environment, and keep doing their job while they’re
in the background.
Summary of FORTH multitasking
Multitasking Forth systems like Mach2 or MacForth, even before the advent of
Juggler, already had a mechanism built in that is very similar to what Juggler does.
When a task has nothing to do for a while, at certain strategic points in the program it
passes control back to the task scheduler, who then calls up the next task.
This is not real multitasking since a task can be nasty and never let loose of the
CPU once it has gained control, effectively stopping all other activity. In a real
multitasking system, an interrupt-driven task scheduler would give control to the
other processes anyway, avoiding such a dead-end situation. In a pseudo-multitasking
environment such as Mach2 or MacForth, the task itself has the responsibility of not
staying active for too long and letting the others have a turn. (The following examples,
again, refer to Mach2. The same or similar Forth routines are provided in MacForth,
so there should be no difficulty in transposing between the two.)
Task scheduling in Mach2 is done in a way largely transparent to the
programmer. Words used for I/O routines such as KEY, ?TERMINAL, EMIT, and others,
will contain a PAUSE, which is a word that returns control to the task scheduler. The
idea being, of course, that during I/O operations there will usually be spare time to do
other things. For long calculations without I/O in between, one would have to provide
additional PAUSEs inside time-consuming loops.
What does PAUSE do exactly? I am not going to give you a disassembly of the task
scheduler here (you could easily do that by yourself), but the task swapping is very
simple and described in detail in the Mach2 manual. The registers used by the task are
saved on the stack, and the next task entered or skipped depending on whether its
STATUS field contains the value WAKE or SLEEP. WAKE, in fact, is the 16-bit value of
a TRAP#n instruction, where the exception vector points to the task scheduler
routine, while SLEEP is simply the value of a JMP instruction (with the address of the
nex task directly following), so that we’ll jump to the next task without doing
anything.
For tasks which are awake, the trap routine will then restore the registers and,
if necessary, execute the vectored menu, control, or user data handling routines. After
having done this, control will be given to the task, until PAUSE is called again.
One default Mach2 task, which is present in any Mach2 application, is the I/O
task. It functions as the main event loop for the program and will distribute events to
the tasks that they belong to (e.g. for a mouseclick it will determine which window it
occurred in and call the event handling routines that belong to the task that owns the
window). The source code of the I/O task is contained in the newest releases of Mach2
and therefore open to any modification, which is going to be helpful for the interfacing
with MultiFinder that we’re about to do.
MultiFinder’s multitasking strategy
If you substitute GetNextEvent for PAUSE in the previous paragraph, you get a
pretty good idea of what MultiFinder is doing. For Macintosh applications, the interface
to the multitasking environment is this well-known trap, GetNextEvent. The Macintosh
operating system follows a strategy very similar to that of the Forth multitasker:
when GetNextEvent is called, it is assumed that the application has finished one
(small) part of its work and is asking what to do next. If there are several null events
in a row, MultiFinder assumes there is nothing more to do in the foreground and
transfers control to a background application. This application then will be given a
null event, and it is assumed that during such null events the program is doing some
useful background activity. The background application will also receive update events
if the window arrangement has changed. GetNextEvent, of course, had to be patched to
implement the task switching strategy.
I am trying to outline it for you in the following, although I do not have all the
necessary information; the MultiFinder development package, APDA document
#KMSMFD, gives only hints as to what is happening. Nevertheless, if you are thinking
of developing programs that take full advantage of the MultiFinder, you should of
course get this brochure, full of development guidelines. Another part of the following
information came from posts on BIX and other bulletin boards.
First, context switching from the foreground to any background application,
while the foreground stays idle, will involve saving the registers and program counter
of the CPU. Furthermore, applications might have accessed and changed low memory
globals; so a copy of the Toolbox globals area is saved with each application, just like
in Switcher. In addition, MultiFinder is intelligent enough to notice any trap patching
that is going on when the application starts up for the first time (I suppose
MultiFinder assumes that all this is taking place before the first call to GetNextEvent,
or looks at the SetTrapAddress calls to see which changes are made). Those trap patches
are also saved with the application, and changed appropriately on context switching. All
this necessary switching information is saved in PCBs (process control blocks), which
are located in memory just under the applications and just above the free memory and
the system heap.
‘Context switching’ therefore means that the background application will have
full control over the Macintosh, including any trap or low memory patches that have
been made. The drawback is that there is quite a lot of switching overhead due to all the
low memory stuff that has to be moved. (In the future, those problems will be taken
care of by a memory management unit).
Clicking on any window that belongs to a background application will move that
window to the front, activate it and move the previous foreground task into the
background (just like Mach2 did all the time). In fact, together with the window that
was clicked, all other windows associated with that same application are also moved in
front of all the others. Such a set of windows that belong to one application is called a
Layer. A new set of Toolbox routines, the Layer Manager, takes care of updating and
maintaining the different window sets in a correct fashion.
When a background application is moved into the foreground, not only contexts
are switched between the new and the old foreground application: also, a sequence of
events occurs that seems to be very similar to the way Switcher changed from one
application to another. In order to ensure correct scrap handling, any internal scrap
maintained by the application has to be moved into the clipboard in a format that other
applications can understand. This procedure is called scrap coercion and can be done in
two different ways.
The recommended way is to implement Suspend/Resume events in the program.
These events have an event code of 15 in the what field of the event record, with bit 0
set in the message field for Resume and cleared for Suspend events. When a Suspend
event is received by the application, it should convert its internal scrap (i.e. the TE
scrap) to the desk scrap, and on a Resume event take whatever is in the desk scrap and
convert it to its internal format.
Applications that don’t understand Suspend/Resume events are forced to go
through a more time-consuming process on switching: MultiFinder, like Switcher,
simulates cutting/pasting to an imaginary desk accessory. Applications that support
desk accessories have to do scrap coercion on that occasion, and the clipboard will be
updated.
A third activity that has to occur is that the new frontmost window has to be
activated while the old front window gets deactivated. MultiFinder will automatically,
together with the Resume event, feed an activate event to the proper window when it is
moved into the foreground, while the window going to the background receives a
deactivate event. However, applications that fully support MultiFinder will not need
these activate/deactivate events since they can do the necessary activations and
deactivations on the Resume and Suspend events.
16-bit flag word:
bit(s) use
15 unused by MultiFinder
14 0 = application does not understand suspend and resume events
1 = application understands suspend and resume events
13 unused by MultiFinder
12 0 = application cannot do backgrounding
1 = application supports backgrounding
11 0 = application is not MultiFinder Aware
1 = application is MultiFinder Aware
0-10 unused by MultiFinder
32 bit word:
preferred memory size
32 bit word:
minimum memory size
Table 1: The SIZE ID=-1 resource.
To indicate the level of compatibility with the MultiFinder, the SIZE ID=-1
resource (table 1), known from Switcher, contains some new flags. Bit 14 of its
16-bit flag word indicates whether the application supports Suspend/Resume events
(like under Switcher); bit 12 indicates whether the application should be periodically
called and given a null event while the foreground task is idle; and bit 11 tells whether
the necessary activation and decativation of the frontmost window are done
automatically on Suspend/Resume events or whether additional activate/deactivate
events have to be provided by MultiFinder. If your application is fully MultiFinder
compatible, all of these bits should be set.
The two long words following the flag word indicate the preferred and minimum
memory size, as before under Switcher.
Making Mach2 programs run in the background
The most interesting thing about MultiFinder is that it allows most existing
applications to continue running in the background, if they are set up to perform
activities while null events are received. Programs written in the Mach2 multitasking
system are perfectly adapted to background processing, since the Forth multitasker
keeps on going as long as each task calls PAUSE regularly. Events are taken care of by
the I/O task and automatically distributed between the various other tasks. Null events
will just transfer control from the I/O task to the next task in the list (see the main
event loop at the end of listing 1; when a null event is received, the IOtask executes a
PAUSE). Therefore, when a Mach2 application receives a null event, all its tasks will
execute once in a row, until the IOtask gets control again and calls GetNextEvent. This
will happen no matter whether the Mach2 program runs in the foreground or in the
background.
So, even if your Mach2 program does not support suspend and resume events and
the automatic window activation and deactivation on switching, you can still have it run
in the background. Simply setting the canBackground bit, bit 12 in the flag word of the
SIZE resource, will keep the program running. To illustrate this, you should now load
the Reflections example from the Mach2 demo disk (Listing 2), which draws nice line
patterns in a small window. The main loop contains a PAUSE so that this program is
polite and lets the other tasks have a turn. TURNKEY the program and then change its
canBackground bit to 1, using ResEdit. Also, set the preferred memory size to 100K
and the minimum size to 80K; this is largely sufficient, and the default given by the
Mach2 system is much higher. You can keep a SIZE resource with these settings in the
MACH.RSRC file so that it will automatically be added to any turnkey application.
I suppose your Macintosh is running Juggler at this moment, if not, you’re out of
luck. After you’ve turnkeyed and ResEdited the demo program, start it and you’ll see
the patterns moving in the window; then click on any Finder window to move it to the
foreground. You’ll notice that the lines continue to move in the background.
WaitNextEvent and modification of the IOTask
Satisfied? There is more we can do. The way our demo runs now, MultiFinder
will wait until two successive null events have been received through GetNextEvent and
on the third one transfer control to a background task. As long as the foreground task
stays idle, the background tasks will be called one after the other. However, the
foreground application still receives control periodically, getting a null event. This is
because a priori we don’t know how long we may take control away from it, and it
might have to do some ‘idling’ activities itself, such as blinking the caret. If the
program had been properly designed to work with MultiFinder, we could have used a
new trap that tells the system how long it may stay away before re-transferring
control to the foreground application. This trap is called WaitNextEvent, and it is
called as follows (Pascal syntax):
function WaitNextEvent ( eventMask: Integer; VAR theEvent:
EventRecord; sleep: longint; mouseRgn: RgnHandle): BOOLEAN;
Its trap code is $A860. The Forth interface to this trap is given in listing 1.
WaitNextEvent called with sleep and mouseRgn equal to 0 works just exactly like
GetNextEvent. However, when sleep contains a nonzero value, that is supposed to be the
number of ticks that the application may be ‘put to sleep’ by MultiFinder before being
called again when there is no non-null event waiting. This means, if during idling you
just want to blink the cursor twice a second, you may call WaitNextEvent with a sleep
value of 30. When a non-null event like a key down or mouse click occurs, control will
be transferred to the foreground routine immediately.
The mouseRgn region handle indicates a region inside which the cursor does not
have to be changed upon mouse movement. When this handle is nonzero, any mouse
movement outside the given region will generate a ‘mouse-moved’ event, which is an
app4event (like Suspend and Resume) with event code=15 and $FA in the high byte of
the event message. When mouseRgn is nil, no mouse-moved events will be generated.
The call to GetNextEvent is made by the IOTask under Mach2. We can now modify
the IOTask in such a way that WaitNextEvent will be used instead. Listing 1 gives the
details.
First, we have to verify that Juggler is running and the call to WaitNextEvent is
a valid trap call. This can be done easily by comparing the trap address of
WaitNextEvent to that of UnknownTrap ($A89F), which is never implemented. If the
two values are different, Juggler is installed. If they are the same, we’ll have to call
good old GetNextEvent.
Of course, this comparison won’t be done on every GetNextEvent call. We should
set a flag at the beginning of our program that indicates the state of the system. The
initialization code of a Mach2 application, however, is not readily accessible, so we
catch the first call to the Forth word GetNextEvent and determine at that point whether
we can WaitNextEvent or not. We then set a flag accordingly, which will be checked on
every following GetNextEvent call. If we call WaitNextEvent, we won’t have to call
SystemTask periodically, either; its function is taken over by WaitNextEvent.
With this modification the IOTask will take care of returning control to other
background applications when null events are occurring. You might now think that one
should put a value of some 3 to 30 ticks into the sleep parameter, since we don’t need
to come back so often when nothing special is happening. This is not so; according to my
experimentation, the maximum sensible value for sleep in the IOTask event loop is 1