File Pointers
Windows, just like UNIX, the C library, and nearly every other OS, maintains a file pointer with each open file handle, indicating the current byte location in the file. The next WriteFile or ReadFile operation will start transferring data sequentially to or from that location and increment the file pointer by the number of bytes transferred. Opening the file with CreateFile sets the pointer to zero, indicating the start of the file, and the handle's pointer is advanced with each successive read or write. The crucial operation required for direct file access is the ability to set the file pointer to an arbitrary value, using SetFilePointer.
SetFilePointer shows, for the first time, how Windows handles the 64-bit NTFS. The techniques are not always pretty with this function, and SetFilePointer is easiest to use with small files.
DWORD SetFilePointer (
HANDLE hFile,
LONG lDistanceToMove,
PLONG lpDistanceToMoveHigh,
DWORD dwMoveMethod)
Return: The low-order DWORD (unsigned) of the new file pointer. The high-order portion of the new file pointer goes to the DWORD indicated by lpDistanceToMoveHigh (if non-NULL). In case of error, the return value is 0xFFFFFFFF.
Parameters
hFile is the handle of an open file with read or write access (or both).
lDistanceToMove is the 32-bit LONGsigned distance to move or unsigned file position, depending on the value of dwMoveMethod.
lpDistanceToMoveHigh points to the high-order portion of the move distance. If this value is NULL, the function can operate only on files whose length is limited to 2322. This parameter is also used to receive the high-order return value of the file pointer.[2] The low-order portion is the function's return value.
[2] Windows is not consistent, as can be seen by comparing SetFilePointer with GetCurrentDirectory. In some cases, distinct input and output parameters are used.
dwMoveMethod specifies one of three move modes.
-
FILE_BEGIN: Position the file pointer from the start of the file, interpreting DistanceToMove as unsigned.
-
FILE_CURRENT: Move the pointer forward or backward from the current position, interpreting DistanceToMove as signed. Positive is forward.
-
FILE_END: Position the pointer backward or forward from the end of file.
It is possible to use this function to obtain the file length by specifying a zero-length move from the end of file.
The method of representing 64-bit file positions causes complexities because the function return can represent both a file position and an error code. For example, suppose that the actual position is location 2321 (that is, 0xFFFFFFFF) and that the call also specifies the high-order move distance. Invoke GetLastError to determine whether the return value is a valid file position or whether the function failed, in which case the return value would not be NO_ERROR. This explains why file lengths are limited to 2322 when the high-order component is omitted.
Another confusing factor is that the high- and low-order components are separated and treated differently. The low-order address is treated as a call by value and returned by the function, whereas the high-order address is a call by reference and is both input and output.
Fortunately, 32-bit file addresses are sufficient for most programming tasks. Nonetheless, the programming examples take the long view and "do it right" using 64-bit arithmetic.
64-Bit Arithmetic
It is not difficult to perform the 64-bit file pointer arithmetic, and our example programs use Microsoft C's LARGE_INTEGER 64-bit data type, which is a union of a LONGLONG (called QuadPart) and two 32-bit quantities (LowPart, a DWORD, and HighPart, a LONG). LONGLONG supports all the arithmetic operations. There is also a ULONGLONG, which is unsigned.
lseek (in UNIX) and fseek (in the C library) are similar to SetFilePointer. Both systems also advance the file position during read and write operations.
|
Specifying File Position with an Overlapped Structure
Windows provides another way to set the file position that does not require SetFilePointer. Recall that the final parameter to both ReadFile and WriteFile is the address of an overlapped structure, and this value has always been NULL in the previous examples. Two members of this structure are Offset and OffsetHigh. You can set the appropriate values in an overlapped structure, and the I/O operation can start at the specified location. The file pointer is changed to point past the last byte transferred, but the overlapped structure values are not changed. The overlapped structure also has a handle member, hEvent, that must be set to NULL. Note: This technique will not work with Windows 9x as the overlapped pointer must be NULL when processing files.
Caution: Even though this example uses an overlapped structure, this is not overlapped I/O, which is covered in Chapter 14.
The overlapped structure is especially convenient when updating a file record, as illustrated in the following code fragment; otherwise, separate SetFilePointer calls would be required before the ReadFile and WriteFile calls. The hEvent field is the last of five fields, as is shown in the initialization statement. The LARGE_INTEGER data type is used to compute the file position.
OVERLAPPED ov = { 0, 0, 0, 0, NULL };
RECORD r; /* Definition not shown
but it includes the RefCount field. */
LONGLONG n;
LARGE_INGETER FilePos;
DWORD nRead, nWrite;
...
/* Update the reference count in the nth record. */
FilePos.QuadPart = n * sizeof (RECORD);
ov.Offset = FilePos.LowPart;
ov.OffsetHigh = FilePos.HighPart;
ReadFile (hFile, r, sizeof (RECORD), &nRead, &ov);
r.RefCount++; /* Update the record. */
WriteFile (hFile, r, sizeof (RECORD), &nWrite, &ov);
If the file handle was created with the FILE_FLAG_NO_BUFFERING CreateFile flag, then both the file position and the record size (byte count) must be multiples of the disk volume's sector size. Physical disk information, including sector size, is returned by GeTDiskFreeSpace.
Overlapped structures will be used again later in this chapter to specify file lock regions and in Chapter 14 for asynchronous I/O and direct file access.
|