Code Sample: Wrapping Some Microsoft® Windows® API Functions

The base code


void sample()
{
    HANDLE hFile= CreateFile("sample.txt",GENERIC_READ,
         0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
    );
    if (hFile) {
        // ... read the file content and display it
        const int bufsize= 200;
        char buffer[bufsize];
        DWORD bytesRead= 0;
        if (ReadFile(hFile,buffer,bufsize,&bytesRead,NULL)) {
            buffer[bytesRead]= '\0';
            printf("%s\n",buffer);
        }
        else {
            printf("File read failed due to error #%ld\n",GetLastError());
        }
        CloseHandle(hFile);
    }
}

We will start with a fairly ordinary C++ function that opens a disk file and prints its output to stdout. It uses the Windows API to access the disk file. Specifically, it uses the CreateFile, ReadFile and CloseHandle API functions.

This function opens a file and checks to verify that the file was opened. It reads the file into a buffer and prints the buffer to stdout. It then closes the file. If the file is not available the function simply returns without any message. If the file is opened but cannot be read an error message will be displayed. Much of the complexity of this function lies in long argument lists, in declaring variables needed to read the file and error handling. As we reengineer this function, the essential behavior of the function will become more clear and the details will migrate to supporting classes. We will end up with more lines of code, but gain reusability and reliability.

The CreateFilefunction creates a file and returns a handle to it. We test the handle to verify that the file was actually created, refer to it when reading the file and then close the file. We've located an object: a file object. The object has a beginning and an end. It has behavior: it allows determining whether the underlying file was opened and it allows reading data from the file. The API functions associated with the file handle are scattered throughout the API. A topical index of the API might lead you to them. It might also lead you to functions that are not associated with the handle. The underlying object is implicit. We will make the association explicit.

When I first started writing this article I found the CreateFile function name confusing. CreateFile can be used to create a file, but it can also be used to open an existing file. I finally realized that what it creates is a file object that represents and allows access to an actual file. With that clarified, I can comfortably refer to opening a file with the CreateFile method.

Once a file has been opened it must be closed. It can only be closed once. Once memory is allocated it must be freed. In the above example it is fairly easy to follow the path from opening and closing and assure that no matter what route is taken the file is closed once and only once no matter what execution path is followed. This cannot be as readily assured in more complex code. We also cannot assure that the file will be closed if an exception is thrown. Unclear object lifetime is a common problem. The open and close function calls end up in different functions. They become buried in conditional logic. Assumptions existing in the original code are forgotten later. Whenever we assume that someone will follow a protocol we risk that they will not. It is better to design a program so that the compiler is instructed how to produce fail-safe code.

[Previous: Introduction] [Next: Wrapping File Open and Close]


Microsoft and Windows are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.

The Code Untangler home page

Copyright © 2001, Randy McLaughlin