home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Crawly Crypt Collection 1
/
crawlyvol1.bin
/
program
/
books
/
68k_book
/
arp_doc
/
chap_10.doc
< prev
next >
Wrap
Text File
|
1985-11-20
|
115KB
|
2,536 lines
Atari ST Machine Specific Programming In Assembly
Chapter 10: AES Initialization Algorithms
Tired of typing file names on TTP input parameter lines? I
assumed as much. Fortunately, the designers of the ST's
operating system have provided an easier way to deal with such
trivia. And the system that alleviates file selections also
contains many other tools with which a programmer can design
sophisticated interfacing algorithms. The user-interface
features are provided by the Application Environment Services
(AES) segment of the ST's operating system. Although the AES
appears to be unreasonably complex during a first study of its
repertoire, I intend to prove to you that using its routines is
one of the easier operating system manipulations. But I'm not
going to try to do the job alone; I sincerely recommend that you
obtain at least one of two books and read it carefully, several
times, if necessary. Because it is not possible for me to
anticipate your every need and desire. Therefore, I want to show
you methods, not canned routines; you'll find lots of those in
other books. Magazine articles are another valuable source of
AES information. The more you read about the ST's operating
system, the less intimidated you will feel by its structure.
Of the two books, Atari ST GEM Programmer's Reference, by
Norbert Szczepanowski and Bernd Gunther, published by Abacus
Software; and COMPUTE!'s Technical Reference Guide--Atari ST,
Volume Two: GEM AES, by Sheldon Leemon; I feel that I must
recommend Leemon's. The Abacus book omits too many AES
functions, and it hardly mentions assembly language; however, it
is just better than no book at all. I should have more
enthusiastically recommended the Leemon book in chapter one. My
enthusiasm had been dampened because Leemon did not include
assembly setups in the function reference section, Appendix A of
his book; but, then, neither does the Abacus book. Nevertheless,
the assembly setups can more readily be inferred from the data
given in the Leemon book. Furthermore, the Leemon book's
assembly language examples are more comprehensive than are those
in the Abacus book.
Initializing Programs With AES Subroutines
The initialization algorithms that have been used in the
programs presented up to this point have been those that invoked
GEMDOS functions. Incorporated into the design of those programs
were some BIOS and XBIOS function invocations also. The AES
subroutines are invoked with a trap #2 exception generating
instruction. When the GEMDOS, BIOS and XBIOS functions are
invoked, parameters are passed to the functions via the stack;
and returned parameters are passed in one or more registers.
Parameters are passed between invoking programs and AES
subroutines via arrays. The arrays must be declared in the data
section of an assembly language program.
It is the descriptions concerning these arrays which
probably cause much of the confusion associated with the AES.
For example, on page 275 of the Abacus GEM book, the discussion
of AES functions and the associated arrays begins, just at the
conclusion of a VDI discussion. The array descriptions begin on
the following page. The first problem in the Abacus book is that
the authors omitted the int_out array in the list at the top of
page 276. The second problem, although it is not immediately
obvious that the information there is less accurate than that
presented elsewhere, is that the functions assigned to the
elements of the control array listed at the bottom of that page
do not agree with other references.
The Abacus book states that the parameters passed to contrl
(sometimes called control) elements 1 and 2 are the size of the
intin (sometimes called int_in or sintin) and intout (sometimes
called int_out or sintout) arrays, respectively, in bytes. Atari
documentation declares the information to be the same as does the
Abacus book, however, that documentation reports that the size of
the data must be specified in words. The COMPUTE! book insists
that the items of information stored in those control array
elements are the number of integer inputs passed in int_in and
the number of integer results returned in int_out (see page 11).
The Peel book (page 3-59) states the parameter values to be the
length of i/p coor table and length of o/p coor table, where
table length is in words; then, in tables under the AES function
headings, indicates that the values are the number of particular
parameter items passed. It seems prudent to suspect that the
information given in the COMPUTE! book is the most consistent,
even though the parameter specifications are also given there in
both sets of terminology; because the information given on pages
10 through 13 leads one to conclude that, if the size of each
parameter is one word long, and if it is the number of parameters
that is specified via the pertinent element of array control,
then the number of parameters, times the size of each parameter,
equals the length of the array portion, in words, that is being
passed. Notice that I said array portion, not the entire size of
the declared array, as the references indicate. That is, if the
number of int_in parameters is 2, then the size of the portion of
the int_in array being passed is 2 words.
There is a neat list of the applicable arrays on page 12 of
the COMPUTE! book; unfortunately, the ordering of the list is not
as it should be for storage of the addresses of the listed items
in the application parameter block (apb) structure. The apb
structure is a pointer array in which the addresses to other
major AES structures are stored. That ordering is shown on page
13. I hope to be able to prove to you that it is more the
inconsistency of data presentation in the references with which
we must contend than it is with AES function intricacy. As you
will see if you look at page 46 of the COMPUTE! book, that
inconsistency is carried over into programming examples. There
you see that not only have the names initially given for the
arrays been changed, but also the values used in the array
declarations: ds.w 8 for int_out (aintout), not ds.w 7 as
indicated on page 12; ds.w 18 for int_in (aintin), not ds.w 16 as
indicated on page 12; ds.l 3 for addr_in (addrin), not ds.l 2 as
indicated on page 12; and ds.l 2 for addr_out (addrout), not ds.l
1 as indicated on page 12. I shall begin the unraveling with
figure 10.1, in which I present the structure of the major AES
variables under discussion.
Figure 10.1. Major AES structures. The apb structure is a
pointer array containing the addresses of the other structures;
each address requires one longword of storage, therefore, the
space occupied by the pointer array is 6 longwords. Within each
structure, the digits indicate the element of the structure. The
order in which the other 6 major AES structures are shown below
is the order in which the addresses of these structures must be
stored in the apb structure.
Within the figure, I have indicated a specific length for
each structure; previously, I have spoken of array portions;
congruency is provided by the following statement: the size
declared for the int_in, int_out, addr_in and addr_out arrays
must be commensurable with the number of parameters required by
the specific AES functions invoked within a program. For
example, when invoking the appl_init function (AES opcode $A), no
parameters are passed, and only one parameter is returned--via
the int_out array; therefore, the size declared for the int_out
array must be at least one word; the size declared for each of
the int_in, addr_in and addr_out arrays is irrelevant. On the
other hand, when invoking the form_dial function (AES opcode $33
dec 51), 9 parameters are passed to the function via the int_in
array, and one parameter is returned via the int_out array. That
function requires a declared size of at least 18 words for the
int_in array and at least 1 word for the int_out array. In the
figure, you can see that the int_in array is illustrated as a 16
word length structure. That's because I used the structure
lengths indicated on page 12 of the COMPUTE! book.
What all of this means is that, in order to declare arrays
of sufficient size for the functions to be invoked within a
program, you must look under the function headings in your
reference book for each of the functions to be used; determine
which function requires the most parameters to be passed and
which requires the most to be returned (these values may not
coincide for a particular function); then declare sizes that meet
the needs of the functions with the greatest number of
parameters. If the form_dial function is not to be invoked
within a program, then the size of the int_in array need not be
sufficient to contain 9 word size integers.
The sizes declared for the apb, control and global
structures are constant for every application. Notice that the
structure shown for global is constructed of both word size and
longword size elements. I should also mention that, since the
addresses of the four arrays for which the sizes need not be
constant must still be stored in the apb array, even if no
parameter exchange takes place, I would consider it prudent to
declare a size of at least 1 element for each of those four. One
other point; when declaring space for variables, a declaration of
ds.w 8 reserves 16 bytes, as does a declaration of ds.b 16, or a
declaration of ds.l 4, or a declaration of ds.w 4 followed by
ds.l 2. We use one method or another simply to satisfy our human
sense of structure--the machine does not give a damn. The values
that are to be stored in each of the array elements are specified
under the function headings in each reference book. I find the
presentations in the Leemon book to be the easiest to decipher.
It is time for an example program. But before that, let me
stress a point. The structure element digits shown in figure
10.1 are not the numbers by which we reference those elements in
assembly language programming. That's because the addressing
modes we use require byte size offsets. Suppose that the
appl_init function is being invoked. Preparatory to the trap #2
invocation, 5 values must be stored in the control array. One
method of accomplishing the required storage is to load the
address of the array in a register, say A4. The function opcode
would then be stored in control array element 0 with the
instruction move.w #$A, (A4), where the digit 0 can be assumed to
exist in front of (A4). But this 0 does not reference element 0
of the array; it references a memory location that is at offset 0
from the base address stored in register A4. If we know that the
other control array elements contain the value 0, then we would
not have to store that value in control array elements 1, 3 and
4; but we would have to store the value 1 in element 2. That
would be accomplished with the instruction move.w #1, 4(A4)
because element 2 of the control array is located in memory at an
offset of 4 bytes from the base address of the array. When the
length of each element of a structure is identical, an element's
offset, in bytes, is calculated by multiplying the element number
by the element length; hence, 2 (for element 2) times 2 bytes
(the element length) equals 4 bytes offset.
There is another method of passing parameters in the control
array, which some programmers find attractive. For each function
to be invoked within a program, an array bearing the function's
name and containing all required parameters is declared within
the data section. Then, before the trap #2 call, the address of
the function being invoked is stored within the apb at the slot
reserved for array control. You may see that method used in
magazine articles. You can try each method and choose the one
that suits you best.
AES Structure Volatility
Although this chapter is the official introduction to
programs which utilize AES functions, I included program 57
(REG_TST5.S) in chapter eight because it seemed to fit there more
naturally. Using that program, I showed that the ST's operating
system protects the registers which are being used by a desk
accessory while it is waiting for a message event. In this
chapter I will concentrate on programs that investigate the
volatility of AES arrays during AES function invocation. Because
the primary, investigatory, algorithm does not vary much from
example to example within the chapter, I will introduce ancillary
functions selected from the other segments of the operating
system at relevant locations, so that you may become familiar
with their usage.
Program 76. A program that investigates AES array corruption
during AES function invocation and saves the results in a disk
file.
; Program Name: PRG_8AR.S
; Version: 1.005
; Assembly Instructions:
; Assemble in Relocatable mode and save with a PRG extension. From
; the desktop, change the extension to ACC. Programs that are to function
; as desk accessories MUST be assembled in Relocatable mode. If you design
; a desk accessory so that it can be assembled in PC-relative mode, and if
; you attempt to load that accessory via MultiDesk, you will receive an
; error pertaining to the attempt to read in the accessory. If you place
; that accessory in the boot directory, the system will reset every time
; it attempts to load the accessory. Sei gewarnt!
; Function:
; This program is used to observe system corruption of AES arrays
; during the execution of AES functions. Data is output into a file which
; assumes the name declared at the variable "filename". You may wish to
; alter the file's path to something like "A:\PRG_8DR.DAT" if you decide to
; assemble and execute the program.
; The program's output data is written with GEMDOS function $9, the write
; string to screen function. But the data is redirected to the file with
; GEMDOS function $46, the f_force function.
; Execution Instructions:
; Place PRG_8AR.ACC in the root directory of your boot disk. During
; the next power-up cycle, the program will be installed in memory as a desk
; accessory; assuming that it is not blocked by a program that permits desk
; accessory loading selections. Of course, if you use MultiDesk, you need
; not power up to use PRG_8AR.ACC immediately.
; The desk accessory is identified as a menu selection by the name
; Accessory Arrays. Choose the desk accessory twice so that the contents
; of the arrays will be printed twice from within the message handler. The
; message handler is disabled after it has been invoked twice.
; MAJOR NOTE:
; Although accessories such as MultiDesk add significant power to a ST
; system; and although initial testing of a desk accessory program can be
; accomplished via MultiDesk loading of the program, thereby alleviating the
; the testing process, the program under test cannot be pronounced correct
; until it has been permitted to load normally from the boot disk.
; For example, if the first statement of this program, which loads the
; stack's address into A7, is moved to a location following any instruction
; that requires stack usage, the accessory will function perfectly when it is
; loaded and executed via MultiDesk; but it will bomb during boot because, at
; that time, no default stack has yet been assigned by the system.
; REMEMBER: Programs executed during boot MUST provide their own stacks.
; NOTE: Within the program, registers are used as variables as much as
; possible to speed things up.
lea stack(pc), a7 ; This must be the first instruction.
create_output_file: ; COMPUTE! TOS book page 270.
move.w #0, -(sp) ; File attribute = read/write.
pea filename(pc)
move.w #$3C, -(sp) ; Function = f_create = GEMDOS $3C.
trap #1 ; File handle is returned in D0.
addq.l #8, sp
move.w d0, handle
redirect_output_bound_for_screen:
; NOTE: If the output file's handle is exchanged with the video screen's
; handle, then the printline function = GEMDOS $9 can be used to
; write to the file.
redirect_output: ; Exchange file handle with screen's handle.
move.w handle(pc), -(sp) ; This is the disk file's handle.
move.w #1, -(sp) ; This is the video screen's handle.
move.w #$46, -(sp) ; Function = f_force = GEMDOS $46.
trap #1
addq.l #6, sp
initialize_register_variables:
move.w #$C8, d3 ; *** D3 is variable for AES call number.
lea aes_pb(pc), a5 ; A5 is pointer to array address block.
lea control(pc), a4 ; A4 is pointer for array 'control'.
lea hex_table(pc), a3 ; A3 points to hexadecimal ASCII digits.
; For each test point, the contents of each AES array are printed.
;
; TEST POINT 0: Before appl_init
;
bsr print_arrays
initialize_application: ; COMPUTE! AES book page 223.
; Application identification = apid returned in int_out[0] and global[2].
move.w #$A, (a4) ; Function = appl_init = AES $A.
move.w #0, 2(a4) ; Input no 16-bit integer parameters.
move.w #1, 4(a4) ; Return one 16-bit integer parameter.
move.w #0, 6(a4) ; Input no 32-bit pointer parameters.
move.w #0, 8(a4) ; Return no 32-bit pointer parameters.
bsr aes ; Invoke trap #2 AES exception.
;
; TEST POINT 1: After appl_init, before menu_register
;
bsr print_arrays
menu_installation: ; COMPUTE! AES book page 248.
; Menu identification number returned in int_out[0].
move.w #$23, (a4) ; Function = menu_register = AES $23.
move.w #1, 2(a4) ; Input one 16-bit integer parameter.
move.w #1, 4(a4) ; Return one 16-bit integer parameter.
move.w #1, 6(a4) ; Input one 32-bit pointer parameter.
move.w #0, 8(a4) ; Return no 32-bit pointer parameters.
lea global(pc), a0 ; Fetch address of global array.
move.w 4(a0), int_in ; Application identification to int_in[0].
move.l #menu_text, addr_in ; Menu text address to addr_in[0].
bsr aes
move.w int_out(pc), menu_id ; Store menu identification number.
; MAIN ACCESSORY LOOP
;
; TEST POINT 2: After menu_register, before evnt_mesag
;
bsr print_arrays
move.l #message, addr_in ; Address of message array to addr_in.
wait_for_message: ; COMPUTE! AES book page 235.
move.w #$17, (a4) ; Function = evnt_mesag = AES $17.
move.w #0, 2(a4) ; Input one 16-bit integer parameter.
move.w #1, 4(a4) ; Return one 16-bit integer parameter.
move.w #1, 6(a4) ; Input one 32-bit pointer parameter.
move.w #0, 8(a4) ; Return no 32-bit pointer parameters.
bsr aes
; When a message is received it is placed in array 'message'.
; ****************************************************************************
; ****************************************************************************
message_handler: ; Entrance point when message is received.
lea message(pc), a0 ; Fetch address of array 'message'.
cmpi.w #$28, (a0) ; Compare ACCESSORY OPEN code with message[0].
bne.s wait_for_message ; Execute the evnt_mesag function.
move.w 8(a0), d0 ; The menu item selected is stored in element
; four (message[4]) of array 'message'. This
; application's id # is in menu_id.
cmp.w menu_id(pc), d0 ; Was this application selected.
bne.s wait_for_message ; Execute the evnt_mesag function.
; ****************************************************************************
; ****************************************************************************
; Execution proceeds past this point only when this application has been
; selected from the menu.
cmpi.w #5, test ; Have five array groups been printed?
beq wait_for_message ; Disable message handler if true.
;
; TEST POINT 3: In message handler, before evnt_mesag
;
bsr print_arrays
cmpi.w #5, test ; Branch after 2nd entrance in message handler.
beq.s close_file
bra wait_for_message ; Execute the evnt_mesag function.
close_file:
move.w handle(pc), -(sp)
move.w #$3E, -(sp) ; Function = GEMDOS $3E = f_close.
trap #1
addq.l #4, sp
redirect_screen_output:
move.w #1, -(sp) ; This is the screen's handle.
move.w handle(pc), -(sp) ; This is the file's handle.
move.w #$46, -(sp) ; Function = f_force = GEMDOS $46.
trap #1
addq.l #6, sp
bra wait_for_message
;
; SUBROUTINES
;
print_arrays:
lea newline(pc), a0
bsr print_line
lea test_header(pc), a0 ; Setup to fetch test point header.
move.w test(pc), d0 ; Load test point number into D0.
lsl.w #2, d0 ; Multiply by 4 to reach next pointer slot.
movea.l 0(a0,d0.w), a0 ; Print test point header.
bsr print_line
lea pre_spaces(pc), a0 ; Print spaces before column headers.
bsr print_line
lea aes_names(pc), a0 ; Print AES array column headers.
bsr print_line
lea pre_spaces(pc), a0 ; Print spaces before underline.
bsr print_line
lea aes_underline(pc), a0; Print AES underline.
bsr print_line
moveq.l #0, d7 ; D7 is up counter to print 5 rows.
moveq.l #4, d6 ; D6 is down counter to print 5 elements.
put_row:
lea buffer(pc), a0 ; Buffer is an 80 byte line buffer.
movea.l a5, a6 ; Copy aes parameter block address.
move.w #5, d5 ; D5 is array counter for 6 arrays.
move.w #11, d0 ; Print beginning spaces to line up columns.
put_space:
move.b #$20, (a0)+
dbra d0, put_space
put_element: ; Print contents of array element.
move.w d7, d0 ; Print array element number.
andi.b #$F, d0 ; Mask most significant nibble.
move.b 0(a3,d0.w), d0 ; Store appropriate hex character in D0.
move.b d0, (a0)+
move.b #$3A, (a0)+ ; A colon.
move.b #$20, (a0)+ ; A space.
move.w d7, d0 ; Multiply contents of D7 by 2 in D0 to
lsl.w #1, d0 ; obtain offset for next array element.
movea.l (a6)+, a1 ; Copy array address into A1 and increment
; A6 to point to next array address.
move.w 0(a1,d0.w), d0 ; Fetch contents of array element.
moveq #3, d2 ; D2 is loop counter for ASCII conversion.
convert_digit: ; Convert a nibble, then print it.
rol.w #4, d0 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d0, d1 ; Copy least significant byte of D0 to D1.
andi.b #$F, d1 ; Mask out most significant nibble of D1.
ext.w d1 ; Extend to word length.
move.b 0(a3,d1.w), d1 ; Fetch ASCII hex digit to D1.
put_digit:
move.b d1, (a0)+
dbra d2, convert_digit ; Loop until D2 = -1.
_put_spaces:
move.b #$20, (a0)+
move.b #$20, (a0)+
dbra d5, put_element
move.b #0, (a0) ; Store NULL at end of string.
lea buffer(pc), a0
bsr print_line
lea newline(pc), a0 ; Print a newline.
bsr print_line
addi.w #1, d7 ; Increment up counter.
dbra d6, put_row
add.w #1, test ; Increment test for next test point.
rts
aes: ; COMPUTE! AES book page 13,
move.l a5, d1 ; Address of aes_pb.
move.w d3, d0 ; AES identifier = $C8.
trap #2
rts
print_line:
move.l a0, -(sp) ; Push string onto stack.
move.w #9, -(sp) ; Function = c_conws.
trap #1
addq.l #6, sp
rts
data
aes_pb: dc.l control,global,int_in,int_out,addr_in,addr_out
test_header: dc.l zero,one,two,three,four
zero:
dc.b $D,$A,'TEST POINT 0: Before appl_init',$D,$A,$D,$A,0
one:
dc.b $D,$A,'TEST POINT 1: After appl_init, before menu_register',$D,$A,$D,$A,0
two:
dc.b $D,$A,'TEST POINT 2: After menu_register, before evnt_mesag',$D,$A,$D,$A,0
three:
dc.b $D,$A,'TEST POINT 3: In message handler, before evnt_mesag',$D,$A,$D,$A,0
four:
dc.b $D,$A,'TEST POINT 4: In message handler second time',$D,$A,$D,$A,0
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
aes_header: dc.b ' AES ARRAYS',$D,$A,0
aes_names: dc.b 'CONTROL GLOBAL INT_IN INT_OUT ADDR_IN '
dc.b 'ADDR_OUT',$D,$A,0
aes_underline:dc.b '------- ------- ------- ------- ------- '
dc.b '--------',$D,$A,0
pre_spaces: dc.b ' ',0
spaces: dc.b ' ',0
menu_text: dc.b ' Accessory Arrays ',0
filename: dc.b 'E:\PRG_8\PRG_8AR.DAT',0
bss
align
;
; AES ARRAYS:
;
; The addresses of the arrays declared below will be stored in the pointer
; array 'aes_pb'. That happens because the program is assembled in Relocatable
; mode. The sizes declared for the arrays are identical simply because I am
; making it easy to line up the program's output.
control: ds.w 5 ; Control parameters.
global: ds.w 5 ; Global parameters.
int_in: ds.w 5 ; Input parameters.
int_out: ds.w 5 ; Output parameters.
addr_in: ds.w 5 ; Input addresses.
addr_out: ds.w 5 ; Output addresses.
;
; APPLICATION VARIABLES
;
handle: ds.w 1
test: ds.w 1
message: ds.l 4 ; 16 byte array.
menu_id: ds.w 1
buffer: ds.b 80
ds.l 300 ; Program stack.
stack: ds.l 0 ; Address of program stack.
end
Execution Results
TEST POINT 0: Before appl_init
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0000 0: 0000 0: 0000 0: 0000 0: 0000 0: 0000
1: 0000 1: 0000 1: 0000 1: 0000 1: 0000 1: 0000
2: 0000 2: 0000 2: 0000 2: 0000 2: 0000 2: 0000
3: 0000 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 1: After appl_init, before menu_register
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 000A 0: 0120 0: 0000 0: 0003 0: 0000 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: 0000 1: 0000
2: 0001 2: 0003 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0000 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 2: After menu_register, before evnt_mesag
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0023 0: 0120 0: 0003 0: 0001 0: 0003 0: 0000
1: 0001 1: 0001 1: 0000 1: 0000 1: D341 1: 0000
2: 0001 2: 0003 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 3: In message handler, before evnt_mesag
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0017 0: 0120 0: 0003 0: 0001 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: D3AA 1: 0000
2: 0001 2: 0003 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 4: In message handler second time
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0017 0: 0120 0: 0003 0: 0001 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: D3AA 1: 0000
2: 0001 2: 0003 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
Execution Results Discussion
Looking at the contents of the AES structures displayed in
the test point tables produced by program 76, you should be able
to spot relevant values passed to the AES system and returned
therefrom. At test point 1, the second element of global
(global[2]) contains the application identification number, 3.
Identical data can be found in the first element of int_out
(int_out[0]). At test point 2, the menu identification number,
1, is evident in int_out[0]. Of particular interest is the fact
that there is no structure content corruption beyond that
purposely imposed by the program involved. We will be able to
use this information to simplify function invocation in future
programs. Note especially that the contents of addr_in[0] remain
stable between evnt_mesag invocations; that is the address of the
message buffer. Remember that this stability is constant only as
long as no other AES function invocation disturbs the contents of
addr_in[0].
Storing Data in the File With GEMDOS $40
It is always advantageous to understand several methods of
accomplishing a task, especially when the system is as complex as
is the ST, therefore, I want to present a method of storing data
in the file which does not rely on GEMDOS function $9 and output
redirection. Program 77 stores data in a buffer of sufficient
size, then it writes the contents of that buffer to disk using
GEMDOS function $40. I have chosen a particular directory path
for the data file; if you decide to experiment with the program,
you may want to alter that path.
Since we have learned that we can rely on the stability of
AES structures during AES function invocation, several short cuts
have been taken in program 77's 'control' array setups. Program
78 accomplishes the same task as does program 77, but it permits
the data file's directory path to be chosen via the AES file
selector function. In addition, program 78 uses a group of
predeclared, function named arrays that are substituted for
'control' in the aes parameter block array, as each AES function
is invoked.
Program 77. Writing data to the desk accessory's data file using
a buffer and GEMDOS function $40 .
; Program Name: PRG_8BR.S
; Version: 1.002
; Assembly Instructions:
; Assemble in Relocatable mode and save with a PRG extension. From
; the desktop, change the extension to ACC. Programs that are to function
; as desk accessories MUST be assembled in Relocatable mode. If you design
; a desk accessory so that it can be assembled in PC-relative mode, and if
; you attempt to load that accessory via MultiDesk, you will receive an
; error pertaining to the attempt to read in the accessory. If you place
; that accessory in the boot directory, the system will reset every time
; it attempts to load the accessory. Sei gewarnt!
; Function:
; This program is used to observe system corruption of AES arrays
; during the execution of AES functions, as does PRG_8AR.S, and this program
; also creates a file in which to store its output data, but this program
; does not write its data via GEMDOS function $9 and redirection; instead it
; stores the data in a buffer, then writes the contents of the buffer to the
; file using GEMDOS function $40.
; Also note some of the short cuts taken to prepare the control array
; for each AES function.
; Execution Instructions:
; Place PRG_8BR.ACC in the root directory of your boot disk. During the
; next power-up cycle, the desk accessory will be installed. From the desktop
; select 'Accessory Arrays' two times in order to store the pertinent data in
; the file buffer. After the second selection, the file will be created and
; the buffer's contents will be written thereto. You can execute the desk
; accessory from within MultiDesk if you desire. You may want to change the
; path for the file so that it is created on another disk or partition.
lea stack, a7 ; This must be the first instruction.
initialize_register_variables:
lea buffer(pc), a5 ; A5 is pointer to buffer.
lea control(pc), a4 ; A4 is pointer for array 'control'.
lea hex_table(pc), a3 ; A3 points to hexadecimal ASCII digits.
; For each test point, the contents of each AES array are printed.
;
; TEST POINT 0: Before appl_init
;
bsr print_arrays
initialize_application: ; COMPUTE! AES book page 223.
; Application identification = apid returned in int_out[0] and global[2].
move.w #$A, (a4) ; Function = appl_init = AES $A.
move.w #1, 4(a4) ; Return one 16-bit integer parameter.
bsr aes ; Invoke trap #2 AES exception.
;
; TEST POINT 1: After appl_init, before menu_register
;
bsr print_arrays
menu_installation: ; COMPUTE! AES book page 248.
; Menu identification number returned in int_out[0].
move.w #$23, (a4) ; Function = menu_register = AES $23.
move.w #1, 2(a4) ; Input one 16-bit integer parameter.
move.w #1, 6(a4) ; Input one 32-bit pointer parameter.
lea global(pc), a0 ; Fetch address of global array.
move.w 4(a0), int_in ; Application identification to int_in[0].
move.l #menu_text, addr_in ; Menu text address to addr_in[0].
bsr aes
move.w int_out(pc), menu_id ; Store menu identification number.
; MAIN ACCESSORY LOOP
;
; TEST POINT 2: After menu_register, before evnt_mesag
;
bsr print_arrays
move.l #message, addr_in ; Address of message array to addr_in.
move.w #$17, (a4) ; Function = evnt_mesag = AES $17.
move.w #0, 2(a4) ; Input one 16-bit integer parameter.
move.w #1, 4(a4) ; Return one 16-bit integer parameter.
move.w #1, 6(a4) ; Input one 32-bit pointer parameter.
wait_for_message: ; This simplification is not always
bsr aes ; appropriate.
; When a message is received it is placed in array 'message'.
; ****************************************************************************
; ****************************************************************************
message_handler: ; Entrance point when message is received.
lea message(pc), a0 ; Fetch address of array 'message'.
cmpi.w #$28, (a0) ; Compare ACCESSORY OPEN code with message[0].
bne.s wait_for_message ; Execute the evnt_mesag function.
move.w 8(a0), d0 ; The menu item selected is stored in element
; four (message[4]) of array 'message'. This
; application's id # is in menu_id.
cmp.w menu_id(pc), d0 ; Was this application selected.
bne.s wait_for_message ; Execute the evnt_mesag function.
; ****************************************************************************
; ****************************************************************************
; Execution proceeds past this point only when this application has been
; selected from the menu.
;
; TEST POINT 3: In message handler, before evnt_mesag
;
cmpi.w #5, test ; Have five array groups been printed?
beq wait_for_message ; This effectively disables the handler.
bsr print_arrays
cmpi.w #5, test ; Branch after 2nd entrance in message handler.
beq.s store_in_file ; Create file and store buffer contents.
bra wait_for_message ; Execute the evnt_mesag function.
store_in_file:
lea buffer(pc), a6 ; Fetch start address.
suba.l a6, a5 ; Calculate number of bytes stored in buffer.
create_file: ; COMPUTE! TOS book page 270.
move.w #0, -(sp) ; File attribute = read/write.
pea filename(pc)
move.w #$3C, -(sp) ; Function = f_create = GEMDOS $3C.
trap #1 ; File handle is returned in D0.
addq.l #8, sp
move.w d0, d4 ; Save file handle in D4.
write_buffer_to_file: ; Function = f_write. COMPUTE! TOS p.274.
pea (a6) ; Push buffer's address.
move.l a5, -(sp) ; Push byte count length.
move.w d4, -(sp) ; COMPUTE!'s TOS book incorrectly specifies
move.w #$40, -(sp) ; a longword operation here; see page 274.
trap #1
lea $C(sp), sp
close_output_file: ; COMPUTE! TOS book page 272.
move.w d4, -(sp) ; Push file handle.
move.w #$3E, -(sp) ; Function = GEMDOS $3E = f_close.
trap #1
addq.l #4, sp
bra wait_for_message ; Execute the evnt_mesag function.
;
; SUBROUTINES
;
print_arrays:
lea newline(pc), a0
bsr store_line
lea test_header(pc), a0 ; Setup to fetch test point header.
move.w test(pc), d0 ; Load test point number into D0.
lsl.w #2, d0 ; Multiply by 4 to reach next pointer slot.
movea.l 0(a0,d0.w), a0 ; Print test point header.
bsr store_line
lea pre_spaces(pc), a0 ; Print spaces before column headers.
bsr store_line
lea aes_names(pc), a0 ; Print AES array column headers.
bsr store_line
lea pre_spaces(pc), a0 ; Print spaces before underline.
bsr store_line
lea aes_underline(pc), a0; Print AES underline.
bsr store_line
moveq.l #0, d7 ; D7 is up counter to print 5 rows.
moveq.l #4, d6 ; D6 is down counter to print 5 elements.
put_row:
lea aes_pb(pc), a6 ; Fetch parameter block address.
move.w #5, d5 ; D5 is array counter for 6 arrays.
move.w #11, d0 ; Print beginning spaces to line up columns.
put_space:
move.b #$20, (a5)+
dbra d0, put_space
put_element: ; Print contents of array element.
move.w d7, d0 ; Print array element number.
andi.b #$F, d0 ; Mask most significant nibble.
move.b 0(a3,d0.w), d0 ; Store appropriate hex character in D0.
move.b d0, (a5)+
move.b #$3A, (a5)+ ; A colon.
move.b #$20, (a5)+ ; A space.
move.w d7, d0 ; Multiply contents of D7 by 2 in D0 to
lsl.w #1, d0 ; obtain offset for next array element.
movea.l (a6)+, a1 ; Copy array address into A1 and increment
; A6 to point to next array address.
move.w 0(a1,d0.w), d0 ; Fetch contents of array element.
moveq #3, d2 ; D2 is loop counter for ASCII conversion.
convert_digit: ; Convert a nibble, then print it.
rol.w #4, d0 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d0, d1 ; Copy least significant byte of D0 to D1.
andi.b #$F, d1 ; Mask out most significant nibble of D1.
ext.w d1 ; Extend to word length.
move.b 0(a3,d1.w), d1 ; Fetch ASCII hex digit to D1.
put_digit:
move.b d1, (a5)+
dbra d2, convert_digit ; Loop until D2 = -1.
_put_spaces:
move.b #$20, (a5)+
move.b #$20, (a5)+
dbra d5, put_element
lea newline(pc), a0 ; Print a newline.
bsr store_line
addi.w #1, d7 ; Increment up counter.
dbra d6, put_row
add.w #1, test ; Increment test for next test point.
rts
aes: ; COMPUTE! AES book page 13,
move.l #aes_pb, d1 ; Address of aes_pb.
move.w #$C8, d0 ; AES identifier = $C8.
trap #2
rts
store_line:
move.b (a0)+, d0
beq.s end_of_string
move.b d0, (a5)+ ; Store byte in buffer.
bra.s store_line
end_of_string:
rts
data
aes_pb: dc.l control,global,int_in,int_out,addr_in,addr_out
test_header: dc.l zero,one,two,three,four
zero:
dc.b $D,$A,'TEST POINT 0: Before appl_init',$D,$A,$D,$A,0
one:
dc.b $D,$A,'TEST POINT 1: After appl_init, before menu_register',$D,$A,$D,$A,0
two:
dc.b $D,$A,'TEST POINT 2: After menu_register, before evnt_mesag',$D,$A,$D,$A,0
three:
dc.b $D,$A,'TEST POINT 3: In message handler, before evnt_mesag',$D,$A,$D,$A,0
four:
dc.b $D,$A,'TEST POINT 4: In message handler second time',$D,$A,$D,$A,0
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
aes_header: dc.b ' AES ARRAYS',$D,$A,0
aes_names: dc.b 'CONTROL GLOBAL INT_IN INT_OUT ADDR_IN '
dc.b 'ADDR_OUT',$D,$A,0
aes_underline: dc.b '------- ------- ------- ------- ------- '
dc.b '--------',$D,$A,0
pre_spaces: dc.b ' ',0
spaces: dc.b ' ',0
menu_text: dc.b ' Accessory Arrays ',0
filename: dc.b 'E:\PRG_8\PRG_8BR.DAT',0
bss
align
;
; AES ARRAYS:
;
; The addresses of the arrays declared below will be stored in the pointer
; array 'aes_pb'. That happens because the program is assembled in Relocatable
; mode. The sizes declared for the arrays are identical simply because I am
; making it easy to line up the program's output.
control: ds.w 5 ; Control parameters.
global: ds.w 5 ; Global parameters.
int_in: ds.w 5 ; Input parameters.
int_out: ds.w 5 ; Output parameters.
addr_in: ds.w 5 ; Input addresses.
addr_out: ds.w 5 ; Output addresses.
;
; APPLICATION VARIABLES
;
file_handle: ds.w 1
test: ds.w 1
message: ds.l 4 ; 16 byte array.
menu_id: ds.w 1
buffer: ds.l $2000
ds.l 300 ; Program stack.
stack: ds.l 0 ; Address of program stack.
end
Execution Results
TEST POINT 0: Before appl_init
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0000 0: 0000 0: 0000 0: 0000 0: 0000 0: 0000
1: 0000 1: 0000 1: 0000 1: 0000 1: 0000 1: 0000
2: 0000 2: 0000 2: 0000 2: 0000 2: 0000 2: 0000
3: 0000 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 1: After appl_init, before menu_register
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 000A 0: 0120 0: 0000 0: 0005 0: 0000 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: 0000 1: 0000
2: 0001 2: 0005 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0000 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 2: After menu_register, before evnt_mesag
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0023 0: 0120 0: 0005 0: 0001 0: 0003 0: 0000
1: 0001 1: 0001 1: 0000 1: 0000 1: 0DCD 1: 0000
2: 0001 2: 0005 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 3: In message handler, before evnt_mesag
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0017 0: 0120 0: 0005 0: 0000 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: 0E36 1: 0000
2: 0001 2: 0005 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 4: In message handler second time
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0017 0: 0120 0: 0005 0: 0000 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: 0E36 1: 0000
2: 0001 2: 0005 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
Predeclaring AES Control Structures
In program 78, no space is reserved for the control
structure, and control array values are not stored just before
each AES function invocation. Instead, four function structures
are declared in the data section of the program. Each of these
function structures serves as array control when the particular
function is invoked. Initially, the address of the first
function structure is stored in the aes_pb slot reserved for
array control. Then, when it is time to invoke the other AES
functions, the address of the relevant function is inserted in
that slot.
Program 78. Illustrates the use of predefined AES control
structures and the AES file selector function.
; Program Name: PRG_8CR.S
; Version: 1.003
; Assembly Instructions:
; Assemble in Relocatable mode and save with a PRG extension. From
; the desktop, change the extension to ACC.
; Function:
; Identical to that of PRG_8BR.S, but this program uses a group of
; predeclared, function named arrays that are substituted for 'control' in
; the aes_pb, as each AES function is invoked. In addition, a path for the
; data file is chosen via the AES file selector function. An additional
; array set is provided after the fsel_input function is invoked, then a
; final set is printed when the message handler is entered the second time.
; Execution Instructions:
; Place PRG_8CR.ACC in the root directory of your boot disk. During the
; next power-up cycle, the desk accessory will be installed. From the desktop
; select 'Accessory Arrays' two times in order to store the pertinent data in
; the file buffer. At the first selection, the file selector will appear so
; that a path and filename can be chosen for the data file; the buffer contents
; are written to the file when the accessory is chosen the second time.
lea stack, a7 ; This must be the first instruction.
initialize_register_variables:
move.w #$C8, d3 ; *** D3 is variable for AES call number.
lea buffer(pc), a5 ; A5 is pointer to buffer.
lea aes_pb(pc), a4 ; A4 is pointer for aes pointer array.
lea hex_table(pc), a3 ; A3 points to hexadecimal ASCII digits.
; For each test point, the contents of each AES array are printed.
;
; TEST POINT 0: Before appl_init
;
bsr print_arrays
initialize_application: ; COMPUTE! AES book page 223.
; Application identification = apid returned in int_out[0] and global[2].
; Since the address of the predeclared appl_init array is already stored in
; aes_pb, only the trap 2 invocation need be done here.
bsr aes ; Invoke trap #2 AES exception.
;
; TEST POINT 1: After appl_init, before menu_register
;
bsr print_arrays
menu_installation: ; COMPUTE! AES book page 248.
; Menu identification number returned in int_out[0].
move.l #menu_register, (a4) ; Store address of next 'control' array.
lea global(pc), a0 ; Fetch address of global array.
move.w 4(a0), int_in ; Application identification to int_in[0].
move.l #menu_text, addr_in ; Menu text address to addr_in[0].
bsr aes
move.w int_out(pc), menu_id ; Store menu identification number.
; MAIN ACCESSORY LOOP
;
; TEST POINT 2: After menu_register, before evnt_mesag
;
bsr print_arrays
wait_for_message: ; Relinquish processor control.
move.l #evnt_mesag, (a4) ; Store address of next 'control' array.
move.l #message, addr_in ; Address of message array to addr_in.
; The above instruction must be executed each time evnt_mesag is invoked
; because the fsel_input function must store addresses in addr_in also.
; Furthermore, the evnt_mesag "control" array must be restored in aes_pb
; because it is replaced by the fsel_input "control" array whenever that
; function is invoked. If these alterations did not take place, then the
; evnt_mesag setup could be simplified as was done in PRG_8BR.S.
bsr aes
; When a message is received it is placed in array 'message'.
; ****************************************************************************
; ****************************************************************************
message_handler: ; Entrance point when message is received.
lea message(pc), a0 ; Fetch address of array 'message'.
cmpi.w #$28, (a0) ; Compare ACCESSORY OPEN code with message[0].
bne.s wait_for_message ; Execute the evnt_mesag function.
move.w 8(a0), d0 ; The menu item selected is stored in element
; four (message[4]) of array 'message'. This
; application's id # is in menu_id.
cmp.w menu_id(pc), d0 ; Was this application selected.
bne.s wait_for_message ; Execute the evnt_mesag function.
; ****************************************************************************
; ****************************************************************************
; Execution proceeds past this point only when this application has been
; selected from the menu.
;
; TEST POINT 3: In message handler, before fsel_input
;
cmpi.w #5, test ; Have six array groups been printed?
beq wait_for_message ; This effectively disables the handler.
bsr print_arrays
cmpi.w #5, test ; Is this 2nd entrance into message handler?
bne.s get_current_drive
store_in_file:
lea buffer(pc), a6 ; Fetch start address.
suba.l a6, a5 ; Calculate number of bytes stored in buffer.
create_file: ; COMPUTE! TOS book page 270.
move.w #0, -(sp) ; File attribute = read/write.
pea path(pc) ; Push address of path.
move.w #$3C, -(sp) ; Function = f_create = GEMDOS $3C.
trap #1 ; Path handle is returned in D0.
addq.l #8, sp
move.w d0, d4 ; Save path handle in D4.
write_buffer_to_file: ; Function = f_write. COMPUTE! TOS p.274.
pea (a6) ; Push buffer's address.
move.l a5, -(sp) ; Push byte count length.
move.w d4, -(sp) ; COMPUTE!'s TOS book incorrectly specifies
move.w #$40, -(sp) ; a longword operation here; see page 274.
trap #1
lea $C(sp), sp
close_output_file: ; Actually, close the path.
move.w d4, -(sp) ; Push path handle.
move.w #$3E, -(sp) ; Function = GEMDOS $3E = f_close.
trap #1
addq.l #4, sp
bra wait_for_message ; Execute the evnt_mesag function.
get_current_drive: ; Current drive = last drive accessed.
move.w #$19, -(sp) ; Function = c_getdrv.
trap #1 ; Current drive number returned in D0.
addq.l #2, sp
; Here we begin to construct the path that will be displayed when the
; file selector function is invoked. Below, I indicate the data that
; is actually being extracted from the system. Most references do not
; accurately describe what is being done with these functions.
add.w #$41, d0 ; Convert drive number to drive letter.
lea path(pc), a6 ; Fetch address of array 'path'.
move.b d0, (a6)+ ; Store drive letter in 'path' array.
move.b #$3A, (a6)+ ; Store a colon in 'path' array.
get_sub_directories: ; The drive is the main directory.
move.w #0, -(sp) ; See Internals page 135 for other values.
pea (a6) ; Push address of next 'path' array element.
move.w #$47, -(sp) ; Function = d_getpath = get subdirectories.
trap #1 ; Subdirectories are returned in the array
addq.l #8, sp ; 'path'.
add_extension:
; The extension '\*.*' is added to complete the path. This string must be
; NULL terminated. For example, assume that the get_current_drive routine
; returns #6; adding 41 would convert this to ASCII F. After the drive
; letter and the colon are stored in the path array, the elements of the
; array would contain:
; F:____________________________________,etc.
; Whatever the get_sub_directories routine returns will be placed in the path
; array; the first character will be stored in element 3. Assume that the
; routine returns \FILE_SEL as a subdirectory. The path array would contain:
; F:\FILE_SEL___________________________,etc.
; The extension is then added to the array contents to yield:
; F:\FILE_SEL\*.*O______________________,etc.
; where the O represents the NULL character. This is the path. At this
; time there is no file name in the path.
; When the file selector is displayed, the contents of array 'path' will
; appear on the path = directory line. The selection line will be blank
; because the program does not write anything to that line.
find_path_end: ; Find NULL at end of returned string.
tst.b (a6)+
bne find_path_end ; Branch back till NULL is found.
subq.l #1, a6 ; Back up to overwrite the NULL.
move.b #$5C, (a6)+ ; Add '\'.
move.b #$2A, (a6)+ ; Add '*'.
move.b #$2E, (a6)+ ; Add '.'.
move.b #$2A, (a6)+ ; Add '*'.
move.b #0, (a6) ; Add NULL.
display_file_selector: ; COMPUTE!'s AES book page 282.
; Returns the drive name and subdirectories selected by the user in the
; array 'path', and, if a file was selected, it is returned in the array
; 'file'.
move.l #fsel_input, (a4) ; Store address of next 'control' array.
initialize_address_in_array:
lea addr_in(pc), a1 ; Fetch address of array 'addr_in'.
lea path(pc), a6 ; Fetch address of array 'path'.
move.l a6, (a1) ; Store address of 'path' in 'addr_in'.
move.l #file, 4(a1) ; Store address of 'file' in 'addr_in'.
bsr aes
;
; TEST POINT 4: In message handler, after fsel_input
;
bsr print_arrays
analyze_returns: ; Look at returns for File Selector box.
tst.w int_out + 2 ; CANCEL button => 0.
beq wait_for_message ; Go there if CANCEL was selected.
tst.b file ; NO FILE CHOSEN => 0.
beq wait_for_message ; Go there if no file was chosen.
find_selected_path_end: ; Search for first asterisk.
; At this point, the selected path does not include the file name, so
; it is only a "partial" path.
cmp.b #$2A, (a6)+
bne find_selected_path_end
subq.l #1, a6 ; Back up to overwrite the asterisk.
; Here we add the file name to the path by appending it to the "partial"
; path. Then we will have constructed the complete path.
lea file(pc), a0
add_filename_to_path:
move.b (a0)+, (a6)+
bne.s add_filename_to_path ; Now we have a true path.
bra wait_for_message ; Execute the evnt_mesag function.
;
; SUBROUTINES
;
print_arrays:
lea newline(pc), a0
bsr store_line
lea test_header(pc), a0 ; Setup to fetch test point header.
move.w test(pc), d0 ; Load test point number into D0.
lsl.w #2, d0 ; Multiply by 4 to reach next pointer slot.
movea.l 0(a0,d0.w), a0 ; Print test point header.
bsr store_line
lea pre_spaces(pc), a0 ; Print spaces before column headers.
bsr store_line
lea aes_names(pc), a0 ; Print AES array column headers.
bsr store_line
lea pre_spaces(pc), a0 ; Print spaces before underline.
bsr store_line
lea aes_underline(pc), a0; Print AES underline.
bsr store_line
moveq.l #0, d7 ; D7 is up counter to print 5 rows.
moveq.l #4, d6 ; D6 is down counter to print 5 elements.
put_row:
lea aes_pb(pc), a6 ; Fetch parameter block address.
move.w #5, d5 ; D5 is array counter for 6 arrays.
move.w #11, d0 ; Print beginning spaces to line up columns.
put_space:
move.b #$20, (a5)+
dbra d0, put_space
put_element: ; Print contents of array element.
move.w d7, d0 ; Print array element number.
andi.b #$F, d0 ; Mask most significant nibble.
move.b 0(a3,d0.w), d0 ; Store appropriate hex character in D0.
move.b d0, (a5)+
move.b #$3A, (a5)+ ; A colon.
move.b #$20, (a5)+ ; A space.
move.w d7, d0 ; Multiply contents of D7 by 2 in D0 to
lsl.w #1, d0 ; obtain offset for next array element.
movea.l (a6)+, a1 ; Copy array address into A1 and increment
; A6 to point to next array address.
move.w 0(a1,d0.w), d0 ; Fetch contents of array element.
moveq #3, d2 ; D2 is loop counter for ASCII conversion.
convert_digit: ; Convert a nibble, then print it.
rol.w #4, d0 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d0, d1 ; Copy least significant byte of D0 to D1.
andi.b #$F, d1 ; Mask out most significant nibble of D1.
ext.w d1 ; Extend to word length.
move.b 0(a3,d1.w), d1 ; Fetch ASCII hex digit to D1.
put_digit:
move.b d1, (a5)+
dbra d2, convert_digit ; Loop until D2 = -1.
_put_spaces:
move.b #$20, (a5)+
move.b #$20, (a5)+
dbra d5, put_element
lea newline(pc), a0 ; Print a newline.
bsr store_line
addi.w #1, d7 ; Increment up counter.
dbra d6, put_row
add.w #1, test ; Increment test for next test point.
rts
aes: ; COMPUTE! AES book page 13,
move.l a4, d1 ; Address of aes_pb.
move.w d3, d0 ; AES identifier = $C8.
trap #2
rts
store_line:
move.b (a0)+, d0
beq.s end_of_string
move.b d0, (a5)+ ; Store byte in buffer.
bra.s store_line
end_of_string:
rts
data
aes_pb: dc.l appl_init,global,int_in,int_out,addr_in,addr_out
test_header: dc.l zero,one,two,three,four,five
zero:
dc.b $D,$A,'TEST POINT 0: Before appl_init',$D,$A,$D,$A,0
one:
dc.b $D,$A,'TEST POINT 1: After appl_init, before menu_register',$D,$A,$D,$A,0
two:
dc.b $D,$A,'TEST POINT 2: After menu_register, before evnt_mesag',$D,$A,$D,$A,0
three:
dc.b $D,$A,'TEST POINT 3: In message handler, before fsel_input',$D,$A,$D,$A,0
four:
dc.b $D,$A,'TEST POINT 4: In message handler, after fsel_input',$D,$A,$D,$A,0
five:
dc.b $D,$A,'TEST POINT 5: In message handler second time',$D,$A,$D,$A,0
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
aes_header: dc.b ' AES ARRAYS',$D,$A,0
aes_names: dc.b 'CONTROL GLOBAL INT_IN INT_OUT ADDR_IN '
dc.b 'ADDR_OUT',$D,$A,0
aes_underline: dc.b '------- ------- ------- ------- ------- '
dc.b '--------',$D,$A,0
pre_spaces: dc.b ' ',0
spaces: dc.b ' ',0
menu_text: dc.b ' Accessory Arrays ',0
align
;
; AES ARRAYS:
;
; PREDEFINED 'CONTROL' STRUCTURES
appl_init: ; This one is prestored in aes_pb.
dc.w 10
dc.w 0
dc.w 1
dc.w 0
dc.w 0
menu_register:
dc.w 35
dc.w 1
dc.w 1
dc.w 1
dc.w 0
evnt_mesag:
dc.w 23
dc.w 0
dc.w 1
dc.w 1
dc.w 0
fsel_input:
dc.w 90
dc.w 0
dc.w 2
dc.w 2
dc.w 0
; The addresses of the arrays declared below will be stored in the pointer
; array 'aes_pb'. That happens because the program is assembled in Relocatable
; mode. The sizes declared for the arrays are identical simply because I am
; making it easy to line up the program's output.
; Note that array 'control' is not declared below,.
bss ; The location of this assembler directive is critical. It must
; not be placed before the predeclared 'control' structures.
global: ds.w 5 ; Global parameters.
int_in: ds.w 5 ; Input parameters.
int_out: ds.w 5 ; Output parameters.
addr_in: ds.w 5 ; Input addresses.
addr_out: ds.w 5 ; Output addresses.
;
; FSEL_INPUT ARRAYS
;
path: ds.b 120 ; Array for drive name and subdirectories.
file: ds.b 14 ; Array for selected file.
;
; APPLICATION VARIABLES
;
file_handle: ds.w 1
test: ds.w 1
message: ds.l 4 ; 16 byte array.
menu_id: ds.w 1
buffer: ds.l $2000
ds.l 300 ; Program stack.
stack: ds.l 0 ; Address of program stack.
end
Execution Results
TEST POINT 0: Before appl_init
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 000A 0: 0000 0: 0000 0: 0000 0: 0000 0: 0000
1: 0000 1: 0000 1: 0000 1: 0000 1: 0000 1: 0000
2: 0001 2: 0000 2: 0000 2: 0000 2: 0000 2: 0000
3: 0000 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 1: After appl_init, before menu_register
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 000A 0: 0120 0: 0000 0: 0003 0: 0000 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: 0000 1: 0000
2: 0001 2: 0003 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0000 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 2: After menu_register, before evnt_mesag
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0023 0: 0120 0: 0003 0: 0001 0: 0003 0: 0000
1: 0001 1: 0001 1: 0000 1: 0000 1: D3D8 1: 0000
2: 0001 2: 0003 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 3: In message handler, before fsel_input
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0017 0: 0120 0: 0003 0: 0001 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: D4D0 1: 0000
2: 0001 2: 0003 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 4: In message handler, after fsel_input
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 005A 0: 0120 0: 0003 0: 0001 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0001 1: D446 1: 0000
2: 0002 2: 0003 2: 0000 2: 9C58 2: 0003 2: 0000
3: 0002 3: 0000 3: 0000 3: 0000 3: D4BE 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 5: In message handler second time
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0017 0: 0120 0: 0003 0: 0001 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0001 1: D4D0 1: 0000
2: 0001 2: 0003 2: 0000 2: 9C58 2: 0003 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: D4BE 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
Execution Results Discussion
There is nothing unexpected in the test point tables
produced by the program. Note that the first table does contain
the values stored in the appl_init array since it was predeclared
and its address was prestored in aes_pb. In the test point 5
table, note that addr_in[0] contains the address of the message
array, as it should, but that the fsel_input required address
that was stored in addr_in[1] during that function's invocation
has not been disturbed. The stability of that array element
could be used to advantage if it was necessary to invoke
fsel_input again.
Spawning a Program to Create a Desk Accessory's File
Each ST programmer tends to concentrate productivity in
areas of personal interest and commitment; therefore, it is not
unusual to find that information passed on to one programmer by
another is not quite applicable to the second programmer's
particular programming applications. For example, I am
completely neglecting the concept of color monitor programming
requirements in this book. In order to offset this neglect, I am
inclined to illustrate several methods of accomplishing a task.
In some cases, I am prompted to do so because of something I have
heard or read; in other cases, I feel that readers may notice
something of assistance within the additional material. It is
with from this viewpoint that I include programs 79 and 80.
This pair of programs also create a file and store the
contents of the AES structures therein, but the desk accessory,
program 79, does not create the file; instead, it stores its data
in a buffer and spawns program 80 so that it can create the file,
write the buffer contents therein and properly close the file. I
have chosen particular directory paths for the programs and the
data files; if you decide to experiment with the programs, you
may want to alter those paths.
Program 79. Spawning a program from within a desk accessory so
that the spawned program can create a file in which to store data
generated by the desk accessory.
; Program Name: PRG_8DR.S
; Version: 1.002
; Assembly Instructions:
; Assemble in Relocatable mode and save with a PRG extension. From
; the desktop, change the extension to ACC. Programs that are to function
; as desk accessories MUST be assembled in Relocatable mode. If you design
; a desk accessory so that it can be assembled in PC-relative mode, and if
; you attempt to load that accessory via MultiDesk, you will receive an
; error pertaining to the attempt to read in the accessory. If you place
; that accessory in the boot directory, the system will reset every time
; it attempts to load the accessory. Sei gewarnt!
; Function:
; Identical to that of PRG_8CR.S, but this program does not create a
; file and place data directly therein. Instead, another program is spawned
; to create the file and transfer the data from the buffer to the file. The
; name of the disk file to be created by PRG_8EP.TOS is passed as a parameter
; in an environmental string.
; Execution Instructions:
; Place PRG_8DR.ACC and PRG_8EP.TOS in the root directory of your boot
; disk. During the next power-up cycle, the desk accessory will be installed.
; From the desktop select 'Accessory Arrays' two times in order to store the
; pertinent data in the file buffer. After the second selection, PRG_8EP.TOS
; will be executed. PRG_8EP.TOS will create file PRG_8DR.DAT, write the
; contents of the buffer to the file and close the file properly. You can
; execute the desk accessory from within MultiDesk, but don't forget that
; PRG_8EP.TOS must be in the same directory as is PRG_8DR.ACC. You may want
; to change the path for the file so that it is created on another disk or disk
; partition.
lea stack, a7 ; This must be the first instruction.
; Note that when the buffer is declared before the stack, pc-relative
; addressing can't be used in the instruction above, because of the distance
; between the instruction and the address of the stack.
initialize_register_variables:
move.w #$C8, d3 ; *** D3 is variable for AES call number.
lea buffer(pc), a5 ; A5 is pointer to buffer.
lea aes_pb(pc), a4 ; A4 is pointer for aes pointer array.
lea hex_table(pc), a3 ; A3 points to hexadecimal ASCII digits.
; The file buffer's address must be converted to an ASCII hexadecimal string
; and stored in the variable "command_string" so that it can be passed in that
; form to the spawned process, PRG_8EP.TOS. Note that the 8 digit string can be
; passed in "pure" form when it is done in this manner; that is, no quotes nor
; NULL character is needed. Refer to COMPUTE!'s TOS book pages 100 - 102 for
; other important information concerning the spawning process.
store_buffer_address_in_command_line:
move.l a5, d0
lea command_string, a0 ; Fetch address of command line string.
moveq #7, d2 ; D2 is loop counter for ASCII conversion.
_convert_digit: ; Convert a nibble, then store it.
rol.l #4, d0 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d0, d1 ; Copy least significant byte of D0 to D1.
andi.b #$F, d1 ; Mask out most significant nibble of D1.
ext.w d1 ; Extend to word length.
move.b 0(a3,d1.w), d1 ; Fetch ASCII hex digit to D1.
_put_digit:
move.b d1, (a0)+
dbra d2, _convert_digit ; Loop until D2 = -1.
; For each test point, the contents of each AES array are printed.
;
; TEST POINT 0: Before appl_init
;
bsr print_arrays
initialize_application: ; COMPUTE! AES book page 223.
; Application identification = apid returned in int_out[0] and global[2].
; Since the address of the predeclared appl_init array is already stored in
; aes_pb, only the trap 2 invocation need be done here.
bsr aes ; Invoke trap #2 AES exception.
;
; TEST POINT 1: After appl_init, before menu_register
;
bsr print_arrays
menu_installation: ; COMPUTE! AES book page 248.
; Menu identification number returned in int_out[0].
move.l #menu_register, (a4) ; Store address of next "control" array.
lea global(pc), a0 ; Fetch address of global array.
move.w 4(a0), int_in ; Application identification to int_in[0].
move.l #menu_text, addr_in ; Menu text address to addr_in[0].
bsr aes
move.w int_out(pc), menu_id ; Store menu identification number.
; MAIN ACCESSORY LOOP
;
; TEST POINT 2: After menu_register, before evnt_mesag
;
bsr print_arrays
move.l #message, addr_in ; Address of message array to addr_in.
move.l #evnt_mesag, (a4) ; Store address of next "control" array.
wait_for_message: ; COMPUTE! AES book page 235.
bsr aes
; When a message is received it is placed in array 'message'. Note the
; short cuts taken above. Since the aes_pb and the addr_in structures remain
; uncorrupted after they have been initialized for the evnt_mesag function,
; only the trap 2 exception need be invoked each time that function is to be
; executed.
; ****************************************************************************
; ****************************************************************************
message_handler: ; Entrance point when message is received.
lea message(pc), a0 ; Fetch address of array 'message'.
cmpi.w #$28, (a0) ; Compare ACCESSORY OPEN code with message[0].
bne.s wait_for_message ; Execute the evnt_mesag function.
move.w 8(a0), d0 ; The menu item selected is stored in element
; four (message[4]) of array 'message'. This
; application's id # is in menu_id.
cmp.w menu_id(pc), d0 ; Was this application selected.
bne.s wait_for_message ; Execute the evnt_mesag function.
; ****************************************************************************
; ****************************************************************************
; Execution proceeds past this point only when this application has been
; selected from the menu.
;
; TEST POINT 3: In message handler, before evnt_mesag
;
cmpi.w #5, test ; Have five array groups been printed?
beq wait_for_message
bsr print_arrays
cmpi.w #5, test ; Branch after 2nd entrance in message handler.
beq.s store_in_file ; Spawn process to create file.
bra wait_for_message ; Execute the evnt_mesag function.
store_in_file:
move.b #0, (a5) ; Store NULL at end of file buffer.
pea environ_string
pea command_string ; Contains address of file buffer.
pea program ; Push address of program name string.
move.w #0, -(sp)
move.w #$4B, -(sp) ; Function = GEMDOS $4B = p_exec.
trap #1
lea $10(sp), sp
bra wait_for_message ; Execute the evnt_mesag function.
;
; SUBROUTINES
;
print_arrays:
lea newline(pc), a0
bsr store_line
lea test_header(pc), a0 ; Setup to fetch test point header.
move.w test(pc), d0 ; Load test point number into D0.
lsl.w #2, d0 ; Multiply by 4 to reach next pointer slot.
movea.l 0(a0,d0.w), a0 ; Print test point header.
bsr store_line
lea pre_spaces(pc), a0 ; Print spaces before column headers.
bsr store_line
lea aes_names(pc), a0 ; Print AES array column headers.
bsr store_line
lea pre_spaces(pc), a0 ; Print spaces before underline.
bsr store_line
lea aes_underline(pc), a0; Print AES underline.
bsr store_line
moveq.l #0, d7 ; D7 is up counter to print 5 rows.
moveq.l #4, d6 ; D6 is down counter to print 5 elements.
put_row:
lea aes_pb(pc), a6 ; Fetch parameter block address.
move.w #5, d5 ; D5 is array counter for 6 arrays.
move.w #11, d0 ; Print beginning spaces to line up columns.
put_space:
move.b #$20, (a5)+
dbra d0, put_space
put_element: ; Print contents of array element.
move.w d7, d0 ; Print array element number.
andi.b #$F, d0 ; Mask most significant nibble.
move.b 0(a3,d0.w), d0 ; Store appropriate hex character in D0.
move.b d0, (a5)+
move.b #$3A, (a5)+ ; A colon.
move.b #$20, (a5)+ ; A space.
move.w d7, d0 ; Multiply contents of D7 by 2 in D0 to
lsl.w #1, d0 ; obtain offset for next array element.
movea.l (a6)+, a1 ; Copy array address into A1 and increment
; A6 to point to next array address.
move.w 0(a1,d0.w), d0 ; Fetch contents of array element.
moveq #3, d2 ; D2 is loop counter for ASCII conversion.
convert_digit: ; Convert a nibble, then print it.
rol.w #4, d0 ; Rotate most significant nibble to the
; least significant nibble position.
move.b d0, d1 ; Copy least significant byte of D0 to D1.
andi.b #$F, d1 ; Mask out most significant nibble of D1.
ext.w d1 ; Extend to word length.
move.b 0(a3,d1.w), d1 ; Fetch ASCII hex digit to D1.
put_digit:
move.b d1, (a5)+
dbra d2, convert_digit ; Loop until D2 = -1.
_put_spaces:
move.b #$20, (a5)+
move.b #$20, (a5)+
dbra d5, put_element
lea newline(pc), a0 ; Print a newline.
bsr store_line
addi.w #1, d7 ; Increment up counter.
dbra d6, put_row
add.w #1, test ; Increment test for next test point.
rts
aes: ; COMPUTE! AES book page 13,
move.l a4, d1 ; Address of aes_pb.
move.w d3, d0 ; AES identifier = $C8.
trap #2
rts
store_line:
move.b (a0)+, d0
beq.s end_of_string
move.b d0, (a5)+ ; Store byte in buffer.
bra.s store_line
end_of_string:
rts
data
aes_pb: dc.l appl_init,global,int_in,int_out,addr_in,addr_out
; PREDEFINED 'CONTROL' STRUCTURES
appl_init: ; This one is prestored in aes_pb.
dc.w 10
dc.w 0
dc.w 1
dc.w 0
dc.w 0
menu_register:
dc.w 35
dc.w 1
dc.w 1
dc.w 1
dc.w 0
evnt_mesag:
dc.w 23
dc.w 0
dc.w 1
dc.w 1
dc.w 0
test_header: dc.l zero,one,two,three,four
zero:
dc.b $D,$A,'TEST POINT 0: Before appl_init',$D,$A,$D,$A,0
one:
dc.b $D,$A,'TEST POINT 1: After appl_init, before menu_register',$D,$A,$D,$A,0
two:
dc.b $D,$A,'TEST POINT 2: After menu_register, before evnt_mesag',$D,$A,$D,$A,0
three:
dc.b $D,$A,'TEST POINT 3: In message handler, before evnt_mesag',$D,$A,$D,$A,0
four:
dc.b $D,$A,'TEST POINT 4: In message handler second time',$D,$A,$D,$A,0
hex_table: dc.b '0123456789ABCDEF'
newline: dc.b $D,$A,0
aes_header: dc.b ' AES ARRAYS',$D,$A,0
aes_names: dc.b 'CONTROL GLOBAL INT_IN INT_OUT ADDR_IN '
dc.b 'ADDR_OUT',$D,$A,0
aes_underline: dc.b '------- ------- ------- ------- ------- '
dc.b '--------',$D,$A,0
pre_spaces: dc.b ' ',0
spaces: dc.b ' ',0
menu_text: dc.b ' Accessory Arrays ',0
program: dc.b 'PRG_8EP.TOS',0
environ_string: dc.b 'E:\PRG_8\PRG_8DR.DAT',0
bss
command_string: ds.b 30 ; Must contain address of buffer.
align
;
; AES ARRAYS:
;
; The addresses of the arrays declared below will be stored in the pointer
; array 'aes_pb'. That happens because the program is assembled in Relocatable
; mode. The sizes declared for the arrays are identical simply because I am
; making it easy to line up the program's output.
; Note that array "control" is not declared below.
global: ds.w 5 ; Global parameters.
int_in: ds.w 5 ; Input parameters.
int_out: ds.w 5 ; Output parameters.
addr_in: ds.w 5 ; Input addresses.
addr_out: ds.w 5 ; Output addresses.
;
; APPLICATION VARIABLES
;
test: ds.w 1
message: ds.l 4 ; 16 byte array.
menu_id: ds.w 1
buffer: ds.l $2000
ds.l 300 ; Program stack.
stack: ds.l 0 ; Address of program stack.
end
Program 80. The program that creates the file for program 79.
This program accepts a file name as a passed parameter in an
environmental string. Note that the data passed to this program
via its command line is processed with an ASCII hexadecimal
string to binary number conversion algorithm.
; Program Name: PRG_8EP.S
; Version: 1.001
; Assembly Instructions:
; Assemble in PC-relative mode and save with a TOS suffix.
; Execution Instructions:
; This program is spawned by PRG_8DR.ACC. Both programs must reside in
; the same directory.
; Function:
; This program creates disk file specified in the first environmental string
; and stores therein the data contained in PRG_8DR.ACC's buffer. The purpose
; of this exercise is to illustrate an alternate method of creating a disk file
; for a desk accessory, writing data to the file and invoking proper file
; closure.
fetch_command_line_address:
lea -$82(pc), a3
fetch_environmental_string_address:
lea -$54(a3), a4 ; Fetch address of string pointer.
movea.l (a4), a4 ; Fetch address of string.
; The data in the command line is a string of eight ASCII hexadecimal digits
; passed from PRG_8DR.ACC when that program spawned this one. There are no
; characters before that string and none following. The string is the address
; of PRG_8DR.ACC's buffer, in which data that is to be written to a disk file
; has been stored. The address must be converted from an ASCII hexadecimal
; string to a binary longword.
; The data in the environmental string can be used as it exist.
process_command_line: ; Convert ASCII hexadecimal string to binary.
moveq.l #0, d0 ; Accumulator.
moveq.l #7, d2 ; Loop counter.
convert_string:
move.b (a3)+, d1 ; D1 is used for each character conversion.
cmpi.b #$39, d1 ; Character greater than 9 test.
bgt not_decimal
cmpi.b #$30, d1 ; Character less than 0 test.
blt not_valid
subi.b #$30, d1 ; Convert ASCII hex character to binary.
bra.s accumulate
not_decimal:
cmpi.b #$41, d1 ; Character less than A test.
blt not_valid
cmpi.b #$46, d1 ; Character greater than F test.
bgt not_valid
subi.b #$37, d1 ; Convert ASCII hex character to binary.
accumulate:
lsl.l #4, d0 ; Shift one nibble = one hex character.
add.b d1, d0
dbra d2, convert_string
move.l d0, a3 ; Address of buffer is in D0.
movea.l a3, a0 ; Copy for buffer byte count.
count_bytes_in_buffer:
moveq.l #0, d0 ; NULL byte test register.
moveq.l #0, d3 ; Byte count accumulator.
count:
move.b (a0)+, d0 ; Look at each character in buffer and
beq create_file ; accumulate count until NULL is found at
addq.l #1, d3 ; end of buffer.
bra.s count
create_file: ; COMPUTE! TOS book page 270.
move.w #0, -(sp) ; File attribute = read/write.
pea (a4) ; Environmental string has file's path.
move.w #$3C, -(sp) ; Function = f_create = GEMDOS $3C.
trap #1 ; File handle is returned in D0.
addq.l #8, sp
move.w d0, d4 ; Save file handle in D4.
write_buffer_to_file: ; Function = f_write. COMPUTE! TOS p.274.
pea (a3) ; Push buffer's address.
move.l d3, -(sp) ; Push byte count length.
move.w d4, -(sp) ; COMPUTE!'s TOS book incorrectly specifies
move.w #$40, -(sp) ; a longword operation here; see page 274.
trap #1
lea $C(sp), sp
close_output_file: ; COMPUTE! TOS book page 272.
move.w d4, -(sp) ; Push file handle.
move.w #$3E, -(sp) ; Function = GEMDOS $3E = f_close.
trap #1
addq.l #4, sp
not_valid:
terminate:
move.w #0, -(sp)
trap #1
data
align
bss
program_end: ds.l 0
end
Execution Results
TEST POINT 0: Before appl_init
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 000A 0: 0000 0: 0000 0: 0000 0: 0000 0: 0000
1: 0000 1: 0000 1: 0000 1: 0000 1: 0000 1: 0000
2: 0001 2: 0000 2: 0000 2: 0000 2: 0000 2: 0000
3: 0000 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 1: After appl_init, before menu_register
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 000A 0: 0120 0: 0000 0: 0005 0: 0000 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: 0000 1: 0000
2: 0001 2: 0005 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0000 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 2: After menu_register, before evnt_mesag
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0023 0: 0120 0: 0005 0: 0000 0: 0003 0: 0000
1: 0001 1: 0001 1: 0000 1: 0000 1: 0DD5 1: 0000
2: 0001 2: 0005 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 3: In message handler, before evnt_mesag
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0017 0: 0120 0: 0005 0: 0000 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: 0E5C 1: 0000
2: 0001 2: 0005 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
TEST POINT 4: In message handler second time
CONTROL GLOBAL INT_IN INT_OUT ADDR_IN ADDR_OUT
------- ------- ------- ------- ------- --------
0: 0017 0: 0120 0: 0005 0: 0000 0: 0003 0: 0000
1: 0000 1: 0001 1: 0000 1: 0000 1: 0E5C 1: 0000
2: 0001 2: 0005 2: 0000 2: 9C58 2: 0000 2: 0000
3: 0001 3: 0000 3: 0000 3: 0000 3: 0000 3: 0000
4: 0000 4: 0000 4: 0000 4: 0000 4: 0000 4: 0000
A Practical Example
As a practical desk accessory example, I have chosen a
program which represents a continuation of the linear print
buffer discussion of the last chapter. You will be able to
invoke this program from within any application permitting access
to desk accessories. Furthermore, since you should be somewhat
familiar with the program's purpose, you will be able to
concentrate on the AES functions introduced to invoke the alert
form. I suggest that you change the name of this program and put
it to good use as a desk accessory. I call the source file
PRTASCII.S and the object file PRTASCII.ACC in my system.
Program 81. A desk accessory linear print buffer. The AES file
selector function is invoked to obtain the path for the file to
be printed. If the desk accessory is selected while it is busy
with a file, the form_alert function is invoked.
; Program Name: PRG_8FR.S
; Version: 1.002
; Assembly Instructions:
; Assemble in Relocatable mode and save with a PRG extension. From
; the desktop, change the extension to ACC. Programs that are to function
; as desk accessories MUST be assembled in Relocatable mode.
; Function:
; A desk accessory that prints the file selector chosen ASCII file.
; The print buffer which is used by this program is linear, not circular.
; This program also installs a parallel interface interrupt handler. The
; handler is invoked each time that the logic level of pin 11 of the parallel
; port drops from high to low.
; This program initiates the interrupt controlled printing process by
; sending a NULL character to the printer (A BELL character could also be
; used, if that is desirable.).
; The default size of the linear print buffer is 32,768 bytes. You can
; alter the buffer size and assemble the program with the larger size if you
; desire.
; The program reads the file to be printed and stores it in the buffer
; very rapidly, but the speed with which the buffer is cleared depends on
; the particular system configuration: if there is an external hardware buffer
; between the computer and the printer, or if the printer has an internal
; buffer of significant size, then the linear buffer will transfer its
; information to the external buffers very rapidly also.
; If there are no such buffers to speed up the transfer rate from the
; buffer to the printer, then it is possible that you may interrupt a session
; in progress by selecting the desk accessory before the buffer is clear. If
; that event occurs, an alert box will be displayed indicating that the buffer
; is busy. You will be able to select OK to continue the printing session
; (a Return key press is sufficient), or you may select CANCEL to stop the
; session. If you do choose to cancel the session, the buffer will be cleared
; immediately; but remember that the printer will continue to print until its
; internal buffer, however small, is also cleared.
; Execution Instructions:
; Place PRG_8FR.ACC in the root directory of your boot disk. During
; the next power-up cycle, the program will be installed in memory as a desk
; accessory; assuming that it is not blocked by a program that permits desk
; accessory loading selections. Of course, if you use MultiDesk, you need
; not power up to use PRG_8FR.ACC immediately.
; The desk accessory is identified as a menu selection by the name
; PRINT ASCII.
; MAJOR NOTE:
; Although accessories such as MultiDesk add significant power to a ST
; system; and although initial testing of a desk accessory program can be
; accomplished via MultiDesk loading of the program, thereby alleviating the
; the testing process, the program under test cannot be pronounced correct
; until it has been permitted to load normally from the boot disk.
; For example, if the first statement of this program, which loads the
; stack's address into A7, is moved to a location following the routine that
; installs the interrupt handler, the accessory will function perfectly when
; it is loaded and executed via MultiDesk; but it will bomb during boot
; because, at that time, no default stack has yet been assigned by the system.
; REMEMBER: Programs executed during boot MUST provide their own stacks.
; NOTE: Within the program, registers are used as variables as much as
; possible to speed things up.
lea stack(pc), a7 ; This must be the first instruction.
install_interrupt_handler_vector:; Parallel port pin 11 interrupt handler.
pea interrupt_handler(pc); Push interrupt handler address.
move.w #0, -(sp) ; Interrupt level for MFP GPIP bit 0.
move.w #$D, -(sp) ; Functon = XBIOS #13 decimal = mfpint.
trap #14
addq.l #8, sp
install_desk_accessory:
lea aes_pb(pc), a3 ; aes_pb = AES parameter block.
move.l a3, d5 ; Use D5 as a variable for aes_pb address.
lea control(pc), a4 ; A4 is pointer for array 'control'.
move.w #$C8, d3 ; *** D3 is variable for AES call number.
initialize_application:
move.w #$A, (a4) ; Function = appl_init = AES $A.
move.w #1, 4(a4) ; Return one int_out parameter.
move.l d5, d1 ; D5 contains address of aes parameter block.
move.w d3, d0 ; D3 contains AES call number.
trap #2 ; apid returned in int_out[0] and global[2].
menu_installation:
move.w #$23, (a4) ; Function = menu_register = AES $23.
move.w #1, 2(a4) ; Pass one int_in parameter.
move.w #1, 6(a4) ; Pass one addr_in parameter.
lea int_out(pc), a6 ; Fetch address of int_out array.
move.w (a6), int_in ; Application identification to int_in[0].
lea addr_in(pc), a5 ; Fetch address of addr_in array.
move.l #menu_text, (a5) ; Menu text address to addr_in[0].
move.l d5, d1 ; Address of aes parameter block to D1.
move.w d3, d0 ; D3 contains AES call number.
trap #2 ; Menu identification number returned
; in int_out[0].
move.w (a6), d4 ; Store menu identification number in D4.
; MAIN ACCESSORY LOOP
lea message(pc), a3 ; A3 is variable for message array address.
wait_for_message: ; Relinquish processor control.
move.w #$17, (a4) ; Function = evnt_mesag = AES $17.
move.w #0, 2(a4) ; Input one 16-bit integer parameter.
move.w #1, 4(a4) ; Return one 16-bit integer parameter.
move.w #1, 6(a4) ; Input one 32-bit pointer parameter.
move.w #0, 8(a4) ; Return no 32-bit pointer parameters.
move.l a3, (a5) ; Address of message array to addr_in.
; The above instruction must be executed each time evnt_mesag is invoked
; because the form_alert and fsel_input functions must store addresses in
; addr_in also. If that were not so, then the evnt_mesag setup could be
; simplified; that is, the address of the message array could be stored in
; addr_in once, before the wait_for_message label.
; In addition, I have been successful with the following abbreviated
; implementation of the evnt_mesag function, even though the fsel_input
; function intervenes between evnt_mesag invocations:
; lea message(pc), a3
; wait_for_message:
; move.w #$17, (a4)
; move.w #0, 2(a4)
; move.l a3, (a5)
; move.l d5, d1
; move.w d3, d0
; trap #2
; Therefore, it does not seem to matter that the fsel_input function
; requires that the value 2 in 4(a4) and 6(a4), while the evnt_mesag
; function requires the value 1 in those control array elements. I have
; not used the abbreviated version in this program because I want to avoid
; any unexplained phenomenona in your implementation of the program. But
; you should be aware of this type of simplification technique because it
; permits shorter, faster programs.
move.l d5, d1 ; Address of aes parameter block to D1.
move.w d3, d0 ; AES call number to D0.
trap #2
; When a message is received it is placed in array 'message'.
; ****************************************************************************
; ****************************************************************************
; Execution proceeds past this point only when a message is received.
; Preserve contents of registers A3, A4, A5, D3, D4, and D5 during the rest of
; the program.
message_handler: ; Entrance point when message is received.
lea message(pc), a0 ; Fetch address of array 'message'.
cmpi.w #$28, (a0) ; Compare ACCESSORY OPEN code with message[0].
bne.s wait_for_message ; Execute the evnt_mesag function.
move.w 8(a0), d0 ; The menu item selected is stored in element
; four (message[4]) of array 'message'. This
; application's id # is in D4.
cmp.w d4, d0 ; Was this application selected.
bne.s wait_for_message ; Execute the evnt_mesag function.
; ****************************************************************************
; ****************************************************************************
; Execution proceeds past this point only when this application has been
; selected from the menu.
; Preserve contents of registers A3, A4, A5, D3, D4, and D5 during the rest of
; the program.
busy_test: ; Printing session in progress test.
move.l filesize, d0 ; Fetch file size.
beq.s get_current_drive ; Display alert if buffer is busy.
form_alert:
move.w #$34, (a4) ; Function = form_alert = AES $34.
move.w #1, 2(a4) ; Input one 16-bit integer parameter.
move.w #1, 4(a4) ; Return one 16-bit integer parameter.
move.w #1, 6(a4) ; Input one 32-bit pointer parameter.
move.w #0, 8(a4) ; Return no 32-bit pointer parameters.
move.w #1, int_in ; Default exit button to int_in[0].
move.l #alert_text, addr_in ; Alert text address to addr_in[0].
move.l d5, d1 ; Address of aes parameter block to D1.
move.w d3, d0 ; AES call number to D0.
trap #2
cmp.w #1, int_out ; Button test.
beq wait_for_message ; Continue printing.
move.l #0, filesize ; Cancel printing session.
move.l #buffer, extract_pointer
bra wait_for_message
get_current_drive: ; Current drive = last drive accessed.
move.w #$19, -(sp) ; Function = c_getdrv.
trap #1 ; Current drive number returned in D0.
addq.l #2, sp
; Here we begin to construct the path that will be displayed when the
; file selector function is invoked. Below, I indicate the data that
; is actually being extracted from the system. Most references do not
; accurately describe what is being done with these functions.
add.w #$41, d0 ; Convert drive number to drive letter.
lea path(pc), a6 ; Fetch address of array 'path'.
move.b d0, (a6)+ ; Store drive letter in 'path' array.
move.b #$3A, (a6)+ ; Store a colon in 'path' array.
get_sub_directories: ; The drive is the main directory.
move.w #0, -(sp) ; See Internals page 135 for other values.
pea (a6) ; Push address of next 'path' array element.
move.w #$47, -(sp) ; Function = d_getpath = get subdirectories.
trap #1 ; Subdirectories are returned in the array
addq.l #8, sp ; 'path'.
add_extension:
; The extension '\*.*' is added to complete the path. This string must be
; NULL terminated. For example, assume that the get_current_drive routine
; returns #6; adding 41 would convert this to ASCII F. After the drive
; letter and the colon are stored in the path array, the elements of the
; array would contain:
; F:____________________________________,etc.
; Whatever the get_sub_directories routine returns will be placed in the path
; array; the first character will be stored in element 3. Assume that the
; routine returns \FILE_SEL as a subdirectory. The path array would contain:
; F:\FILE_SEL___________________________,etc.
; The extension is then added to the array contents to yield:
; F:\FILE_SEL\*.*O______________________,etc.
; where the O represents the NULL character. This is the path. At this
; time there is no file name in the path.
; When the file selector is displayed, the contents of array 'path' will
; appear on the path = directory line. The selection line will be blank
; because the program does not write anything to that line.
find_path_end: ; Find NULL at end of returned string.
tst.b (a6)+
bne find_path_end ; Branch back till NULL is found.
subq.l #1, a6 ; Back up to overwrite the NULL.
move.b #$5C, (a6)+ ; Add '\'.
move.b #$2A, (a6)+ ; Add '*'.
move.b #$2E, (a6)+ ; Add '.'.
move.b #$2A, (a6)+ ; Add '*'.
move.b #0, (a6) ; Add NULL.
display_file_selector: ; COMPUTE!'s AES book page 282.
move.w #$5A, (a4) ; Function = fsel_input.
move.w #0, 2(a4) ; Returns the drive name and subdirectories
move.w #2, 4(a4) ; selected by the user in the array 'path'
move.w #2, 6(a4) ; and, if a file was selected, it is returned
move.w #0, 8(a4) ; in the array 'file'.
initialize_address_in_array:
lea addr_in(pc), a1 ; Fetch address of array 'addr_in'.
lea path(pc), a6 ; Fetch address of array 'path'.
move.l a6, (a1) ; Store address of 'path' in 'addr_in'.
move.l #file, 4(a1) ; Store address of 'file' in 'addr_in'.
move.l d5, d1 ; Address of aes parameter block to D1.
move.w d3, d0 ; D3 contains AES call number.
trap #2
analyze_returns: ; Look at returns for File Selector box.
tst.w int_out + 2 ; CANCEL button => 0.
beq wait_for_message ; Go there if CANCEL was selected.
tst.b file ; NO FILE CHOSEN => 0.
beq wait_for_message ; Go there if no file was chosen.
find_selected_path_end: ; Search for first asterisk.
; At this point, the selected path does not include the file name, so
; it is only a "partial" path.
cmp.b #$2A, (a6)+
bne find_selected_path_end
subq.l #1, a6 ; Back up to overwrite the asterisk.
; Here we add the file name to the path by appending it to the "partial"
; path. Then we will have constructed the complete path.
lea file(pc), a0
add_filename_to_path:
move.b (a0)+, (a6)+
bne.s add_filename_to_path ; Now we have a true path.
set_dta:
pea dta(pc) ; dta = address of 44 byte buffer.
move.w #$1A, -(sp) ; GEMDOS function = set dta.
trap #1
addq.l #6, sp
search_for_file:
move.w #0, -(sp) ; Attribute = normal access.
pea path(pc) ; Push address of path.
move.w #$4E, -(sp) ; GEMDOS function = search first.
trap #1
addq.l #8, sp
tst d0
bne wait_for_message ; Do nothing if file not found.
open_file: ; Function returns file handle in D0.
move.w #0, -(a7) ; Open as read only.
pea path(pc) ; Push address of path.
move.w #$3D, -(a7) ; See Internals page 127.
trap #1
addq.l #8, a7
move.w d0, d6 ; Store file handle in D6.
read_file:
pea buffer(pc) ; Push buffer address
lea dta(pc), a0
move.l $1A(a0), filesize ; Store file size in handler variable.
move.l $1A(a0), -(sp) ; Number of bytes to read.
move.w d6, -(sp) ; File's handle number.
; NOTE:
; While I use the terminology found in the references when I say that
; D6 contains the file's handle, the truth is that we are not dealing with
; "just" a file; we are dealing with a path, therefore, D6 actually contains
; a path handle, not a file handle.
move.w #$3F, -(sp) ; GEMDOS function = read.
trap #1
lea $C(sp), sp ; Reposition stack pointer.
close_file: ; Actually, close the path.
move.w d6, -(sp) ; Push file (path) handle.
move.w #$3E, -(sp) ; See Internals page 128.
trap #1
addq #4, sp
initiate_interrupt_process: ; Execute in supervisor mode.
pea send_null(pc)
move.w #$26, -(sp)
trap #14
addq.l #6, sp
bra wait_for_message ; Execute the evnt_mesag function.
send_null: ; The printer will drop the logic level of
moveq.l #0, d0 ; pin 11 from high to low after receiving this
; character, even though nothing will be
; printed.
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B (register 17) of PSG.
move.b d0, 2(a1) ; Write character to PSG register 17.
move.b #$E, (a1) ; Select port A (register 16) of PSG.
move.b (a1), d0 ; Get current register 16 value.
andi.b #$DF, d0 ; Bit 5 to zero.
move.b d0, 2(a1) ; Bit 5 of port A to zero.
ori.b #$20, d0 ; Bit 5 to one.
move.b d0, 2(a1) ; Bit 5 of port A to one.
rts
interrupt_handler: ; Parallel interface interrupt handler.
movem.l d0/a0-a1, -(sp) ; Save content of registers used by handler.
move.l filesize, d0 ; Fetch file size.
beq.s clear_in_service_bit ; Exit if buffer is empty (filesize = 0).
lea io_buffer, a0 ; Fetch address of io_buffer pointer.
movea.l 4(a0), a1 ; Fetch content of extract pointer.
subq.l #1, d0 ; Decrement filesize.
move.l d0, 8(a0) ; Store new filesize value.
bne.s print_character ; Stop printing if filesize is 0.
move.l (a0), 4(a0) ; Put buffer start address in extract pointer.
bra.s clear_in_service_bit
print_character:
move.b (a1)+, d0 ; Fetch character. Increment extract pointer.
move.l a1, 4(a0) ; Store location of next character.
lea $FF8800, a1 ; Address of Programmable Sound Generator.
move.b #$F, (a1) ; Select port B (register 17) of PSG.
move.b d0, 2(a1) ; Write character to PSG register 17.
move.b #$E, (a1) ; Select port A (register 16) of PSG.
move.b (a1), d0 ; Get current register 16 value.
strobe_low:
andi.b #$DF, d0 ; Bit 5 to zero.
move.b d0, 2(a1) ; Bit 5 of port A to zero.
strobe_high:
ori.b #$20, d0 ; Bit 5 to one.
move.b d0, 2(a1) ; Bit 5 of port A to one.
clear_in_service_bit:
movem.l (sp)+, d0/a0-a1 ; Restore registers used by handler.
bclr #0, $FFFA11 ; Clear MFP in service bit.
rte
data
menu_text: dc.b ' PRINT ASCII ',0
alert_text: dc.b '[3][ | | | Print buffer is busy.][OK|CANCEL]',0
; In the above declaration, each blank space followed by a vertical bar
; inserts a blank line in the alert box. I have used this method of
; formatting the line of text so that it is where I want it in the box.
align
io_buffer: dc.l buffer ; Pointer to buffer's starting address.
extract_pointer: dc.l buffer ; Buffer character output pointer.
filesize: dc.l 0 ; filesize = 0 when buffer is empty.
aes_pb: dc.l control,global,int_in,int_out,addr_in,addr_out
bss
;
; AES ARRAYS: The addresses of the arrays declared below will be stored in
; the pointer array 'aes_pb'. That happens because the program
; is assembled in Relocatable mode. Note that minimum space
; has been reserved for the int_in, int_out, addr_in and
; addr_out structures. It is the fsel_input function which
; requires the 2 words for int_out and the 2 longwords for
; addr_in.
;
control: ds.w 5 ; Control parameters.
global: ds.w 15 ; Global parameters.
int_in: ds.w 1 ; Input parameters.
int_out: ds.w 2 ; Output parameters.
addr_in: ds.l 2 ; Input addresses.
addr_out: ds.l 1 ; Output addresses.
;
; FSEL_INPUT ARRAYS
;
path: ds.b 120 ; Array for drive name and subdirectories.
file: ds.b 14 ; Array for selected file.
;
; APPLICATION VARIABLES
;
message: ds.l 4 ; 16 byte array.
dta: ds.l 11 ; 44 byte array.
ds.l 300 ; Program stack.
stack: ds.l 0 ; Address of program stack.
buffer: ds.l $2000 ; Linear print buffer.
end
Other AES Functions
Of the 11 AES libraries discussed on pages 8 and 9 of
COMPUTE!'s AES book, I have briefly explored the Application
Library, the Event Library, the Menu Library, the Form Library
and the File Selector Library. I am going to delay discussions
concerning the other libraries until I have introduced VDI
Initialization Algorithms and Resource Construction. Once again,
I want to mention that many of these other functions are
discussion in magazines, such as ST Log and Atari Explorer.
Also, these subjects are discussed in COMPUTE!'s AES book. If
the xdef and xref assembler directives seen in programs designed
for assemblers other than AssemPro seem foreign to you, then you
should consult those subjects in references such as the Ford and
Topp or Stan Kelly-Bootle books.
Conclusion
I have kept this chapter short and uncomplicated; it
contains enough information about the AES to permit further
explorations using the COMPUTE! AES book if you are impatient,
but it is not cluttered with so much information that it bogs you
down. In addition, I have introduced some of the more
interesting TOS functions. I suggest that you explore these
further using the COMPUTE! TOS book. Sheldon Leemon has written
three excellent books for the ST; each seems better than the
last; and I hope he continues his superior work. His references
are valuable resources for both C language and assembly language
programmers.
I will continue the discussion of desk accessories in the
next chapter, but it will be from a shifted viewpoint. Enough
material has been covered to permit excursions into the subject
of alterations to software packages produced by others, which you
purchase or obtain via public domain, but which do not perform
entirely to your personal satisfaction.