Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Win32 File Objects

The basic low-level file-manipulation and device I/O functions provided by the Win32 API are CreateFile(), ReadFile(), WriteFile(), SetFilePointer(), LockFile(), UnlockFile(), and CloseHandle(). There are many more, but these probably are the most commonly used.

These functions manipulate a Win32 file object referenced by a Win32 HANDLE type. The object can be of various types, such as these:

Basic File I/O

CreateFile() is used to both create new files and open existing files, and it returns a Win32 HANDLE to the open file.

The first parameter to CreateFile() is the filename, which can be a UNC filename and pathname or the name of a device, such as COM1: or LPT1:.

The size of this filename is limited under Windows 95 and Windows 98 to the maximum size defined by MAX_PATH (which is set to 260 characters). Under Windows NT, however, you can prefix the pathname with the characters \\?\ to bypass this limitation.

The second parameter lets you specify the requested access mode, which can be 0 if you only want to determine the attributes of the file. Or, this parameter can be a combination of GENERIC_READ and GENERIC_WRITE for read and write access.

The third parameter lets you specify how you want to share the file with other applications. If you want exclusive access, you can pass a NULL value. Otherwise, FILE_SHARE_READ or FILE_SHARE_WRITE indicates that you will let other applications have read or write access to the file.

The fourth parameter to CreateFile() lets you specify security attributes by passing a pointer to a SECURITY_ATTRIBUTES structure. These attributes are available only under Windows NT, and you normally would pass a NULL to this parameter to gain the same (default) attributes as the calling process. If your application spawns other processes and then wants to inherit this file handle, however, you should set the bInheritHandle member of this structure to TRUE. Otherwise, the child process will not be able to use the file. This restriction on inheritance also applies to Windows 95 and Windows 98.

The fifth parameter lets you specify how you want to open the file. You can pass any of the values from Table 8.1.

Table 8.1. File Open/Creation Flags Used with CreateFile()

Flag Value Description
OPEN_EXISTING Opens an existing file or device and returns an error if the specified name doesn't exist.
OPEN_ALWAYS If the file doesn't exist, the function creates it as if you had passed the CREATE_NEW flag.
TRUNCATE_EXISTING Opens an existing file and wipes out the contents so that the file becomes a zero-length file.
CREATE_NEW Creates a new file but returns an error if the file already exists.
CREATE_ALWAYS Creates a new file, regardless of any existing file with the same name.

The sixth parameter lets you set and combine specific file attributes, such as hidden, archive, and read-only. Normally, you would use the FILE_ATTRIBUTE_NORMAL flag value.

You also can set a large number of flags in this parameter that change the way the subsequent file read and write functions work, as in the following ways:

The seventh parameter lets you specify the handle of a template file that can be used to set the file attributes of a file being created. This functionality isn't supported on Windows 95, however, and returns an unsupported error code.

If CreateFile() successfully creates the file, a valid HANDLE to that Win32 file object is returned; otherwise, INVALID_HANDLE_VALUE is returned. You can use the GetLastError() function to find out what went wrong and return an appropriate error code, as with all the file I/O functions.

You then can use the HANDLE value for subsequent ReadFile() and WriteFile() operations. The parameters for ReadFile() and WriteFile() indicate a buffer to store or read the data from, the number of bytes to write or read, and a pointer to a DWORD value to store the number of bytes successfully read or written. There is also a pointer to an OVERLAPPED structure for overlapped I/O (discussed later in this chapter, in the section, "Asynchronous I/O"). You can pass NULL for this parameter for normal blocking I/O (waiting for the function to complete). These functions return a simple Boolean value to indicate success (TRUE) or failure (FALSE).

You can reposition the current file position with the SetFilePointer() function. You can pass an amount to move by, as well as a flag value or FILE_BEGIN, FILE_CURRENT, or FILE_END to indicate that the amount is relative to the start, current position, or end of the file.

This function lets you specify 64-bit amounts to move by. Normally (for files smaller than 4GB), you'd only use the second parameter to specify the low 32-bit value and pass zero to the third parameter. However, if you have truly huge files, you can pass the high-order 32-bit word in the third parameter to access 264 bytes of data!

If a call to SetFilePointer() fails, a value of (DWORD)-1 is returned.

You can lock and unlock sections of a file using the LockFile() and UnlockFile() functions. These functions also let you specify two 64-bit numbers for the byte position to lock from and the size of the locked region. A Boolean return code indicates success or failure.

Finally, you can close the file with a call to the CloseHandle() function.

You can copy files by passing the source and destination filenames to the CopyFile() function, or you can rename them with the MoveFile() function.

The following listing illustrates these various file-handling functions with a program that uses a Win32 file object to calculate prime numbers (unusually getting faster with the higher primes!):

Example 8.1. File Handling Functions

#include "stdafx.h"
#include "windows.h"
#include "iostream.h"

const DWORD dwPrimes = 50000;        // Primes to 50,000

DWORD TestPrimes(HANDLE hFile);
DWORD DisplayError(LPSTR strError);

int main(int argc, char* argv[])
{
    HANDLE hPrimes = CreateFile(
            "Primes",                   // Filename
            GENERIC_READ|GENERIC_WRITE, // Read & Write
            NULL,                       // No Sharing
            NULL,                       // Default Security
            CREATE_ALWAYS,              // Create it
            FILE_FLAG_DELETE_ON_CLOSE,  // Delete afterwards
            NULL);                      // No template
    if (hPrimes == INVALID_HANDLE_VALUE)
        return DisplayError("Creating");
    // Set the file size to the max number of primes
    char buf[100];
    memset(buf,0,sizeof(buf));
    DWORD dwBytesWritten;
    while(WriteFile(hPrimes,&buf,sizeof(buf),
            &dwBytesWritten,NULL))
         if (GetFileSize(hPrimes,NULL)>dwPrimes) break;
    TestPrimes(hPrimes);
    return CloseHandle(hPrimes); // Close the file
}

DWORD TestPrimes(HANDLE hFile)
{
    DWORD dwBytesRead,dwBytesWritten,dwTestPrime = 2;
    do
    {
        BOOL bFillMode = FALSE;
        DWORD dwPos = dwTestPrime;
        while(dwPos<dwPrimes)
        {
            if (SetFilePointer(hFile,dwPos,0,
                    FILE_BEGIN)==(DWORD)-1)
                return DisplayError("Positioning");

            if (!bFillMode) // Is it a prime?
            {
                BYTE byteTestByte;
                if (!ReadFile(hFile,&byteTestByte,1,
                        &dwBytesRead,NULL))
                    return DisplayError("Reading");

                if (byteTestByte == 0) // Yes!
                {
                    cout << dwPos << "" << flush;
                    bFillMode = TRUE;

                    // Backup a byte
                    if (SetFilePointer(hFile,-1,0,
                        FILE_CURRENT)==(DWORD)-1)
                        return DisplayError("Positioning");
                }
            }
            if (bFillMode) // Remove all the factors
            {
                BYTE byteFill = 1;
                if (!WriteFile(hFile,&byteFill,1,
                        &dwBytesWritten,NULL))
                    return DisplayError("Writing");
            }
            dwPos+=dwTestPrime;
        }
    } while(dwTestPrime++ < dwPrimes);
    return 0;
}

DWORD DisplayError(LPSTR strError)
{
    DWORD dwError = GetLastError();
    cout << "An error occurred when " << strError
         << ", Errno = " << dwError;
    return dwError;
}
						

Asynchronous I/O

Asynchronous I/O under Windows is curiously termed overlapped I/O. Fundamentally, these terms refer to the capability to start a read or write request, and then return from the read or write function immediately without waiting for the request to finish. Given that I/O usually involves considerable waiting, asynchronous I/O can speed up a program by allowing it to perform some other processing while the I/O request is handled in the background. After your program has performed some other processing, it can check to see whether the I/O request has completed and process the I/O or continue with other jobs.

Some single-threaded programs may need to perform asynchronous operations so that they don't block while waiting for input (such as characters from a serial port or keyboard).

You can indicate that you want to perform an asynchronous I/O operation on a Win32 file object by passing the FILE_FLAG_OVERLAPPED value to the sixth parameter of the CreateFile function used to open the object. You then must provide a pointer to an OVERLAPPED structure for subsequent read and write operations.

The OVERLAPPED structure then is filled with internal information about the request when you call ReadFile() or WriteFile(). The ReadFile() or WriteFile() function returns immediately, and your program can perform other tasks. The functions return a FALSE value, indicating that an error occurred, with GetLastError() returning an ERROR_IO_PENDING value. This is a normal return code for asynchronous operations and just indicates that the operation is in progress.

You then can periodically check whether the operation has completed by using the HasOverlappedIoCompleted() macro, passing it a pointer to the OVERLAPPED structure.

You should ensure that the memory for this structure doesn't fall out of scope or become deleted before the function returns. Otherwise, a nasty crash or a blocked I/O situation can occur.

You can set the Offset and OffsetHigh DWORD members of the OVERLAPPED structure to specify a start position for a disk file transfer. You also can set the hEvent member to a manual-reset synchronization event handle that will be set to the signaled state when the operation has been completed. This lets you use the WaitForSingleObject() or WaitForMultipleObjects() function to wait for the I/O to complete.

You can find more details about an asynchronous operation using the GetOverlappedResult() function. This function needs the handle of the file object, a pointer to the OVERLAPPED structure, as input. It then can return the number of bytes transferred so far into a pointer to a DWORD passed as the third parameter. The fourth parameter is a Boolean value that you can use to make the function wait until the I/O operation is completed by passing TRUE. Otherwise, a FALSE value causes GetOverlappedResult() to return immediately, even though the I/O hasn't completed.

If an error occurs during the I/O operation, GetOverlappedResult() returns a FALSE value, and GetLastError() shows the reason for failure. However, this return code also may be ERROR_IO_PENDING if the operation is still in progress.

If you want to supply callback functions to be called when the I/O operation completes, you can use the ReadFileEx() and WriteFileEx() functions. These functions let you pass a pointer to an application CALLBACK function that will be passed any error codes, the number of bytes transferred, and a pointer to the OVERLAPPED structure after the I/O request completes.

You can find a sample listing of asynchronous overlapped I/O in the "Asynchronous Communications" section later in this chapter.

You can cancel an asynchronous I/O operation in progress by using the CancelIo() function and passing it the file object handle.

Share ThisShare This

Informit Network