Drivers
Volume Number: 7
Issue Number: 7
Column Tag: C Workshop
Addicted to DRiVeRs 
By Dan Green, Manassas, VA
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
Evolution of a Desk Accessory or Addicted to DRiVeRs
MenuMem is the name that I have given to a desk accessory that I created one
weekend back in ’87. It’s function is to display the amount of free memory (in the
application heap) in the menu bar (in K). Sort of like the way those clock INITs keep
the time in the menu bar.
A Brief History
Actually, when I first came up with the idea, I wanted to make an INIT, but the
only way that I could think of to update the menu was to install a VBL task. Since the
Memory Manger needs to be called, a VBL task is out of the question. A desk accessory
seemed like a nice compromise since it would enable the addition of menu items.
This worked fine. As long as you didn’t mind choosing the desk accessory every
time you opened a new application. After a few seconds I realized that if a trap was
patched when the DA received a goodbye kiss, the DA could be opened back up again when
a new application is started. Which one to patch? SystemTask seemed a logical choice.
This way, the DA would never open up in an application that didn’t support DAs.
The code went something like this:
case goodBye:
.
.
.
INSTALLPATCH()
break;
where ThePatch() and INSTALLPATCH() are two assembly language routines, the code
for which is given below (in MPW Asm).
;1
; ThePatch()
SystemTask EQU $1B4
ThePatch PROC EXPORT
STRING ASIS
ENTRY(StartSize,EndSize,DAName,OldTrapAddr) : CODE
StartSize
subq #2,A7 ; open the DA back up
pea DAName
_OpenDeskAcc
addq #2,A7
move.w #SystemTask,D0
move.l OldTrapAddr(PC),A0
_SetTrapAddress ; remove our patch
lea ThePatch(PC),A0
_DisposPtr ; deallocate our memory
move.l OldTrapAddr(PC),A0
jmp (A0) ; and call SystemTask
OldTrapAddr DC.L 0
; DAs begin with a NULL char so there are 8 total
DAName DC.W $0800
DC.B 'MenuMem'
ALIGN 2
EndSize
ENDP
; INSTALLPATCH()
InstallPatch PROC EXPORT
STRING ASIS
PatchSize EQU -6
NewTrapAddress EQU -4
link A6,#-6
move.l #(EndSize-StartSize),D0 move.w D0,Size(A6)
_NewPtr,sys ; Need patch in System Heap
move.l A0,NewTrapAddress(A6)
beq.s Return
move.w #SystemTask,D0 ; D0 = TrapNum
_GetTrapAddress
lea OldTrapAddr(PC),A1
move.l A0,(A1)+ ; Patch routine
move.w PatchSize(A6),D0
move.l NewTrapAddress(A6),A1 ; A1 = DstPtr
lea ThePatch(PC),A0 ; A0 = SrcPtr
_BlockMove
move.w #SystemTask,D0
move.l NewTrapAddress(A6),A0
_SetTrapAddress
Return
unlk A6
rts ; Back to C Land
ENDP
The Problem
This worked exactly as desired until MultiFinder appeared. When opened under
MultiFinder, MenuMem acted like every other DA. That is, it would open up within the
DA handler (unless the option key was held down when it was opened. In that case, like
all other DAs, it would open up within the application running in the foreground). But
when I switched to another application, no menu. Even worse, if I tried to open
MenuMem again from within another application, MultiFinder would automatically
switch me to the layer it was currently opened in. Furthermore, since MultiFinder
keeps track of any patches that are installed by the application, and removes them when
you switch to another one, my patch would never be called, and a small block of memory
in the System Heap would be lost. This was totally unacceptable.
The Solution
The answer lies in opening the DA up before MultiFinder is ever run, and
repeatedly making control calls to allow it to update it’s menu. If opened at startup
time, there is an added advantage: No more trips to the Apple Menu. This approach is
also perhaps somewhat simpler as there are no traps to patch, and no Assembly is
required. You’ll still need two AA batteries though (Couldn’t resist). Of course, the
internals of the DA have changed somewhat. It is now written in THINKC (The previous
version was written in MPW C), and uses only 2 global variables.
Before I discuss the structure of the latest (and hopefully last) version of
MenuMem I would like to mention a few words about THINKC. My advice for the few
people out there who don’t have THINKC? GET IT. It is an absolutely wonderful
development environment. E specially if you are into creating Desk Accessories or code
resources (FKEYs, INITs, XCMDs, XFCNs etc.).
Since the beginning of time (January 1, 1904 to be exact) the creation of code
resources in HLLs such as FORTRAN or Pascal has been hindered by the fact that no
global variables were allowed (Most Mac compilers generate code that accesses global
variables indirectly using register A5. Read the Memory and Segment Loader chapters
of Inside Macintosh for more details on this). The more creative compiler writers have
developed various implementations to allow the declaration of global variables in code
resources, including appending globals to the end of the code. THINKC accesses global
variables in code resources indirectly from register A4. In addition, if you are
creating a device DRiVeR, THINKC will dynamically allocate a handle for your globals
and store it in the dCtlStorage field of the DRiVeR’s device control entry, lock it, and
dereference it into register A4. Though it is not stated in the manual, it is important to
note that THINKC allocates this memory by calling GetResource(‘DATA’, drvrID),
where drvrID is the owned resource ID of the DRiVeR, and then passing the returned
handle to DetachResource() to remove it from the resource map. So if you need your
globals to stick around between applications, you can use ResEdit to set the System Heap
attribute.
Below is a description of the program and all of the routines, followed by the
source code. This code is written under the assumption that the INIT, DRVR and DATA
resources will be in the resource fork of the same file.
MenuMem: The INIT
pascal void main()
The INIT portion of MenuMem needs to load in and open the DRiVeR portion.
However, if there is a resource of type ‘DRVR’ with the same ID as ours in the System