Section Contents
The memory which an OPL program may use is unlimited up to the constraint placed by the machine itself. This means that addresses must be represented by 32-bit integers, the widest numeric type available in OPL.
OPL includes powerful facilities to handle input and output (I/O). These functions and commands can be used to access all types of files in the filing system, as well as various other parts of the low-level software.
This section describes how to open, close, read and write to files, and how to set the position in a file. The data file handling commands and functions have been designed for use specifically with data files. The I/O functions and commands are designed for general file access. You dont need to use them to handle data files.
These are powerful functions and commands and they must be used with care. Before using them you must read this section closely and have a good grounding in OPL in general.
You should have a good understanding of error handling (see Error Handling) before using the I/O functions.
The functions in this section never raise an OPL error message. Instead they return a value - if this is less than zero an error has occurred. It is the responsibility of the programmer to check all return values, and handle errors appropriately. Any error number returned will be one of those in the list given in OPL error values. You can use ERR$ to display the error as usual.
Many of these functions use a handle, which must be a long integer variable. IOOPEN assigns this handle variable a value, which subsequent I/O functions use to access that particular file. Each file you IOOPEN needs a different handle variable.
In this section, var denotes an argument which should normally be a LOCAL or GLOBAL variable. (Single elements of arrays may also be used, but not field variables or procedure parameters.) Where you see var the address of the variable is passed, not the value in it. (This happens automatically; dont use ADDR yourself.)
In many cases the function you are calling passes information back by setting these var variables.
var is just to show you where you must use a suitable variable; you dont actually type var.
For example: ret%=IOOPEN(var handle&,name$,mode%) in the syntax description indicates that IOOPEN(h&,"abc",0) is correct (provided h& is a simple variable), but IOOPEN(100,"abc",0) is incorrect.
It is possible, though, that you already have the address of the variable to use. It might be that this address is held in a field variable, or is even a constant value, but the most common situation is when the address was passed as a parameter to the current procedure.
If you add a # prefix to a var argument, this tells OPL that the expression following is the address to be used, not a variable whose address is to be taken.
Here is an example program:
PROC doopen:(phandle&, name$, mode%) LOCAL error% REM IOOPEN, handling errors error% = IOOPEN(#phandle&, name$, mode%) IF error% : RAISE error% : ENDIF ENDP
The current value held in phandle& is passed to IOOPEN. You might call doopen: like this:
LOCAL filhand%,... ... doopen:(ADDR(filhand%),"log.txt",$23) ...
The doopen: procedure calls IOOPEN with the address of filhand%, and IOOPEN will write the handle into filhand%.
Section Contents
ret%=IOOPEN(var handle%,name$,mode%
or
ret%=IOOPEN(var handle%,address&,mode%)
for unique file creation.
Creates or opens a file (or device) called name$ and sets handle% to the handle to be used by the other I/O functions.
mode% specifies how the file is to be opened. It is formed by ORing together values which fall into the three following categories:
One and only one of the following values must be chosen from this category.
$0000 |
Open an existing file (or device). The initial current position is set to the start of the file. |
$0001 |
Create a file which must not already exist. |
$0002 |
Replace a file (truncate it to zero length) or create it if it does not exist. |
$0003 |
Open an existing file for appending. The initial current position is set to the end of the file. For text format files (see $0020 below) this is the only way to position to end of file. |
$0004 |
Creates a file with a unique name. For this case, you must use the address of a string instead of name$. This string specifies only the path of the file to be created (any file name in the string is ignored). The string at address% is then set by IOOPEN to the unique file name generated (this will include the full path). The string must be large enough to take 130 characters (the maximum length file specification). For example: s$="C:\home\" IOOPEN(handle%,ADDR(s$),mode%) This mode is typically used for temporary files which will later be deleted or renamed. |
One and only one of the following values must be chosen from this category. When creating a file, this value specifies the format of the new file. When opening an existing file, make sure you use the format with which it was created.
$0000 |
The file is treated as a byte stream of binary data with no restriction on the value of any byte and no structure imposed upon the data. Up to 16K can be read from or written to the file in a single operation. |
$0020 |
The file is treated as a sequence of variable length records. The records are assumed to contain text terminated by any combination of the CR and LF ($0D, $0A) characters. The maximum record length is 256 bytes and Control-Z ($1A) marks the end of the file. |
These values are declared as constants in Const.oph
. See Including header files.
Any combination of the following values may be chosen from this category.
$0100 |
Update flag. Allows the file to be written to as well as read. If not set, the file is opened for reading only. You must use this flag when creating or replacing a file. |
$0200 |
Choose this value if you want the file to be open for random access (not sequential access), using the IOSEEK function. |
$0400 |
Specifies that the file is being opened for sharing for example, with other running programs. Use if you want to read, not write to the file. If the file is opened for writing ($0100 above), this flag is ignored, since sharing is then not feasible. If not specified, the file is locked and may only be used by this running program. |
Files should be closed when no longer being accessed. This releases memory and other resources back to the system.
ret%=IOCLOSE(handle%)
Closes a file (or device) with the handle handle% as set by IOOPEN.
Section Contents
ret%=IOREAD(handle%,address&,maxLen%)
Reads up to maxLen% bytes from a file with the handle handle% as set by IOOPEN. address& is the address of a buffer into which the data is read. This buffer must be large enough to hold a maximum of maxLen% bytes. The buffer could be an array or even a single integer as required. No more than 16K bytes can be read at a time.
The value returned to ret% is the actual number of bytes read or, if negative, is an error value.
If maxLen% exceeds the current record length, data only up to the end of the record is read into the buffer. No error is returned and the file position is set to the next record.
If a record is longer than maxLen%, the error value Record too large (-43) is returned. In this case the data read is valid but is truncated to length maxLen%, and the file position is set to the next record.
A string array buffer$(255) could be used, but make sure that you pass the address ADDR(buffer$)+1 to IOREAD. This leaves the leading byte free. You can then POKEB the leading byte with the count (returned to ret%) so that the string conforms to normal string format. See the example program.
If you request more bytes than are left in the file, the number of bytes actually read (even zero) will be less than the number requested. So if ret%<maxLen%, end of file has been reached. No error is returned by IOREAD in this case, but the next IOREAD would return the error value End of file (-36).
To read up to 16K bytes (8192 integers), you could declare an integer array buffer%(8192).
ret%=IOWRITE(handle%,address&,length%)
Writes length% bytes stored in a buffer at address& to a file with the handle handle%.
When a file is opened as a binary file, the data written by IOWRITE overwrites data at the current position.
When a file is opened as a text file, IOWRITE writes a single record; the closing CR/LF is automatically added.
ret%=IOSEEK(handle%,mode%,var offset&)
Seeks to a position in a file that has been opened for random access (see IOOPEN above).
mode% specifies how the argument offset& is to be used. offset& may be positive to move forwards or negative to move backwards. The values you can use for mode% are:
1 |
Set position in a binary file to the absolute value specified in offset&, with 0 for the first byte in the file. |
2 |
Set position in a binary file to offset& bytes from the end of the file |
3 |
Set position in a binary file to offset& bytes relative to the current position. |
6 |
Rewind a text file to the first record. offset& is not used, but you must still pass it as a argument, for compatibility with the other cases. |
IOSEEK sets the variable offset& to the absolute position set.
This program opens a plain text file such as one created with the Export as text option in the File menu of the Program editor and types it to the screen. Press Esc to quit and any other key to pause the typing to the screen.
PROC ioType: LOCAL ret%,fName$(128),txt$(255),address& LOCAL handle%,mode%,k% PRINT "Filename?", :INPUT fName$ : CLS mode%=$0400 OR $0020 REM open=$0000,text=$0020,share=$0400 ret%=IOOPEN(handle%,fName$,mode%) IF ret%<0 showErr:(ret%) RETURN ENDIF address&=ADDR(txt$) WHILE 1 k%=KEY IF k% REM if keypress IF k%=27 REM Esc pressed RETURN REM otherwise wait for a key ELSEIF GET=27 RETURN REM Esc pressed ENDIF ENDIF ret%=IOREAD(handle%,address&+1,255) IF ret%<0 IF ret%<>-36 REM NOT EOF showErr:(ret%) ENDIF BREAK ELSE POKEB address&,ret% REM leading byte count PRINT txt$ ENDIF ENDWH ret%=IOCLOSE(handle%) IF ret% showErr:(ret%) ENDIF PAUSE -100 :KEY ENDP PROC showErr:(val%) PRINT "Error",val%,err$(val%) GET ENDP
Section Contents
This section provides the general background necessary for understanding how EPOC I/O devices can be accessed by an OPL application program.
Many operating system services are implemented in two steps:
In most cases, as well as providing functions for each step, the system provides a function containing both the above steps. Such functions are called synchronous because they automatically synchronise the requesting process by waiting until the operation has completed. The internal function that makes the request without waiting for completion is called an asynchronous function.
Examples of asynchronous request functions are:
IOC (or IOA) |
for requests on an open I/O channel |
KEYA |
for requests on the keyboard channel |
GETEVENTA32 |
for requests of events from the window server |
PLAYSOUNDA: |
(in |
The synchronous versions of the above functions are IOW , GET, GETEVENT32 and PLAYSOUND: respectively.
Applications use asynchronous requests in situations like the following:
Processes wait for the completion of asynchronous requests by waiting on their I/O semaphore where each request is associated with a status word.
When a process is created, the system automatically creates an I/O semaphore on its behalf (a more accurately descriptive name would have been the asynchronous request semaphore). After making one or more asynchronous requests using IOC or IOA, a process calls IOWAIT to wait on the I/O semaphore for one of the requests to complete. A typical application spends most of its time waiting on its I/O semaphore. For example, an interactive application process that is waiting for user input is waiting on the I/O semaphore.
Semaphores are provided to synchronise cooperating processes (where, in this context, a process includes a hardware interrupt). In OPL semaphores are used for synchronising the completion of asynchronous requests.
The semaphores in EPOC are counting semaphores, having a signed value that is incremented by calling IOSIGNAL and decremented by calling IOWAIT. A semaphore with a negative value implies that a process must wait for the completion of an event.
The process or the hardware interrupt handler that implements the requested operation sends a signal to the process (using a generalised version of IOSIGNAL directed to the required process) to indicate that the operation has completed. If one or more wait handlers have been installed (wait handlers are described below), they may process the signal and re-signal using IOSIGNAL. In some cases, it is convenient for the requester to use IOSIGNAL to signal itself and subsequently to process that signal in a central call to IOWAIT.
Although the arguments to asynchronous request functions vary, they all take a so-called status word argument, which subsequently contains the status of the requested operation. The status word is a 16-bit integer except when calling an asynchronous OPX procedure that specifically requires a 32-bit integer status word, such as PLAYSOUNDA: in System.opx
.
All asynchronous requests exhibit the following behaviour:
A 32-bit status word however contains &80000001. Const.oph provides constant definitions for these values.
For 32-bit status words an EPOC error value is used. The EPOC error values are listed in EPOC Error Values.
Making a request while a previous request on the same status word is still pending will normally result in a system panic where the program is killed immediately, without being able to trap the error.
When there are multiple requests, each request is associated with a different status word. After returning from IOWAIT, the caller typically polls each status word until one is found that contains other than -46. That completion is then processed (which might include renewing the asynchronous request) and IOWAIT is called again to process the next completion.
Most asynchronous request functions made using IOC (or IOA) can be cancelled. To cancel such requests use the IOCANCEL function.
For asynchronous requests made using a mechanism other than IOC (or IOA), a specific cancelling function must be used instead:
The following general principles apply to all functions that cancel an asynchronous request:
A 16-bit status word is set to -48 when a request has been effective.
A 32-bit status word is set to -3 (EPOCs error code for Cancel error) when a request using a 32-bit status word is cancelled.
When waiting for the completion of a particular asynchronous request, the wait on the I/O semaphore must be sure that it is not fooled into a premature return by the completion of any other pending asynchronous request. This is done by calling IOWAITSTAT which behaves in a similar way to IOWAIT except that it only returns when the associated status word is other than -46.
For a 32-bit status word request, IOWAITSTAT32 is used instead, again behaving in a similar way to IOWAIT except that it only returns when the associated 32-bit status word is other than &80000001.
In general, IOWAITSTAT (IOWAITSTAT32) is a safer option than IOWAIT to "use up" the signal resulting from the cancelled operation. If the cancel is not immediately effective and another completion causes IOWAIT to return, the program could continue and make another request before the cancelled operation completes (which would result in a process panic).
In the following example, the opened asynchronous timer timChan% is used to construct a synchronous function which attempts to write the passed string to the opened serial channel serChan%. If it takes more that 5 seconds to complete the write, the procedure raises the inactivity error -54. For simplicity it is assumed that there are no other outstanding events which could complete and that both requests started successfully. Thus it is certain that on return from the call to IOWAIT, one of the two asynchronous requests has completed.
PROC strToSer:(inStr$) LOCAL str$(255) REM local copy of inStr$ needed for ADDR LOCAL len%,timStat%,serStat%,timeout%,ret%,pStr& str$=inStr$ pStr&=ADDR(str$)+1 REM pointer to string skipping leading count byte len%=LEN(str$) IOC(serChan%,2,serStat%,#pStr&,len%) REM request asynchronous serial write timeout%=50 REM 5 second timeout IOC(timChan%,1,timStat%,timeout%) REM Relative timer - function 1 IOWAIT IF serStat%=-46 REM must have timed out IOCANCEL(serChan%) REM cancel serial request IOWAITSTAT serStat% REM use up the signal RAISE -54 REM inactivity timeout ENDIF IOCANCEL(timChan%) REM cancel timer request IOWAITSTAT timStat% REM use up the signal ENDP
In a multi-tasking operating system it is extremely anti-social to wait for an operation to complete by polling the status word in a tight loop rather than call IOWAIT (because the polling will "hog" the processor to no benefit). However, when there is useful work to be done between each poll, it can be appropriate to poll - for example, to check periodically for user input while performing an extended calculation.
When a poll detects a completed status word, it is still obligatory to "use up" the signal by calling IOWAIT (otherwise you will get a "stray signal" later).
The status word will be set only when either IOWAIT or IOYIELD is called. For a 32-bit status word, call IOWAITSTAT32 instead.
For example:
... IOC(handle%,func%,stat%,#pBuf&,len%) DO calc: REM perform part of some calculation until request complete IOYIELD REM allow status word to be set UNTIL stat%<>-46 ...
The following I/O functions provide access to devices. A full description is not within the scope of this manual, since these functions require extensive knowledge of the EPOC operating system and related programming techniques. The syntax and argument descriptions are provided here for completeness.
The previous section explains in detail the semantics of asynchronous I/O. In the descriptions of the asynchronous I/O functions below, a careful reading of that section is assumed.
ret%=IOW(handle%,func%,var arg1,var arg2)
The device driver opened with handle% (as returned by IOOPEN) performs the synchronous I/O function func% with the two further arguments. The size and structure of these two arguments is specified by the particular device drivers documentation.
IOC(handle%,func%,var stat%,var a1,var a2)
IOC(handle%,func%,var stat%,var a1)
IOC(handle%,func%,var stat%)
Make an I/O request with guaranteed completion. The device driver opened with handle% (as returned by IOOPEN) performs the asynchronous I/O function func% with up to two further arguments. The size and structure of these arguments is specified by the particular device drivers documentation.
As explained in detail in the previous section, asynchronous means that the IOC returns immediately, and the OPL program can carry on with other statements. status% will always be set to -46, which means that the function is still pending.
When, at some later time, the function completes, status% is automatically changed. (For this reason, status% should usually be global since if the program is still running, status% must be available when the request completes, or the program will probably crash). If status%>=0, the function completed without error. If status%<0, the function completed with error. The return values and error codes are specific to the device driver.
If an OPL program is ready to exit, it does not have to wait for any signals from pending IOC calls.
ret%=IOA(handle%,func%,var status%,var arg1,var arg2)
The device driver opened with handle% (as returned by IOOPEN) performs the asynchronous I/O function func% with two further arguments. The size and structure of these two arguments is specified by the particular device drivers documentation.
This has the same form as IOC, but the return value cannot be ignored. IOC is effectively the same as:
ret%=IOA(h%,f%,stat%,...) IF ret%<0 stat%=ret% IOSIGNAL ENDIF
IOC allows you to assume that the request started successfully - any error is always given in the status word stat%. If there was an error, stat% contains the error code and the IOSIGNAL causes the next IOWAIT to return immediately as if the error occurred after completion. There is seldom a requirement to know whether an error occurred on starting a function, and IOC should therefore nearly always be used in preference to IOA.
IOWAIT
Wait for an asynchronous request (such as one requested by IOC, KEYA or GETEVENTA32) to complete. IOWAIT returns when any asynchronous I/O function completes. Check status% to find out which request has completed. Use IOWAITSTAT with the relevant status word to wait for a particular request to complete.
IOSIGNAL
Replace a signal of an I/O functions completion.
As shown in A simple example using asynchronous I/O in the previous section, it is sometimes useful to construct a synchronous operation from two asynchronous operations by waiting for either to complete before returning. In that case it waited for either the serial write request or a timeout. As noted there, the example assumed that there were no other outstanding asynchronous requests. If there had been one or more asynchronous requests made before calling that procedure, such as a request to play a sound file, and if that request had completed before both the serial write and the timer request, the procedure would incorrectly assume that the timer had completed:
IOWAIT IF serStat%=-46 REM must have timed out IOCANCEL(serChan%) REM cancel serial request IOWAITSTAT serStat% REM use up the signal RAISE -54 REM inactivity timeout ENDIF IOCANCEL(timChan%) REM cancel timer request
IOWAITSTAT timStat% REM use up the signal
To deal with this situation correctly, the procedure should instead check that one of the particular two requests it knows about has completed and re-signal to put back the signal consumed for the sound file completion:
PROC strToSer:(inStr$) LOCAL str$(255) REM local copy of inStr$ needed for ADDR LOCAL len%,timStat%,serStat%,timeout%,ret%,pStr& LOCAL signals% REM count of external signals LOCAL err% str$=inStr$ pStr&=ADDR(str$)+1 REM ptr to string skipping leading count byte len%=LEN(str$) IOC(serChan%,2,serStat%,#pStr&,len%) REM request asynchronous serial write timeout%=50 REM 5 second timeout IOC(timChan%,1,timStat%,timeout%) REM relative timer - function 1 WHILE 1 REM forever loop IOWAIT REM wait for any completion IF timStat%<>-46 REM timed out IOCANCEL(serChan%) REM cancel serial request IOWAITSTAT serStat% REM use up the signal err%=-54 REM inactivity timeout (raised REM below after re-signalling) BREAK REM stop waiting and re-signal ELSEIF serStat%<>-46 REM serial write complete IOCANCEL(timChan%) REM cancel timer request IOWAITSTAT timStat% REM use up the signal BREAK REM stop waiting and re-signal ELSE REM other unknown request signals%=signal%+1 REM count other signals REM loop again for next ENDIF ENDWH WHILE signals%>0 REM now re-signal any consumed external signals IOSIGNAL signals%=signals%-1 ENDWH IF err% RAISE err% ENDIF ENDP
IOSIGNAL is called only after exiting the IOWAIT loop, otherwise the signal would cause the IOWAIT to return immediately.
IOWAITSTAT var status%
Wait for a particular asynchronous function, called with IOC, to complete.
IOWAITSTAT32 var stat&
Similar to IOWAITSTAT but takes a 32-bit status word. IOWAITSTAT32 should be called only when you need to wait for completion of a request made using a 32-bit status word when calling an asynchronous OPX procedure. status& will be &80000001 while the function is still pending, and on completion will be set to the appropriate EPOC error code, listed in Error Handling. See also About OPXs and Keyword Reference.
IOYIELD
Ensures that any asynchronous function is given a chance to run. Some devices are unable to perform an asynchronous request if an OPL program becomes computationally intensive, using no I/O (screen, keyboard etc.) at all. In such cases, the OPL program should use IOYIELD before checking its status% variable. This is always the case under EPOC. IOYIELD is the equivalent of IOSIGNAL followed by IOWAIT - the IOWAIT returns immediately with the signal from IOSIGNAL, but the IOWAIT causes any asynchronous handlers to run.
IOCANCEL(handle%)
Cancels any outstanding asynchronous I/O request made using IOC (or IOA) on the specified channel, causing them to complete with the completion status word containing -48 (I/O cancelled). The return value is always zero and may be ignored.
The IOCANCEL function is harmless if no request is outstanding (e.g. if the function completed just before cancellation requested).
GETEVENTA32(var status%,var event&())
This is an asynchronous window server read function. You must declare a long integer array with at least 16 elements. If a window server event occurs, the information is returned in event&() as described under GETEVENT32 in the alphabetic listing.
GETEVENTC(var status%)
Cancels a GETEVENTA32. Note that IOWAITSTAT should not be called after GETEVENTC - OPL consumes the GETEVENTC signal.
err%=KEYA(var status%,key%())
This is an asynchronous keyboard read function. You must declare an integer array with two elements here, key%(1) and key%(2) to receive the keypress information. If a key is pressed, the information is returned in this way:
key%(1) is assigned the character code of the key.
The least significant byte of key%(2) takes the key modifier, in the same way as KMOD 2 for Shift down, 4 for Control down and so on. KMOD cannot be used with KEYA.
The most significant byte of key%(2) takes the count of keys pressed (0 or 1).
KEYA needs an IOWAIT in the same way as IOC.
IOW has this specification:
ret%=IOW(handle%,func%,var arg1,var arg2)
Here are some uses:
LOCAL a%(6) IOW(-2,8,a%(),a%()) REM 2nd a% is ignored
senses the current text window (as set by the most recent SCREEN command) and the text cursor position ignoring any values already in the array a%(). This gives the same information as SCREENINFO, which should be used in preference (see Keyword Reference).
The first four elements are set to represent the offset of the current text window from the default text window. a%(1) is set to the x-offset of the top left corner of the current text window from the default text windows top left corner and a%(2) to the y-offset of the top left corner of current text window. Similarly a%(3) and a%(4) give the offset of bottom right corner of text window from the bottom right corner of the default text window. For example, if the most recent SCREEN command was SCREEN 10,11,4,5 the first four elements of the array a%() would be set to (3,4,13,15). The x and y positions of the cursor relative to the current text window are written to a%(5) and a%(6) respectively. All positions and offsets take 0,0, not 1,1, as the point at the top left.
LOCAL i%,a%(6) i%=2 a%(1)=x1% :a%(2)=y1% a%(3)=x2% :a%(4)=y2% IOW(-2,7,i%,a%())
clears a rectangle at x1%,y1% (top left), x2%,y2% (bottom right). If y2% is one greater than y1%, this will clear part or all of a line.
The final two procedures in this module call the two IOW screen functions described beforehand. The rest of the module lets you select the function and values to use. It uses the technique used in Menus of handling menus and short-cut keys by calling procedures with string expressions.
PROC iotest: GLOBAL x1%,x2%,y1%,y2% LOCAL i%,h$(2),a$(5) x1%=2 :y1%=2 x2%=25 :y2%=5 REM our test screensize SCREEN x2%-x1%,y2%-y1%,x1%,y1% AT 1,1 PRINT "Text window IO test" PRINT "Control-Q quits" h$="cr" REM our shortcut keys DO i%=GET IF i%=$122 REM MENU key mINIT mCARD "Set","Rect",%r mCARD "Sense","Cursor",%c i%=MENU IF i% AND INTF(LOC(h$,CHR$(i%))) a$="proc"+chr$(i%) @(a$): ENDIF ELSEIF i% AND $200 REM shortcut key i%=(i%-$200) i%=LOC(h$,CHR$(i%)) REM One of ours? IF i% a$="proc"+MID$(h$,i%,1) @(a$): ENDIF REM ignore other weird keypresses ELSE REM some other key, so return it PRINT CHR$(i%); ENDIF UNTIL 0 ENDP PROC procc: LOCAL a& a&=iocurs&: PRINT "x";1+(a& AND &ffff); PRINT "y";1+(a&/&10000) ENDP PROC procr: LOCAL xx1%,yy1%,xx2%,yy2% LOCAL xx1&,yy1&,xx2&,yy2& dINIT "Clear rectangle" dLONG xx1&,"Top left x",1,x2%-x1% dLONG yy1&,"Top left y",1,y2%-y1% dLONG xx2&,"Bottom left x",2,x2%-x1% dLONG yy2&,"Bottom left y",2,y2%-y1% IF DIALOG xx1%=xx1&-1 :xx2%=xx2&-1 yy1%=yy1&-1 :yy2%=yy2&-1 iorect:(xx1%,yy1%,xx2%,yy2%) ENDIF ENDP PROC iocurs&: LOCAL a%(4),a& REM dont change the order of these! a%(1)=x1% :a%(2)=y1% a%(3)=x2% :a%(4)=y2% IOW(-2,8,a%(),a%()) REM 2nd a% is ignored RETURN a& ENDP PROC iorect:(xx1%,yy1%,xx2%,yy2%) LOCAL i%,a%(6) i%=2 :REM "clear rect" option a%(1)=xx1% :a%(2)=yy1% a%(3)=xx2% :a%(4)=yy2% IOW(-2,7,i%,a%()) ENDP