Machine Code Made Easy - Part 26
Author
Description
Bonjour, mes amis, and welcome to the third part of the Driver
special!! (This month you might spot some unnecessary!!!
exclamation! mar!ks lying around. For some strange reason I
can't stop myself! Aaarghhh!!!!!)
Anyway, on with the show: Last time round I looked at
application vectors - today it's the turn of the jump table.
Essentially, vectors transfer control from Driver to the
application and the jump table transfers control from the
application back to Driver. Simple, yeah? Oh shut your face.
There are about 50 entries in version 1.0, at three byte
intervals:
0. JINTERRUPT: Call every frame interrupt.
1. JNMI: Call to reset colours and break back to basic. Also
useful for breakpoints when debugging code.
2. JMENU.INIT: Call to initialise menu list with HL = address of
list. Remember that all addresses must have D15 set to seperate
them from the Driver code, although they get accessed in lower
memory. See last month for more information on this.
The menu list data consists of the addresses (D15 set) of the
menus in order, using 0 to end the list.
9. JVECTOR.INIT: Enter with HL = address of vector list. The
list consists of the addresses for the vectors in turn (see last
month), with 0 indicating that the vector is not used.
10. JAPPL.INIT: Application initialisation. Enter with HL =
application name, DE pointing to the page table and BC =
variable table. The first is obvious (end the name with FFh) and
gets used for the desktop window. The others are explained later
on.
11. JGRAPHIC.INIT: Graphics initialisation. Includes the
character set being copied from the Driver data page, the arrow
pointer being turned on and the screen being printed.
12. JPRINT.SCREEN: Call to print all the windows in turn,
starting with the application desktop. The pointer and cursor
are turned off beforehand and then restored afterwards - sleeper
mode is also turned off.
13. JPR.PARTSCREEN: Call to print all the windows in turn,
starting with the "A"th one displayed. For example, entry with A
= 0 has the same effect as JPRINT.SCREEN, A = 1 will start with
the first one after the desktop and so on.
Pointer and cursor dealt with as above.
24: JPRINT.WINDOW: Prints window A on the screen. Make sure the
pointer and cursor are turned off.
27: JPRINT.SCROLLS: Reprints the scroll bars and move gadget for
window A. Useful when something in a window changes and they
need to be adjusted. (Of course, each of the three gadgets is
only printed is the window has it.)
30: JOPEN.WINDOW: Open window number A at coords DE, with size
BC. H holds the type and IX points to its name. (The name ends
with FFh and is centred when the window is printed. Even if the
window has no name gadget you must assign something!)
Alternatively, enter with A = 0 and a window number will be
allocated and returned in A. CY is set if there are no windows
available.
The screen display is not changed.
33. JCLOSE.WINDOW: Enter with A = window number to close,
including 0 to close the applications. The relevant vector
(Close window or close application) will be called by the
routine. Screen not updated.
34. JWINDOW.ICON: Prints an icon in window A, trimming it at the
right and bottom to fit in. Enter with HL = address of data, DE
= relative coords within window, BC = real size, B'C' = size to
use after trimming top/left. (Normally the same as BC). Just to
confuse you, if HL is >=8000h the Driver data page is used,
otherwise the application is used.
If the icon is to be trimmed top/left, DE would be 0 and B'C'
would hold the size AFTER trimming. Any further trimming at
right/bottom is dealt with.
42. JWINDOW.TEXT: Print some text in window A. DE holds the
relative coords, and IX holds the address of the text. As above,
trimming is done automatically at right/bottom, but you must
enter with C = number of characters to skip (normally 0). To
trim at the left, therefore, enter with E = 0 and C = no
characters to skip over.
Also enter with A' = paper colour (0-3), B = pen colour.
45. JPOINTER.ON: Enter with A = page, HL = address of pointer
data. If A = H = 0, the existing pointer is used.
48. JPOINTER.OFF: Pointer is removed from screen and turned off.
The vector JSLEEPER.ON is also called.
51. JCURSOR.ON: Cursor turned on at absolute coords DE. If the
cursor is already on, it is removed from the screen and
repositioned. However, the mode is not changed (this might mean
calling JSLEEPER.OFF to change back to WIMP mode.)
54. JCURSOR.OFF: Cursor removed from screen and turned off.
57. Reserved.
60. Reserved.
63. JSET.MODE: Set Driver mode A.
66. JSLEEPER.ON: Stores current mode and sets mode 4 (sleeper),
de-activating all gadgets.
69. JSLEEPER.OFF: Resets previous mode stored by JSLEEPER.ON.
72. JFPAGES: Returns number of free 16k pages in A. If more than
255 are free, 255 is returned.
75. JRESERVE.PAGE: Allocate and reserve page for application.
Entries are made in application page table (see later), and the
number of pages reserved is increased. If no page is available,
the routine returns CY.
78. JRELEASE.PAGE: Releases page at top of page table and
decreases page total.
81. JPAGE: Enter with A = logical page number (0-127). Returns
physical page numbers in A and B. This is the standard
representation of a data page: A represents the HMPR value, and
B the LEPR (External page in section C) value. The routine also
returns C = lepr, so that on return your application does:
OUT (hmpr),A
OUT (C),B
84. JRUNCLI: Enter with HL = address of BASIC line data in
application. The line is run through the ROM and returns with A
= error number or 0 with CY set for an error.
87. JBASIC: Return to BASIC.
90. JDESKTOP: Return to Driver Desktop.
93. Reserved.
96. JGETKEY: Returns key from head of queue in A (ascii code). A
= 0 if no key was pressed.
99. JFLUSH: Flush out keyboard queue.
102. JSAVEAS: Opens the "Save as..." dialogue box. Entry is with
HL pointing to the file data, which is altered on exit to suit
the file details selected by the user. On return, A = 1 (OK) or
0 (CANCEL).
File data consists of 15 bytes:
0/1 Disk number
2 Drive
3 Sub directory number
4 File type (not relevant to "Save as")
5-14 File name, including trailing spaces.
105. JOPENAS: Opens the "Open a file" dialogue box. Entry and
exit are as for JSAVEAS. (This is where the file type becomes
important).
For opening a file, I would suggest using a default file name
with a wildcard, like "*.icn" which would list only those files.
This is what Icon Master does.
108. JSCROLLUP: Scroll window A up either a bit (C=0) or a lot
(C=1). These correspond to clicking either the up arrow or the
space above the scroll bar indicator. The Scroll Up vector is
called, and the window(s) redrawn.
111. JSCROLLDOWN: As above, but scrolls down.
114. JSCROLLLEFT: As above.
117. JSCROLLRIGHT: As above.
120. JCUT: Cut BC bytes from HL onto the clipboard. BC must
range from 1-256 and HL must be in the application page. CY is
returned if the clipboard is full. (It can grow as long as
memory allows)
123. JPASTE: Paste BC bytes from the clipboard to HL in the
application page. BC must be from 1-256. CY is returned if there
is no data left, with BC holding the number of bytes left
unpasted.
126. JEMPTYCLIP: Empty clipboard, releasing all its pages, and
reset the JCUT pointer to the start.
129. JRESETPASTE: Reset JPASTE pointer to the start of the
clipboard.
132. JKEYCONTROL: Enter with A = 1 if the keyboard is to be
shared between keyboard control of the pointer (if the user has
no mouse) and something else, like entering text. Otherwise A =
0. The user toggles between the two modes using [SYMBOL]-[EDIT].
135. JSELECT.SAW: Select window number A. The affected windows
are redrawn on the screen.
138. JBLOCKS: Use HL as the base address for the window gadget
blocks. (See Set Variables)
141. JCOPYCHARS: Copy character set from the Driver Data page to
a space above the screen. Driver keeps a copy there to speed up
printing text (the pointer is also stored above the screen), but
some DOS routines might corrupt it.
144. JFREE.PAGE: Returns the top free page available in AB, or
CY if none free. No entries are made in any table.
147. JALLOCATE: Allocate page AB in ROM/DOS tables, using value
in C. If C is zero, the page is freed. The application page
table is not affected.
HOW TO USE THE JUMP TABLE
Using the jump table is simple. Just page Driver into section C
and CALL the relevant entry. The numbers given above are offsets
- the table is at 8400h in the Driver page, although I'll
probably change this at the last minute. Anyway, one of the the
set variables (see the section a few pages on) contains the jump
table base addess. Note, though, that interrupts are disabled on
exit.
The first couple of entries deserve examples. You should be
running interrupts in mode 1, and your handler is called at
0038h:
maskable.int PUSH AF
IN A,(status)
RRA
JP NC,line.int
RRA
RRA
RRA
JP NC,frame.int
maskint.end POP AF
EI
RET
.
.
frame.int IN A,(hmpr)
PUSH AF
LD A,(driver.page)
OUT (hmpr),A
CALL jint
POP AF
OUT (hmpr),A
POP AF
EI
RET
The non-maskable interrupt is called at 0066h:
nmi LD SP,stack
CALL driver_in
JP jnmi
This should make it clear that you can run additional routines
off the frame interrupt, or a line interrupt. It is possible to
run such an interrupt about 20 lines into the screen, and change
both the mode and palette. You might want to change the graphics
for both the window gadget blocks and the pointer to make them
compatible with mode 4. Modes 1 and 2 should be avoided, though.
Uses? Art applications, of course.
MEMORY MANAGEMENT
As I mentioned last month, Driver applications run in lower
memory, paging data, graphics, the screen and Driver itself into
sections C and D. This is to let you use external RAM which can
only be paged there.
The external RAM is paged using HMPR and another two ports -
LEPR (128 dec) which controls section C, and HEPR (129 dec) for
section D. HMPR has D7 set to access the extra memory. So, we
can represent any page with two numbers - the HMPR value (0-31
for internal memory, 128 for external) in A and the LEPR value
(0-255) in B.
All the extra pages used by your application are held in a table
somewhere inside it (PAGETAB). This is 257 bytes long: the first
byte represents the number of data pages (0-128), and then the
pages are listed using the above format (HMPR for page 0, LEPR
for page 0; HMPR for page 1, LEPR for page 1 etc..) JPAGE will
return the two values for a logical page number 0-127, although
I found it easier to do it myself.
You tell Driver the location of the table via JAPPL.INIT (see
above). JRESERVE.PAGE will find a free page (using external
memory if possible) and add it into PAGETAB, updating the number
of pages. JRELEASE.PAGE does the opposite, freeing the top page.
The problem with this (and about the only programming problem
for applications), is that the application data has to be
treated in 16k blocks, and the pages won't be concurrent. Not
very easy to manipulate, especially for things like word
processed documents where you need to keep shuffling memory
about. I'll tell you how I got round this drawback with Notepad
next month. (Incidentally, you can write documents on Notepad up
to 2 MEG BIG!!! - A nice result after having to cope with such a
nasty problem!)
There are advantages, though: you don't have to worry about how
much memory the machine has, and more than one application can
reside and run at the same time. Then the user can cut and paste
bits of data between documents without having to load and save.
(More on clipboarding next month.)
The one further drawback with the paging is loading and saving.
Apart from having to do so using machine code, you can't just
use the DOS's usual load and save routines. Instead, you read
and write blocks of data at a time. Again, I'll detail this more
next month.
One final point: If your application is only going to use 2 data
pages (for example, in an art program), you could alway use code
like this to page them into either section C or D. Enter with HL
= address (8000h-BFFFh for page 0, C000h-FFFFh for page 1):
data_in PUSH AF
PUSH BC
PUSH HL
LD HL,pagetab+1
LD C,(HL) ; Page 0 hmpr
INC HL
LD A,(HL) ; Page 0 lepr
OUT (lepr),A
INC HL
LD B,(HL) ; Page 1 hmpr
INC HL
LD A,(HL)
OUT (hepr),A ; Page 1 hepr
POP HL
LD A,B ; Use page 0
BIT 6,H
JR Z,di1
LD A,C ; Use page 1
DEC A ; Take 1 off for hmpr
XOR C
AND 31 ; Merge D7, in case of XMEM
XOR C
di1 OUT (hmpr),A ; Page it
POP BC
POP AF
RET
You could modify this to use four pages, with all the memory
addressed by a single 16-bit word. If the application needs to
use more than that, you'll find yourself having to address the
memory using 3 bytes. Nasty!
** IMPORTANT **
I must stress, once again, that you have to SET BIT 15 on any
address that you give Driver to do with your application. The
ONLY exception to this is when dealing with icons. Despite this,
your application always runs in lower memory.
DRIVER VARIABLES
To keep your application notified, certain Driver variables are
copied into a table during every Driver Interrupt. You define
the table with JAPPL.INIT:
0. Pointer x coordinate (absolute 0-255)
1. Pointer y coordinate (0-191, 0 is at the top)
2. Mode (0-4)
3. Shift flag. 1 if [SHIFT] is being held, else 0.
4. Mouse button (D0 set when left held, D1 = 0, D2 set when
right held.)
5. Pointer flag.
6. Cursor flag.
7. Cursor x coordinate (as for pointer).
8. Cursor y coordinate (as for pointer).
9. Number of windows open, excluding desktop. (0-255)
10. Active SAW number. (1-255, or 0 if none selected)
11. Number of pages reserved by clipboard (0 if none).
After these twelve bytes, leave space for another 10 or so, to
ensure compatibility with future versions.
SET ADDRESSES
Inside your application, there are several addresses that must
be set aside for specific purposes.
0000 Jumped to when the application is opened for the second
time (or later)
0003 Jumped to when the application is opened for the first
time only.
0006 Called when Driver is being closed. You must return the
address of PAGETAB (with D15 set, of course) in DE.
0009 Reserved for a future "Kickstart" program.
00F0 Name of the application (15 characters max), ending with
FFh. This is centred by the Driver Desktop under the
application's icon.
0100 Icon. 16x24 fat pixels in the usual format (192 bytes
long). Used by both the Driver Desktop and File Manager.
In the first four jumps, the stack is in upper memory, so you
need to provide your own. After this, Driver keeps track of the
application stack so that every time a vector transfers control
from Driver to the application the stack is correct.
One "problem" is the "Driver Closing" jump. Only the currently
open application's stack and page table are monitored, so when
Driver is closed (and all the applications with it), the stack
is in section C and you must return the PAGETAB in DE. You would
probably also want to run a dialogue box to save any changes to
a file, and your own stack would be needed:
0006 JP driver_closing
.
.
.
driver_closing POP HL ; Return address
LD SP,stack
PUSH HL
CALL close_resave
CALL driver_in
LD DE,pagetab+8000h
RET
As you can see, Driver will revert to its own stack once you
return. "driver_in" simply pages Driver into upper memory. An
application vector, "close application", is called when the
application itself is closed and the fiddling with the stack
isn't necessay there.
Incidentally, Driver's page is stored in System Variable 5C97h,
and the Driver Data page is at 5C98h. You might like to copy
these at the start.
(The idea of the Kickstart thing is that you can put
applications in a folder and they will be installed when Driver
is loaded.)
FIXED VARIABLES
As I mentioned earlier, at the start of the Driver code there
are variables at fixed addresses:
0010 PRTOKV.STORE
0012 CMDV.STORE
0014 MTOKV.STORE Stores for ROM vectors to do with the
keyword.
0016 KEYWORD.FLAG 0 if keyword installed, else 1.
0017 DVAR.OFFSET Offset of DVAR 0 within the DOS.
0019 Version number of Driver, times 10.
001A Settings Offset of preference settings within Driver
code.
001C ATAB.ADDRESS Offset of application table within Driver
code.
001E EDITOR.FLAG Normally FFh. Use 1 when writing an
application.
001F BLOCKS Address of window gadget blocks. To
customise the blocks (for example, when using a different screen
mode) change this with D15 set.
0021 Reserved
0023 Clock vector See below.
0026 MTask vector See below.
0029 SEL.APPL Address of selected application within
application table.
002B MAX.APPLS Maximum number of applications (normally
12).
002C JTAB Jump table address.
MULTI-TASKING
Now, although when I discussed multi-tasking a few months back I
mentioned that I'd discounted the idea for Driver, I had a
slight change of heart. Although applications are mutually
independant when it comes to windows and menus, you can have
them running "invisibly" in the background.
This is acheived using the MTask vector mentioned above - this
isn't a normal application vector at all. Instead, you specify a
page and an address, and after the Driver interrupt has done
graphical stuff the vector is called in UPPER MEMORY. And, like
the "Driver closing" vector, the stack is left in Driver.
To let more than one application use the vector, another vector
is stored three bytes after the vectored address, and is run
after the first one. Three bytes after this, the third vector is
stored and then run.
Confused? Good. In practice, a "chain" of vectors looks
something like this:
Driver page, 0026h 18h
0027h 8010h ; Page and address of first
vector.
Page 18h, 8010h JP mtask
8013h 16h
8014h 8123h ; Page and address of second vector.
Page 16h, 8123h JP mtask
8126h 15h
8127h 8200h ; Page and address of third vector.
Page 15h, 8200h JP mtask
8203 00h
8204 0000h ; No more vectors.
Of course, is there aren't any vectors, MTask vector will be 0,0,0.
To set a vector, the following routine (run in lower memory),
will be useful:
Entry: IX = vector address with D15 set (eg. 8026h for Mtask
vector)
HL = address of routine with D15 set. (Eg. 8010h)
set.vector CALL driver_in
PUSH HL ; Copy HL into IY
POP IY
LD A,(IX+0) ; Store the existing vector
LD (IY+0),A
LD A,(IX+1)
LD (IY+1),A
LD A,(IX+2)
LD (IY+2),A
IN A,(lmpr) ; Store the new vector
AND 31
LD (IX+0),A
LD (IX+1),L
LD (IX+2),H
RET
And, at 0010h:
0010h JP mtask
0013h DB 0,0,0
When you want to disconnect the vector, we have to search
through the chain to find it. Then we take the vector from 0013h
(or wherever you've put it) and copy it over. Essentially, we've
taken the link out of the chain and connected the two loose ends
together.
Entry as before.
remove.vector CALL driver_in ; Driver into upper memory.
rv1 IN A,(lmpr)
AND 31
CP (IX+0) ; Check the vector.
JR Z,rv.found
LD E,(IX+1) ; Get the address of the wrong
one.
LD D,(IX+2)
INC DE
INC DE
INC DE
OUT (hmpr),A ; Page it in.
PUSH DE
POP IX ; Copy it into IX.
JR rv1 ; Loop until we find it.
rv.found INC HL
INC HL
INC HL
LD A,(HL) ; Take the link out of the
chain.
LD (IX+0),A
INC HL
LD A,(HL)
LD (IX+1),A
INC HL
LD A,(HL)
LD (IX+2),A
RET
You might notice that in both routines, the entry conditions
include the vector address. This lets the routines also work on
ANOTHER vector (gee, I'm too good to you guys).
I mentioned that the MTask vectors are called after the Driver
Interrupt has done its graphical stuff. This "graphical stuff"
has to done at the start of the interrupt, during the frame
flyback to avoid shear. Essentially, the routine looks like
this:
.
.
Remove pointer from screen
Remove cursor from screen
Call clock vector
Call application graphic vector
Add cursor
Add pointer
.
.
The application graphic vector (number 17), which I omitted from
last month, is called in WIMP and sleeper modes only, with no
entry or return conditions. The clock vector is similar to the
MTask vector and can be found at 0023h.
The reasons that I seperated the two are obvious: Firstly, if
the screen display is going to be changed, the pointer and
cursor must be removed first. Secondly, we want to minimise the
time taken by the graphical vectors, or the pointer (and cursor)
will be added back on after the screen scan has passed them,
making them partly or completely invisible!
I called it the Clock vector because, originally, this was the
only use I saw for it; running a clock at the top left of the
screen. In fact, such clock application will (hopefully!) be
released in one form or another shortly after Driver comes out.
To minimise the time the vector takes, I would suggest doing as
much processing as possible in an accompanying MTask routine.
The aforementioned clock application uses the MTask vector to
"print" the time in a buffer space, which is copied to the
screen by the next clock vector. Of course, dumping one
rectangular graphic is faster than printing 5 smaller ones.
Right, that's yer lot until next month. Cheerio!