printf, print, puti, puth, putc, getc, gets,
etc., when performing file I/O. This greatly simplifies text file operations in assembly language.fopen
, fcreate
, fclose
, fgetc
, fread
, fputc
, and fwrite
. Fgetc
and fputc
perform character at a time I/O, fread
and fwrite
let you read and write blocks of data, the other four functions perform the obvious DOS operations.FileVar
for every file you need open at the same time. This is very easy, just use a definition of the form:MyFileVar FileVar {}
Please note that a Standard Library file variable is not the same thing as a DOS file handle. It is a structure which contains the DOS file handle, a buffer (for blocked I/O), and various index and status variables. The internal structure of this type is of no interest (remember data encapsulation!) except to the implementor of the file routines. You will pass the address of this file variable to the various Standard Library file I/O routines.
Entry parameters: ax- File open mode 0- File opened for reading 1- File opened for writing dx:si- Points at a zero terminated string containing the filename. es:di- Points at a StdLib file variable. Exit parameters: If the carry is set, ax contains the returned DOS error code (see DOS open function).
Fopen
opens a sequential text file for reading or writing. Unlike DOS, you cannot open a file for reading and writing. Furthermore, this is a sequential text file which does not support random access. Note that the file must exist or fopen
will return an error. This is even true when you open the file for writing.
Note that if you open a file for writing and that file already exists, any data written to the file will overwrite the existing data. When you close the file, any data appearing in the file after the data you wrote will still be there. If you want to erase the existing file before writing data to it, use the fcreate
function.
Entry parameters: dx:si- Points at a zero terminated string containing the filename. es:di- Points at a StdLib file variable. Exit parameters: If the carry is set, ax contains the returned DOS error code (see DOS open function).
Fcreate
creates a new file and opens it for writing. If the file already exists, fcreate
deletes the existing file and creates a new one. It initializes the file variable for output but is otherwise identical to the fopen
call.
Entry parameters: es:di- Points at a StdLib file variable. Exit parameters: If the carry is set, ax contains the returned DOS error code (see DOS open function).
Fclose
closes a file and updates any internal housekeeping information. It is very important that you close all files opened with fopen
or fcreate
using this call. When making DOS file calls, if you forget to close a file DOS will automatically do that for you when your program terminates. However, the StdLib routines cache up data in internal buffers. the fclose call automatically flushes these buffers to disk. If you exit your program without calling fclose
, you may lose some data written to the file but not yet transferred from the internal buffer to the disk.
If you are in an environment where it is possible for someone to abort the program without giving you a chance to close the file, you should call the fflush routines (see the next section) on a regular basis to avoid losing too much data.
Entry parameters: es:di- Points at a StdLib file variable. Exit parameters: If the carry is set, ax contains the returned DOS error code (see DOS open function).
This routine immediately writes any data in the internal file buffer to disk. Note that you should only use this routine in conjunction with files opened for writing (or opened by fcreate
). If you write data to a file and then need to leave the file open, but inactive, for some time period, you should perform a flush operation in case the program terminates abnormally.
Entry parameters: es:di- Points at a StdLib file variable. Exit parameters: If the carry flag is clear, al contains the character read from the file. If the carry is set, ax contains the returned DOS error code (see DOS open function). ax will contain zero if you attempt to read beyond the end of file.
Fgetc
reads a single character from the file and returns this character in the al
register. Unlike calls to DOS, single character I/O using fgetc
is relatively fast since the StdLib routines use blocked I/O. Of course, multiple calls to fgetc
will never be faster than a call to fread
(see the next section), but the performance is not too bad.
Fgetc
is very flexible. As you will see in a little bit, you may redirect the StdLib input routines to read their data from a file using fgetc
. This lets you use the higher level routines like gets
and getsm
when reading data from a file.
Entry parameters: es:di- Points at a StdLib file variable. dx:si- Points at an input data buffer. cx- Contains a byte count. Exit parameters: If the carry flag is clear, ax contains the actual number of bytes read from the file. If the carry is set, ax contains the returned DOS error code (see DOS open function).
Fread
is very similar to the DOS read
command. It lets you read a block of bytes, rather than just one byte, from a file. Note that if all you are doing is reading a block of bytes from a file, the DOS call is slightly more efficient than fread
. However, if you have a mixture of single byte reads and multi-byte reads, the combination of fread
and fgetc
work very well.
As with the DOS read operation, if the byte count returned in ax
does not match the value passed in the cx
register, then you've read the remaining bytes in the file. When this occurs, the next call to fread
or fgetc
will return an EOF error (carry will be set and ax
will contain zero). Note that fread
does not return EOF unless there were zero bytes read from the file.
Entry parameters: es:di- Points at a StdLib file variable. al- Contains the character to write to the file. Exit parameters: If the carry is set, ax contains the returned DOS error code (see DOS open function).
Fputc
writes a single character (in al
) to the file specified by the file variable whose address is in es:di
. This call simply adds the character in al
to an internal buffer (part of the file variable) until the buffer is full. Whenever the buffer is filled or you call fflush
(or close the file with fclose
), the file I/O routines write the data to disk.
Entry parameters: es:di- Points at a StdLib file variable. dx:si- Points at an output data buffer. cx- Contains a byte count. Exit parameters: If the carry flag is clear, ax contains the actual number of bytes written to the file. If the carry is set, ax contains the returned DOS error code (see DOS open function).
Like fread
, fwrite
works on blocks of bytes. It lets you write a block of bytes to a file opened for writing with fopen or fcreate.
Fputc
and fwrite
are the only two output routines, for example. The "C" programming language standard library (on which the UCR Standard Library is based) provides many routines like fprintf, fputs, fscanf,
etc. None of these are necessary in the UCR Standard Library because the UCR library provides an I/O redirection mechanism that lets you reuse all existing I/O routines to perform file I/O.putc
routine consists of a single jmp
instruction. This instruction transfers control to some actual output routine via an indirect address internal to the putc code. Normally, this pointer variable points at a piece of code which writes the character in the al
register to the DOS standard output device. However, the Standard Library also provides four routines which let you manipulate this indirect pointer. By changing this pointer you can redirect the output from its current routine to a routine of your choosing. All Standard Library output routines (e.g., printf, puti, puth, puts
) call putc
to output individual characters. Therefore, redirecting the putc
routine affects all the output routines.getc
routine is nothing more than an indirect jmp
whose pointer variable normally points at a piece of code which reads data from the DOS standard input. Since all Standard Library input routines call the getc
function to read each character you can redirect file input in a manner identical to file output.GetOutAdrs, SetOutAdrs, PushOutAdrs,
and PopOutAdrs
are the four main routines which manipulate the output redirection pointer. GetOutAdrs
returns the address of the current output routine in the es:di
registers. Conversely, SetOutAdrs
expects you to pass the address of a new output routine in the es:di
registers and it stores this address into the output pointer. PushOutAdrs
and PopOutAdrs
push and pop the pointer on an internal stack. These do not use the 80x86's hardware stack. You are limited to a small number of pushes and pops. Generally, you shouldn't count on being able to push more than four of these addresses onto the internal stack without overflowing it.GetInAdrs, SetInAdrs, PushInAdrs,
and PopInAdrs
are the complementary routines for the input vector. They let you manipulate the input routine pointer. Note that the stack for PushInAdrs/PopInAdrs
is not the same as the stack for PushOutAdrs/PopOutAdrs
. Pushes and pops to these two stacks are independent of one another.PutcStdOut[9]
. Therefore, you can return the output hook to its normal initialization state at any time by executing the statements[10]:mov di, seg SL_PutcStdOut mov es, di mov di, offset SL_PutcStdOut SetOutAdrs
The PutcStdOut
routine writes the character in the al
register to the DOS standard output, which itself might be redirected to some file or device (using the ">" DOS redirection operator). If you want to make sure your output is going to the video display, you can always call the PutcBIOS
routine which calls the BIOS directly to output a character[11]. You can force all Standard Library output to the standard error device using a code sequence like:
mov di, seg SL_PutcBIOS mov es, di mov di, offset SL_PutcBIOS SetOutAdrs
Generally, you would not simply blast the output hook by storing a pointer to your routine over the top of whatever pointer was there and then restoring the hook to PutcStdOut
upon completion. Who knows if the hook was pointing at PutcStdOut
in the first place? The best solution is to use the Standard Library PushOutAdrs
and PopOutAdrs
routines to preserve and restore the previous hook. The following code demonstrates a gentler way of modifying the output hook:
PushOutAdrs ;Save current output routine. mov di, seg Output_Routine mov es, di mov di, offset Output_Routine SetOutAdrs <Do all output to Output_Routine here> PopOutAdrs ;Restore previous output routine.
Handle input in a similar fashion using the corresponding input hook access routines and the SL_GetcStdOut
and SL_GetcBIOS
routines. Always keep in mind that there are a limited number of entries on the input and output hook stacks so what how many items you push onto these stacks without popping anything off.
To redirect output to a file (or redirect input from a file) you must first write a short routine which writes (reads) a single character from (to) a file. This is very easy. The code for a subroutine to output data to a file described by file variable OutputFile
is
ToOutput proc far push es push di ; Load ES:DI with the address of the OutputFile variable. This ; code assumes OutputFile is of type FileVar, not a pointer to ; a variable of type FileVar. mov di, seg OutputFile mov es, di mov di, offset OutputFile ; Output the character in AL to the file described by "OutputFile" fputc pop di pop es ret ToOutput endp
Now with only one additional piece of code, you can begin writing data to an output file using all the Standard Library output routines. That is a short piece of code which redirects the output hook to the "ToOutput
" routine above:
SetOutFile proc push es push di PushOutAdrs ;Save current output hook. mov di, seg ToOutput mov es, di mov di, offset ToOutput SetOutAdrs pop di pop es ret SetOutFile endp
There is no need for a separate routine to restore the output hook to its previous value; PopOutAdrs
will handle that task by itself.