Hacker’s Corner: Using DOS File Functions
Vector 9:1, page 124
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 124
With DOS being a file based system, a large number of devices operate by behaving as files. Examples are the printer and serial ports and some network devices. DEC Pathworks, for instance has a facility called TTT which associates a process on a VAX with a pseudo-file on the PC. The normal ⎕NREAD function has the disadvantage, when used to read from this type of device, that if you try to read beyond the end-of-file you get an error and no data at all. “And so,” thought I (having just invested in a copy of “Advanced MSDOS Programming” by Ray Duncan) “why not use the DOS file functions via ⎕INT 33?”.
Here I came across a problem. If a DOS function needs a text string, or returns text data then the segment address and offset of the text (or the buffer for data) need to be passed in the DS:DX register pair. Unfortunately ⎕INT, the normal method of calling interrupts, does not allow you to set DS.
The function DOS∆INT, which follows, gets round this problem.
The function passes the symbol table pointer of the variable containing the text in the register DI. Then a gobbet of 8086 machine code reads the address pointed to by DI into DS and calls the interrupt. The extra lines of machine code are for saving and restoring the previous value of DS and for putting into DX the offset of the actual data in the variable (10 for a text vector).
The assembler for the gobbet is:
push ds ; save the previous contents of DS
mov ds,[di] ; move into DS the address pointed to by DI
mov dx,10 ; move into DI the offset of the data in the var
int 33 ; call the DOS interrupt
pop ds ; restore the previous value of DS
ret ; return to APL
and the function which uses it is :
∇ OUTREGS←INREGS DOS∆INT DS_DX;CODE;RESSIZE
[1] ⍝ Call the DOS interrupt with DS:DX pointing to an APL vector
[2] ⍝ REGS is an integer vector of the registers AX BX CX DX BP & SI
[3] ⍝ DS_DX is the symbol table ptr (⎕STPTR) of the variable to which
[4] ⍝ DS:DX should point.
[5] ⍝ The contents of DX will be overwritten by 10 which is the
[6] ⍝ offset into a text vector of the actual data
[7]
[8] ⍝ push ds ; mov ds,[di] ; mov dx,10 ; int 33 ; pop ds ; ret
[9] CODE← 30 142 29 186 10 0 205 33 31 203
[10]
[11] ⍝ Ensure that ⎕CALL returns all of AX ... DI Flags DS and ES
[12] RESSIZE←10 ⎕POKE 886
[13]
[14] ⍝ Call the code with DS_DX in the DI register
[15] OUTREGS←((6↑INREGS),DS_DX)⎕CALL ⎕AV[⎕IO+CODE]
[16]
[17] ⍝ Restore the previous behaviour of ⎕CALL
[18] 0 0 ⍴RESSIZE ⎕POKE 886
∇
Here also are two functions to illustrate its use. These use the DOS functions 61 and 62 to open and read from a file. In the first case the filename is passed to the function and in the second the file data is passed back to APL. The general method is to set up a variable as a text vector in APL, call the DOS∆INT function with the symbol table pointer of the variable as right argument, then interpret the contents of the registers returned.
∇ HANDLE←FILE_OPEN NAME;REGS
[1] ⍝ Open a file with the DOS interrupt, returning the file handle
[2]
[3] ⍝ Null terminate the name as required by DOS
[4] NAME←NAME,⎕TCNUL
[5]
[6] ⍝ Call the DOS fn 61 (open file) in read/write mode (AX←256⊥61 2)
[7] ⍝ with DS_DX pointing at the name
[8] REGS←15618 DOS∆INT ⎕STPTR 'NAME'
[9]
[10] ⍝ Check the flags register for an error, which would be in AX
[11] ⎕ERROR(2|REGS[⎕IO+7])/'Dos file error ',⍕REGS[⎕IO]
[12]
[13] ⍝ If successful return the file handle from AX
[14] HANDLE←REGS[⎕IO]
∇
∇ DATA←HANDLE FILE_READ BYTES;REGS
[1] ⍝ Read from a file with the DOS interrupt
[2]
[3] ⍝ Create a blank result of the correct size
[4] DATA←BYTES⍴' '
[5]
[6] ⍝ Call the DOS fn 63 (read file) with the handle in BX and the
[7] ⍝ number of bytes to read in CX, DS_DX will point to the data area
[8] ⍝ of DATA
[9] REGS←(16128,HANDLE,BYTES)DOS∆INT ⎕STPTR 'DATA'
[10]
[11] ⍝ Check the flags register for an error, which would be in AX
[12] ⎕ERROR(2|REGS[⎕IO+7])/'Dos file error ',⍕REGS[⎕IO]
[13]
[14] ⍝ If successful, AX holds the no of bytes actually read, which are
[15] ⍝ returned
[16] DATA←REGS[⎕IO]↑DATA
∇
As mentioned above this last function has the advantage over ⎕NREAD that it will not cause an error if an attempt is made to read beyond the end of a file.