Globals
Volume Number: 2
Issue Number: 4
Column Tag: Assembly Language Lab
DA Shows use of Globals and Resources
By Norman Braskat, El Toro, Ca
Desk Accessories from Assembly
Desk accessories are a very basic part of the Macintosh interface and the
development of them need not be a major undertaking, if you follow the guidelines in
this shell DA. In this month's column, I outline how to develop a desk accessory using
assembly language that illustrates how menu items, windows, resources and global
storage can be handled within the DA.
Editor's Notes
[This program was written and compiled entirely in assembly using Consulair's
C compiler! That's right. If your tired of waiting for Apple to update the MDS assembler
system to run under HFS without crashing, here is an alternative. The Consulair C
compiler has a built-in MDS assembler that is fully MDS compatible! You simply run
it like you would the MDS system. Be sure to get the latest release 4.53. This release
includes version 1.53 of the famous Editor. It seems that Consulair went ahead and
fixed up their own version of the editor, obtaining 1.53, before working under contract
to bring Apple's editor and MDS system up to version 2.0. The result is that Bill's 1.53
editor is more solid and more compatible than the current 2.0d1 beta HFS editor being
distributed by Apple to developers. However, when the final 2.0 release becomes
released, that will be equivalent to the 1.53 Bill is distributing with Mac C version
4.53. Now if you understood all that, you should have no trouble with this month's
column!
The link file shown at the end of the article is for the Mac C linker, which is MDS
compatible and it works under HFS on the Mac Plus (the MDS one doesn't). You no
longer need to patch or otherwise coerce the MDS linker into launching and finding it's
files. The job file shown is also for the Mac C Exec and is the same as it would be under
the MDS exec, only the Mac C version works! You can now invoke the job file from the
editor and have the assembly, link and execution of a program proceed automatically. In
this example, we execute the DA Mover program after linking so we can move the new
DA into the system file and test it out. This actually works, although the DA Mover
comes up without knowing what file to open. MacTutor is now offering the Consulair
Mac C and the linker in our Technical software store for those who want to use it as
either an MDS assembler replacement or a C system (still a good C system!) or both.
For those who don't need a C system, we are also offering McAssembly which is nearly
MDS compatible using a conversion utility and the Consulair Linker by itself. -Ed]
General Desk Accessory Information
To begin, the device manager executes the desk accessory as a procedure call of
the following format to one of the five desk accessory procedures: Open, Control,
Prime, Status, and Close.
(D0: ResultCode) := DA Procedure(A0: ParamBlock, A1: DCERecord )
The 'Open' command is called in response to a _OpenDeskAcc toolbox call and in
general performs any initialization required by the desk accessory. When the user
selects any DA from the Apple menu, the Finder or application calls _OpenDeskAcc. It
should allocate any private storage required by the desk accessory, stores a handle to it
in the dCtlStorage field of the DCE record, initializes any local variables, install any
interrupt handlers, change interrupt vectors, create any windows, ... ect...
The 'Close' command is called in response to either _CloseDeskAcc toolbox call or
by the segment launcher to close all the desk accessories, and in general it should undo
what is done by the Open procedure, by releasing all memory used, removing any
interrupt handlers, restoring any system state... ect ... If you must keep state between
when the desk accessory is closed and later opened, store it in a re-locatable block of
memory pointed to by the dCtlStorage field of the DCE record. In practice, 'Close' is
called by System Click when the user clicks the go-away box of a system window
belonging to a desk accessory, or by the application when the user selects "close" in the
File menu for an open DA window. When that happens, storage used by the DA may be
released. When a DA menu item 'quit' is called, the DA can do it's normal close function
as part of a 'Control' call on the menu event without releasing that storage, thus keeping
the variables until a 'Close' call or another 'Open' call. The code for this DA illustrates
how this is done. A 'Close' from the go-away box of the DA's window will release the
handle to the storage, but a 'Close' from the DA's menu will remove the menu and
window without releasing the storage area, thus preserving variables until the next
'Open' call.
The 'PRIME' and 'STATUS' commands are called in response to a _Read, _Write,
or _Status trap, and they are not used by desk accessories ( I think ). For cleaness
sake I use empty procedures ( RTS's ). These routines would be used by drivers for
printers and other I/O type devices.
The 'Control' command performs most of the work done by the desk accessory. It
is called in response to one of the functions shown in figure 1 and in general will look
like a case statement which decides what to do, and then does the required action. All
events are handled as 'Control' calls and in general, any action during an active DA's life
is handled as a 'Control' call.
As figure 1 shows, the A0 and A1 pointers allow access to all the information
required to determine exactly what operations are requested and to execute them. The
A0 register contains the pointer to the parameter block (see figure 3) while the A1
register contains the pointer to the DCE record (see figure 2). And for DA's, the result
code returned in D0 should always be zero.
Pic. 1: Our DA does
windows, menus...
Pic.
2: And alerts!
Device Control Entry Record
The DCE record (see figure 2 ) contains some information taken from the DA's
header, the always present queing infromation, a handle to the DA's storage area, a
pointer to the DA's window, and some other control information. Most desk accessorys
are only interested in the 'dCtlStorage', 'dCtlRefNum', and the 'dCtlWindow' fields, and
in general the rest of the record is only used by drivers and the device manager. Upon
entry, A1 holds a pointer to the DCE record and great care must be taken to see that this
pointer is preserved throughout the DA. Many DA errors are due to this pointer being
clobbered by a trap call.
The dCtlStorage handle allows the desk accessory to have it's own global memory
area seperate from the stack. The DA cannot use register A5 since that is used by the
underlying application for it's globals. Care must be taken to unlock and deallocate any
memory the DA may have when it is closed, since the device manager will not do this for
you.
IO Parameter Block
The Parameter block pointed to by A0 (see figure 3) contains the action code and
any data passed by the device manager. The csCode contains the action code and will be a
value from 64 to 73. The parameter block will contain any data passed. Menu actions
will pass the MenuID (28) followed by the MenuItemNum (30).
Support Resources
To use the 'DA MOVER' program for installing your desk accessory it will be
necessary to make the resource ID's for alerts, dialogues, pictures, ect.. be a function
of the DA's number. The equation used is:
ResurceID := (( DA's number ) x #32) + #$FFFFC000 + index
and
( DA's number ) := (- (DA's RefNum) - #1)
This would have the resources for DA (#12) starting at #-16000, with a
maximum of #32. The assembler makes it a bit difficult to code this 16-bit 2's
complement number, but I found that using a decimal value of 49536 in the resource
header produces the desired equivalent of -16000. (See the resource section of the
listing). Also, the DA name should have a null ($0) byte as the first character of the
name, but the assembler won't let you do this in the resource header. The resource
editor can be used to do this, but as it turns out, the Font/DA mover does it for you
when it installs the DA.
Desk Accessory Header Format
The first 9 words of the desk accessory constitute a header and is followed by the
DA's title string. Figure 4 defines the structure of the header block. With this
information, we can start writting our desk accessory.
In this example, I have coded simple open, control and close procedures to check
for and allocate global memory storage via A4. Once this is done, a central 'launch'
routine is called that provides a single interface to the DA's major code blocks for open,
control and close. The launch routine saves state and calls the three DA routines which
then decode the action parameters and perform the necessary actions. When adapting
this shell to your own needs, you simply place your open code, control code and close
code in the three routines called by launch: OpenDA, MainDA and CloseDA.
; Asm language desk accessory.
; © March 1986 by Norman Braskat
; for MacTutor.
; Extended to windows March 26,1986
; by David Smith
RESOURCE 'DRVR' 12 'EXAMPLE DA'
INCLUDE MacTraps.D
.trap _DeBug $A9FF
StorageSize EQU 6
; Globals relative to A4 [dCtlStorage]
MenuHandle EQU 0 ;[long]
ResourceBase EQU 4 ;[word]
csCode EQU 26 ; control/status [word]
csParam EQU 28 ; parameters [20 bytes]
MenuItemNum EQU 30
; DCE item list
dCtlDriver EQU 0
dCtlFlag EQU 4
dCtlQuer EQU 6
dCtlHead EQU 8
dCtlTail EQU 12
dCtlPosition EQU 16
dCtlStorage EQU 20
dCtlRefNum EQU 24
dCtlCurTicks EQU 26
dCtlWindow EQU 30
dCtlDelay EQU 34
dCtlMask EQU 36
dCtlMenu EQU 38
; Toolbox equates
WindowKind EQU $6C ;offset from wind. rec.
; Event parameter block equates from SysEqu
evtNum EQU 0 ; event [word]
evtMessage EQU 2 ; msg [long]
evtTicks EQU 6 ; TICKS [long]
evtMouse EQU 10 ; mouse pos.[long]
evtMeta EQU 14 ; meta key flags [byte]
evtMBut EQU 15 ; mouse button [byte]
JIODone EQU $8FC ; IODone entry [pointer]
; Register Usage
; A0 = ParamBlock ptr,
; A1 = DCE ptr,
; A2 = evt table ptr from csParam(A0),
; A3 = working DCE ptr (after launch)
; A4 = globals (referenced to dctlstorage),
; A5 = application globals (not used),
; A6 = frame ptr,
; A7 = stack ptr.
; Useful Macros
MACRO PUSH value =
MOVE {value}, -(A7)
|
MACRO PUSH.L value =
MOVE.L {value}, -(A7)
|