System CDEV
Volume Number: 5
Issue Number: 5
Column Tag: Forth Forum
Related Info: Control Panel
System CDEV
By Jörg Langowski, MacTutor Editorial Staff
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
“System configuration cdev”
There is a nice public domain CDEV utility, called Systat, that shows the system
configuration you’re running on: machine type, CPU, floating point hardware, etc.
Ever since my last system update, this CDEV doesn’t run anymore on my Mac II. Don’t
know what I did wrong or what INIT I installed that conflicts with it. So to have that
utility back, I decided to write my own - in Mach2 - to give you an example how to
write CDEVs and how to use the SysEnvirons trap [which is unfortunately left
unimplemented in Mach 2.14].
SysEnvirons
The Macintosh world has evolved a long way from the one-configuration era of the
128K Mac. There are now - at least - 8 different types of machines on which an
application could run, the 512K old ROM Mac (yes, some still have one), the Lisa, the
512KE, Mac Plus, Mac SE, SE/030, Mac II and Mac IIx. I’m not counting the ‘small’
MacII with fewer slots since I haven’t seen that machine as of this writing.
Add to this variety of machines all the different accessories that could be present,
such as large screens or accelerator cards, and you end up with an impressing array of
different configurations. The confusion is far from being as bad as in the MS-DOS
world, but it’s there; no wonder that some applications start having problems running
on all possible configurations.
It is for this reason that the SysEnvirons trap - described in IM V-6 - has been
added to the system. This routine returns information about the system configuration
the program is running on, so that you can exit gracefully from your application if -
for instance - color Quickdraw is needed but not available.
SysEnvirons accepts two arguments: a pointer to a data structure called a
sysEnvRec in A0 and a version number in D0. The version number is used to maintain
upward compatibility. Right now, two versions of the trap call exist, version 1 and 2,
which both return the same information. If, at a later time, new versions are defined
that return more or other information, the old calls can still be used, returning the
data in the same old format. SysEnvirons returns the sysEnvRec pointer in A0 and a
result code in D0.
The sysEnvRec, at the moment, has the following fields (in Forth notation):
0 CONSTANT environsVersion
2 CONSTANT machineType
4 CONSTANT systemVersion
6 CONSTANT processor
8 CONSTANT hasFPU
9 CONSTANT hasColorQD
10 CONSTANT keyBoardType
12 CONSTANT atDrvrVersNum
14 CONSTANT sysVRefNum
The fields are described in IM V, but some newer configurations are not
mentioned in the book. As pointed out earlier, environsVersion can be 1 or 2
(according to my experimentation), but the information returned is the same in both
cases. machineType can be 0 to 7, because in addition to the MacII, we have the Mac IIx,
the MacII ‘small version’, and the SE/030 now. processor can be 0 to 4, the last value
corresponding to the 68030.
[Incidentally, I know about two otherwise very good programs which check the
processor type, but don’t think about Motorola’s upward compatibility. TMON at first
wouldn’t run on my IIx, because it doesn’t know the processor type returned by
SysEnvirons. There are processor-specific resources MonC and MonI in TMON, and the
68030 requires MonC ID=3 and MonI ID=3 to be present. If one simply duplicates the
MonC ID=2 and MonI ID=2 resources and changes their IDs to 3, TMON runs. Languages
Systems Fortran, too, has its problems. If you compile a program with the 68020 code
option on, it won’t run on a IIx, because that machine has no 68020! Obviously, LS
Fortran should check for 68020 or greater, and not for 68030 only. They know about
that bug and will revise it.]
The SysEnvirons trap glue code, and some words returning individual values
from the sysEnv record, are printed in the example. The last part contains some code
that you may execute from the Mach2 environment and which will print the system
configuration information directly to the console window. So now you know what
machine you’re using (isn’t that great).
The CDEV
To put the SysEnvirons information to some practical use, we’ll write a control
panel utility (CDEV) that displays this information in the control panel window. CDEVs
are described in IM V-323, and Steven Sheets has also written an article about them
(MT V3#10, p.59). I’ll briefly summarize the relevant points.
A control panel utility is a file of type ‘cdev’ and arbitrary creator (we use
JLMT, for obvious reasons. Not even registered with Apple yet!). The file contains
BNDL, ICN# and FREF ID=-4064 resources, and a JLMT ID=0 resource, to make its
icon correctly appear on the desktop. The resources actually relevant to the control
panel are:
mach ID=-4064, nrct ID=-4064, DITL ID=-4064, and cdev ID=-4064. ‘mach’
determines which machine the CDEV can run on; the control panel will display the
CDEVs icon on the left side of the panel only on machines corresponding to the number
contained in ‘mach’ (see IM for details). The ‘nrct’ resource contains a number of
rectangles that are drawn in the right part of the control panel when the CDEV is
opened. These rectangles are filled with white and framed with a 2-pixel thick black
border. The rest of the control panel is filled with light gray. After the rectangles, the
dialog items in the DITL resource are drawn. The contents of the DITL are appended to
the control panel’s own DITL. Later when a dialog item on the left hand side of the panel
is referenced, its item number will have to be incremented by the number of items in
the control panel’s own DITL.
‘cdev’ contains the actual code for the control panel handler. It is a code resource
with its entry point at the very beginning (like XCMDs, MDEFs, etc.), and it is called
like a Pascal function with the following parameters:
{1}
FUNCTION cdev( message,item,numItems,CPanelID: INTEGER;
VAR theEvent:EventRecord; cDevValue: LONGINT;
CPDialog: DialogPtr):LONGINT;
Our Forth code has to go through the usual gymnastics to be called correctly. We
jump from the start of the resource to a piece of glue code which saves the old
registers, sets up local Forth and loop return stacks, and moves the parameters from
the A7 stack to the Forth stack. The main cdev routine is then called with the
parameters on the Forth stack (see the listing). message will tell the routine what to
do on this call. item is the item number of the dialog item that was hit; this parameter
is only relevant for the hitDev message. numItems is the number of items that precede
the CDEVs DITL in the item list. To address a dialog item from its own DITL, the CDEV
has to add this number to the item number. CPanelID is the resource ID of the control
panel DA. theEvent is a pointer to en event record, valid for hitDev, nulDev, activDev,
deActivDev and keyEvtDev messages. cdevValue contains the return value of the last
call of the cdev routine. A CDEV might, for instance, allocate its private storage and
pass a handle to it as a function result; the next time it is called, it will find this
handle back in the cdevValue parameter. CPDialog is the dialog pointer to the control
panel dialog.
CDEV messages
A CDEV can receive several different messages in the message parameter. For our
purpose, the initDev message is the only relevant one. This message is sent once when
the CDEV is opened. All we need to do is to find the system information at this time and
fill in the dialog items so they can be displayed. Once this is done, updating of the dialog
will be handled by the control panel. Other CDEVs might need to handle more messages.
The allowed values are:
initDev=0, hitDev=1, closeDev=2, nulDev=3, updateDev=4, activDev=5,
deActivDev=6, keyEvtDev=7, macDev=8, undoDev=9, cutDev=10, copyDev=11,
pasteDev=12, clearDev=13.
hitDev corresponds to a mouse down event in control panel dialog item. closeDev
is sent when the control panel is closed, updateDev to keyEvtDev are sent to handle
events, and undoDev to clearDev are sent when the corresponding Edit menu item is
selected.
nulDev works like the accRun message in a desk accessory; it is sent periodically
and allows the CDEV to perform actions like cursor blinking, time updating, etc.
macDev is sent optionally on initialization, depending on the value of the ‘mach’
resource; when this message is sent, the control panel should check whether it can
really run on this particular machine configuration.
Our CDEV routine only handles the initDev message. It will beep once, and then
put string constants corresponding to machine type, system version etc. in the various
dialog items. It has been checked on a Mac 512KE, Mac Plus, Mac SE, SE/030, Mac II
and IIx. It’s up to you to add the checking code for the 64K ROM Mac and Lisa.
Language Systems Fortran and Prototyper
Even though this is ‘officially’ a Forth column, I’d like to tell you some more of
my experiences with Language Systems Fortran. That system, together with
Prototyper, gives you an amazingly powerful environment to outfit Fortran programs
with a nice Mac-ish interface. And you can even have lengthy calculations run in the
background, if you modify your Fortran code slightly.
I won’t describe the program that I’ve been working on in detail. Suffice it to say
that it’s quite long, used for some kind of curve fitting, and uses some 2-5 seconds per
iteration on a Microvax II. It compiles nicely under MPW/LS Fortran, and the
resulting application runs from a dumb terminal window and even gives the same
results as the Vax. However. Once started, Fortran takes control and releases it only at
the very end of the program. 50 iterations, 2 seconds per iteration = 100 seconds of
twiddling thumbs.
There is an easy way to give some Multifinder compatibility to LS Fortran
applications. Listing 2 shows a small routine that calls WaitNextEvent with an event
mask corresponding to update events and app4 events (context switching). Putting
calls to this routine in strategic places of your Fortran program, so that it is called
several times per second, will make the program ‘background-able’. A mouse click on
another application’s window will switch the Fortran program to the background,
where it will continue to execute. However, your word processing, or spreadsheet
activity, is slowed down only slightly. My pause routine will also set the cursor to a
watch when the button is pressed inside the Fortran window. That way, the program
can indicate that it is still calculating, and reset the cursor to an arrow when one
iteration is done; other routines could at that point grab the mouse down event and
handle it, to change parameters of the calculation etc.
The second step of making a Fortran program ‘Mac-ish’ is to add a real
menu/dialog interface for entering and changing the parameters to the program.
Typically, one would enter file names, initial values, matrix sizes, etc. In my
particular case, I used Prototyper to generate the interface, which gave me Pascal
code. The Fortran program was split up in convenient subroutines, which were called
as Externals from the Pascal main program.
The strategy was the following: Input parameters, file names, etc. were set via
menus and dialogs. When all parameters had been set, a menu item could be selected to
start the calculation. This menu selection would simply set a flag in the Pascal
program, doCalc. The main event loop’s null event handler would check this flag and if
it was set, run one iteration of the Fortran code per event loop. Of course, since each
iteration took several seconds, this would slow down the user interaction way too
much.
Therefore, whenever a non-null event was detected in the main event loop,
another flag was set which disabled null event background processing. This flag was
kept on for 5 seconds after each non-null event.
In summary, this strategy will transform your Fortran program into an
application that can be juggled into the background at any time with 0.1 sec delay and
calculate in the background. When you click on the application in the foreground, the
cursor will change to a watch, the current iteration will finish, and then you can
change parameters, stop the calculation, print intermediate results, etc. through the
interface provided by Prototyper. When no user action is detected for 5 seconds, the
program will start calculating again.
French Hacker Story - the End (???)
You might have followed the story of Faraglace: The French hacker who was
supposed to pay very heavily for the mistake of having removed the copy protection
from some programs - notably 4th Dimension. He did this out of his own private
curiosity, the products of this activity got into the hands of some pirates, and the whole
issue was taken to court. It seems, after the result of a recent lawsuit last month, that
the story is finally coming to a more reasonable conclusion: The total amount of fines
and damages has gone down from a sum of the order of $100,000 to an amount of more
like $2,000 (!). This does not settle the issue for the pirates who sold the
de-protected stuff; that’s separate. But the fact that a court has recognized that
cracking and stealing software are not quite the same thing relieves me. ACI is still
going to appeal this verdict. More to come, hopefully good news.
Till next month, happy threading
Listing 1: SysConfig cdev
\ System configuration cdev
\ Example for MacTutor written in Mach2 Forth
\ J.Langowski March 1989
only forth also mac also assembler
\ Define SysEnvirons trap; not present in Mach2.14
\ alternatively, use the trap compiler accessible on the
\ GEnie Mach2 libraries and on the V5#1 source code disk
.TRAP _SysEnvirons $A090
0 CONSTANT environsVersion
2 CONSTANT machineType
4 CONSTANT systemVersion
6 CONSTANT processor
8 CONSTANT hasFPU
9 CONSTANT hasColorQD
10 CONSTANT keyBoardType
12 CONSTANT atDrvrVersNum
14 CONSTANT sysVRefNum
\ compiler support words for kernel-independent
\ definitions, defproc resources, etc.
\ :xdef compiles a JMP at the beginning of the
\ block, which is resolved at the end of the definition
\ by ;xdef.
: :xdef ( -- branch marker )
create -4 allot
$4EFA w, ( JMP )
0 w, ( entry point to be filled later )
0 , ( length of routine to be filled later )
here 6 - 76543 ( marker for stack checking )
;
: ;xdef { branch marker entry | -- }
marker 76543 <> abort” xdef mismatch”
entry branch - branch w!
here branch - 2+ branch 2+ !
;
: xlen 4 + @ ; ( get length word of external definition )
\ **** cdev proc glue macros for kernel-independent code
CODE cdev.prelude
LINK A6,#-512 ( 512 bytes of local Forth stack )
MOVEM.L A0-A5/D0-D7,-(A7) ( save registers )
MOVE.L A6,A3 ( setup local loop return stack )
SUBA.L #256,A3 ( in the low 256 local stack bytes )
MOVE.L 8(A6),D0 ( CPDialog )