Function Resources
Volume Number: 1
Issue Number: 8
Column Tag: C Workshop
"Function Resources
By Robert B. Denny, President, Alisa Systems, Inc., MacTutor Editorial
Board
Several weeks ago, a group of us were having a "programmer's lament
discussion, centering around some of the seemingly needless holes in the Mac's
development environment, in particular the Macintosh Development System (MDS)
assembler/ linker package. I happen to know some of the private history behind it's
implementation, and why it lacks librarian and selective linking support.
FIG 1 STATUS CODE IN TABLES
FIG 2 STATUS CODES NOT IN TABLES
It is alleged that the developer pleaded for those features but "Apple" refused to
pay for them, citing lack of need. Ah, well, such is the plight of us old-timers who got
spoiled 5 years ago on 16-bit systems which had 32K of memory!
The conversation meandered to more fertile ground. We discussed such things as
sharing code between applications running under Switcher, a common error alert
system, and how one could implement a "package-like" resource that could be loaded,
locked and jumped into at run time ... without linking it in ... written in C (or at least
most of it).
The result of that discussion is a way to implement what I'll call Function
Resources. A function resource (FR) is simply a resource that you can read in, lock
down and call as a C function. It is not linked into your program (as would be a
segment).
Caveat
The techniques presented in this article are specific to the Apple MDS
assembler/linker and to the Consulair Mac C compiler, which uses the Apple MDS
system for assembly and linking. If you are using another development environment,
the ideas presented here will still be of use, within the limits imposed by your
development system.
I wrote my first function resource in assembler to get a feel for the way the MDS
linker handles code that is assembled following a RESOURCE directive in the assembly
source. That FR consists of a list of English language error messages for all of the
system error codes, including those generated by the AppleTalk network drivers, and
an alert display function.
The purpose was to provide a C-callable "package" that would put up a meaningful
alert for any system error, and return an indication of whether the user pressed a
"Quit" button or a "Resume" button. The resource contains all of the error messages,
not the application. It isn't exactly in the spirit of the Mac interface, but it's very
handy.
Techniques
The application program contains a very small transfer function which simply
loads the FR resource, locks it down, then does a JSR to it's beginning. Upon return
from the FR, the transfer function unlocks the resource, which has the "purgeable
attribute, then returns the FR's function result to the caller.
Since the FR is not linked with the application, any sharing of data must be via
parameters supplied with the call. However, there is nothing to prevent you from
defining a "work area" in your program, then passing a pointer to the work area as a
parameter to the FR.
If you write the FR itself so that it contains no read-write static data, then a
single copy of the FR can be called by multiple applications and/or desk accessories.
Such an FR is said to be re-entrant. R/W static data is called impure data, while
read-only static data is called pure data. For a routine to be re-entrant, it must not
contain impure data. FR's can have read-write data if it is allocated on the stack at
entry, like automatic variables in C.
With this as a background, lets cover the steps needed to create and use a Function
Resource:
1. Write the FR in assembler or C. Begin it with the assembler's RESOURCE
directive.
2. Assemble and link the FR. Use the linker's /RESOURCES option prior to the FR
module name to create a resource file.
3. Optionally, combine the FR with other resources such as dialogs, alerts and
window templates via an RMAKER run.
4. Write a transfer function which will load and lock the resource and transfer
control to the code contained therein.
5. Link the transfer function into your program.
6. Call the transfer function to use the function resource.
Creating the Function Resource
The MDS assembler and linker provide the support needed do create the function
as a resource without any additional hacking with utilities such as "Fedit" or "ResEdit".
The assembly must start with a RESOURCE directive, declaring the remainder of the
module as resources rather then normal code. For example:
RESOURCE 'PROC' 2000 'Foo 1.2' 32
This declares the module as a resource, with resource ID of 2000, resource type
of "PROC" and with the "purgeable" attribute bit set. The type of PROC is an arbitrary
choice on my part. For an FR written in Mac C, put the RESOURCE directive inside
#asm/#endasm at the top of the source file. The optional resource name can be
anything.
The MDS linker is smart enough to properly handle relocatable references in
assembled resources. On the other hand, it cannot resolve inter-module references
when linking resources. This means that your FR must be written to assemble as a
single .REL file. You can have several source files, but they must be pulled together at
assembly or compile time by text "include" directives. Given that you have your FR's
.REL file, use the following linker commands to create the resource file, ready to use:
/OUTPUT MyFR.PROC
/Globals -0
/Type 'RSRC'
/Resources
MyFR
$
The /OUTPUT directive gives the resource file it's name. The suffix ".PROC" is
an arbitrary choice on my part, the name may be anything reasonable. The reason for
the /GLOBALS directive will be covered later, just be sure it's there and you'll be safe.
The "-0" is required because the linker wants a negative value for the parameter. The
file type "RSRC" indicates the file contains generic resources. The /RESOURCES
directive indicates the beginning of resource data.
The result of assembling and linking as just described is a resource file which
contains a purgeable resource of type PROC, with resource ID of 2000, which consists
of code beginning at the first location in the resource.
A Simple Function Resource
The following is the MDS assembly code for the error alert FR. The tables are not
complete but it should be clear how to complete them.
Mac C uses registers D0-D6 to pass arguments, returning the function result in
either D0 or A0, depending on whether the function returns a scalar or a pointer,
respectively. Remember that the FR is called via the transfer function and not directly
from the C program.
The text displayed in the alert box is formatted using the Dialog Manager's
ParamText() function. If the error code and it's English message are in the tables, the
display is in English. If not, the toolbox "NumToStr" binary to decimal ASCII
conversion "package" is called to convert the error code and it is displayed as
"unknown error N". See the RMAKER control file which follows the assembly source
for more on the use of ParamText().
Figure 1 shows what the error alert looks like for a known status code, Figure 2
shows its appearance for an unknown status code. The "user prefix" is the word "OOPS
followed by a carriage return ('\r').
;
; ** PERROR **
;
; Function resource for displaying system/AppleTalk
; error alert.
;
Include MacTraps.D
Include SysErr.txt ; System error codes
Include AtalkEqu.D ; AppleTalk error codes
;
RESOURCE 'PROC' 2000 'Perror 1.0' 32
;
; P-string format by default
STRING_FORMAT 3
;
; ENTRY:
; D0.W = Error/status code
; D1.L -> P-String to precede error message text
; D2.W = Resource ID of alert box to use (normally
; 2000)
;
BASE:
Link a6,#-32 ; Workspace for NumToStr
Move.W d2,d7 ; Save Alert ID across ParamText
Move.L d1,-(sp) ; -> Caller's message for
; ParamText
Lea codes,a0 ; A0 -> base of code table
@10:
Move.W (a0)+,d3 ; D3 = table code (a0 -> offset)
Beq.S @20 ; (oops, end of table)
Cmp.W d0,d3 ; Matched?
Beq.S @30 ; (yes, display it)
Addq #2,a0 ; A0 -> next code
Bra.S @10
@20:
; Code not in table. Display "unknown error nn
Move.L a6,a0 ; A0 -> temp buffer
Ext.L d0 ; Sign-extend error code in D0
Clr.W -(sp) ; Selector for NumToStr
_Pack7 ; A0 -> P-string of error code
Pea 'Unknown error ' ; P2 = Our prefix
;
(for ParamText)
Move.L a0,-(sp) ; P3 = Error code string
; (for
ParamText)
Bra.S @40
; Code found in table, we have English message
@30:
Clr.L d0 ; Zero out D0.L
Move.W (a0),d0 ; D0 - offset to string
Lea Strings,a0 ; A0 -> base of strings
Add.L d0,a0 ; A0 -> our string
Move.L a0,-(sp) ; P2 = Error message
; (for
ParamText)
Pea L999 ; P3 = nothing (for
ParamText)
; Common code to display the alert
@40:
Pea L999 ; P4 = nothing (for
ParamText)
_ParamText ; Set the text (wipes d2!)
Clr.W -(sp) ; For function result
Move.W d7,-(sp) ; P1 = alert ID
Clr.L -(sp) ; Nil ProcPtr
_StopAlert ; Put up the alert
Move.W (sp)+,d0 ; Return alert function result
Unlk a6 ; Clean up
Rts ; Return to
application's transfer
; function
;
; The following table contains ordered pairs consisting of ; a system
error code followed by the byte offset into a
; list of strings of the error message for that error code.
;
CODES:
dc.w controlErr, 0
dc.w statusErr, (L2 - STRINGS)
dc.w readErr, (L3 - STRINGS)
dc.w writErr, (L4 - STRINGS)
dc.w badUnitErr, (L5 - STRINGS)
dc.w unitEmptyErr, (L6 - STRINGS)
dc.w openErr, (L7 - STRINGS)
dc.w closErr, (L8 - STRINGS)
dc.w dRemovErr, (L9 - STRINGS)
dc.w dInstErr, (L10 - STRINGS)
dc.w abortErr, (L11 - STRINGS)
dc.w notOpenErr, (L12 - STRINGS)
additional codes & message offsets here
dc.w 0 ; End of table
;
; This table contains the error messages, addressed by
; the offsets contained in the previous table.
;
STRINGS:
L1: dc.b 'I/O Control failed'
L2: dc.b 'I/O Status failed'
L3: dc.b 'I/O Read failed'
L4: dc.b 'I/O Write failed'
L5: dc.b 'Bad unit number'
L6: dc.b 'Unit is empty'
L7: dc.b 'I/O Open failed'
L8: dc.b 'I/O Close failed'
L9: dc.b 'Cannot remove open driver'
L10: dc.b 'Driver not found'
L11: dc.b 'I/O aborted by KillIO'
L12: dc.b 'I/O to unopened driver'
additional messages here
L999: dc.b 0,0 ; Addressable empty string
END
Assuming the above was assembled to a file called PERROR.REL, the next step is to
link the REL file into a 0-based image in resource format. The linker control file is
shown below:
/OUTPUT Perror.PROC
/Globals -0
/Resources