The main rule of file I/O in Windows is this: Do not keep files open for long periods of time. More specifically, that means you should not keep a file open between messages. You should open or create a file, read it or write to it in several large gulps, and then close it—all in the course of processing a single message.
You have two options for using files that have been opened with the OpenFile call:
Use normal C functions for file I/O. The file handle returned from OpenFile can be used directly with the ”low-level“ file I/O functions. The most important of these functions are open, read, lseek, close, create, write, and tell. We used the read and close functions in the HEAD program in Chapter 5.
The problem with these functions is that you can't use far pointers with them unless your program is compact model or large model. As you know, compact model and large model are not recommended for Windows programs because their data segments must be fixed in memory. If you want to read part of a file into a global memory segment, you must first read the file into a local memory block and then transfer it to the global memory block.
You can also use the normal C buffered file I/O functions such as fopen, fread, fwrite, and fclose. The MS-DOS file handle returned from OpenFile can be converted to a structure of type FILE using fdopen. The buffered file I/O functions are of less value in Windows than in other environments, because you need to read and write in large chunks, and buffering doesn't help unless you're reading small parts of a file.
Use file I/O functions included in Windows. These go by the names of _lopen, _lclose, _lcreat, _llseek, _lread, and _lwrite. The ”l“ prefix indicates that these functions accept far pointers for read and write buffers, thus allowing you to use them with global memory blocks. (These functions existed in Windows since version 1 but have only been documented beginning in Windows 3.)
This third method turns out to be the easiest when the file must be read into or written from buffer areas accessible only with far pointers. (The normal C low-level file I/O calls are preferable when you can use near pointers.) Do not write your own assembly-language routines for interfacing with the MS-DOS file I/O functions.
You'll probably use OpenFile to open and create files, but you can also use _lopen and _lcreat. The syntax is:
hFile = _lopen (lpszPathName, iReadWrite) ;
The lpszPathName parameter is a filename with an optional drive and subdirectory path. The iReadWrite parameter should be set to one of the identifiers OF_READ, OF_WRITE, or OF_READWRITE. The hFile value returned from _lopen is an MS-DOS file handle if the file is opened or -1 if the file cannot be opened.
The _lcreat function looks similar to the _lopen call:
hFile = _lcreat (lpszPathName, iAttribute) ;
However, the second parameter is an MS-DOS file attribute. Use 0 for a normal (nonhidden, nonsystem, read-write) file. If the file already exists, the size is truncated to 0 and opened; if it doesn't exist, it is created and opened. Like the _lopen call, _lcreat returns an MS-DOS file handle if the function is successful or -1 if an error occurs.
After an _lopen or _lcreat call, the file pointer is set initially to the beginning of the file. Normally, all reading and writing is sequential. The file pointer is updated after each read or write. However, you can use _llseek (MS-DOS Function 42H) to change the file pointer:
lPosition = _llseek (hFile, lPosition, iMethod) ;
The iMethod parameter should be set to one of the following values:
Value | Purpose |
0 | Move the file pointer lPosition bytes from the beginning of the file |
1 | Move the file pointer lPosition bytes from the current position in the file |
2 | Move the file pointer lPosition bytes from the end of the file |
The value of lPosition returned from _llseek is the new position of the file pointer if the function is successful or -1L if an error occurs.
If you want to open an existing file and add data to it, you call:
_llseek (hFile, 0L, 2) ;
This moves the file pointer to the end of the file. You can also use _llseek to determine the size of a file. You might want to define a function called FileLength to do this:
long FileLength (int hFile)
{
long lCurrentPos = _llseek (hFile, 0L, 1) ;
long lFileLength = _llseek (hFile, 0L, 2) ;
_llseek (hFile, lCurrentPos, 0) ;
return lFileLength ;
}
FileLength saves the current position of the file pointer, moves the file pointer to the end of the file, and then restores the file pointer to its original position.
To write to a file, use:
wBytesWritten = _lwrite (hFile, lpBuffer, wBytes) ;
The lpBuffer parameter is a far pointer to the data you want to write to the file, and wBytes is the number of bytes to write. The file buffer cannot extend past the end of a segment. The wBytesWritten value returned from the function is the number of bytes that are actually written. This can be less than wBytes if not enough disk space is available to write the entire buffer. Normally, MS-DOS function calls allow you to write up to 65,535 bytes to a file, but _lwrite returns -1 to signal an error, so you'll want to restrict yourself to 65,534 bytes or less.
The function to read from a file is similar:
wBytesRead = _lread (hFile, lpBuffer, wBytes) ;
The lpBuffer parameter is a far pointer to an area that receives the data read from the file, and wBytes is the number of bytes to read. The wBytesRead value returned can be less than wBytes if the end of the file is encountered before wBytes are read. A return value of -1 signals an error.
Finally, to close a file, use:
_lclose (hFile) ;
When working with files and global memory blocks, you may also need to make use of string functions that work with far pointers. Windows includes functions called lstrlen, lstrcpy, lstrcat, and lstrcmp that are equivalent to the normal C string functions strlen, strcpy, strcat, and strcmp, except that they use far pointers. These functions are useful for moving data between two global memory blocks or between a global memory block and a local memory block. They are coded in assembly language and are thus much faster than equivalent C code. The lstrcmp function is a ”Windows version“ of the strcmp function: It is case sensitive for both the normal ASCII character codes and the ANSI character codes, and it can accommodate strings with multibyte character codes.