Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Compatibility I/O

The Windows operating systems support a range of I/O styles that provide compatibility with other operating systems—principally, UNIX and DOS.

These functions can make it easier to port simple UNIX applications and old C-style, text-based applications onto Windows platforms. For simple batch-file processing jobs, these functions can be easier to use than the sophisticated Win32 functions.

All these functions ultimately make calls down to the Win32 functions discussed in previous chapters.

Low-Level I/O

The term low-level I/O is a misnomer, because the real Windows Win32 API low-level I/O functions are the CreateFile(), ReadFile(), and WriteFile() functions. However, the low-level I/O functions refer to the C-style low-level I/O functions inherited from the UNIX world.

The basic handle for these low-level functions is called a file descriptor and consists of an integer value. Three predefined standard handles always are available that always have the values 0, 1, and 2.

These handles are available only from console applications and run from an MS-DOS shell window. By default, the standard input descriptor (0) is the keyboard, the output descriptor (1) is the display, and the error descriptor (2) also sends output to the display.

However, these file descriptors can be redirected by using the MS-DOS shell's pipe (|) and redirection symbols (< and >). This redirection provides a powerful tool to concatenate the input and output of programs.

For example, consider this familiar MS-DOS command:

DIR | MORE

What this really means is that the output handle (1) of the DIR command becomes the input handle (0) of the MORE command.

The following SORT command redirects the input file descriptor (0) to read a file rather than the keyboard, and the output file descriptor (1) is sent to a file rather than the display:

SORT < INPUT.TXT > SORTED.TXT

The shell is responsible for hooking these file handles to the appropriate input or output file or device and passing them to the command when it starts.

Your low-level I/O program can read and write to these handles using the _read() and _write() functions.

For example, the following console application garbles the input from the descriptor (0) by incrementing all the characters with an ASCII code over 32 and then writes the result to the output descriptor (1):

#include "stdafx.h"
#include "io.h"
int main(int argc, char* argv[])
{
    char szChr;
    while(_read(0,&szChr,1))
    {
        if (szChr>32) szChr++;
        _write(1,&szChr,1);
    }
    return 0;
}

You can run this code from the MS-DOS command line, piping the output of DIR into the standard input file descriptor, like this:

DIR | GARBLE

You can create or open files with low-level I/O using the _creat() and _open() functions. The _creat() function requires a filename and a flag to indicate the file read and write permissions. You can combine the _S_IREAD and _S_IWRITE flag values to indicate these permissions. The _open() function requires a filename and a combination of the following flag values for its second parameter, as Table 8.2 shows.

Table 8.2. File Open/Creation Flags Used for the _open Function.

Flag Value Description
_O_CREAT Create and open for writing.
_O_APPEND Open the file for appending.
_O_RDONLY Open for read only.
_O_WRONLY Open for write only.
_O_RDWR Open for reading and writing.
_O_BINARY Don't perform text conversions.
_O_TEXT Perform text conversions.
_O_TRUNC Open and truncate the file.

The optional third parameter lets you specify the _S_IREAD and _S_IWRITE flag values as in the _creat() function. Both _creat() and _open() return a file handle integer for use in subsequent functions, or a –1 value if the file couldn't be opened.

You can position the file using the _lseek() function passing the file handle, the offset position, and the origin flag in a similar way to the Win32 SetFilePointer() function. The origin flags are SEEK_SET, SEEK_CUR, and SEEK_END to indicate that the file seek should be relative to the start, current, or end file position. You can find the current file position by using the _tell() function.

You should use _close() to finally close the file. The following example illustrates these functions by creating a file containing a text string, closing it, and then reopening it to read the text string backward and write the output to the console.

#include "stdafx.h"
#include "io.h"
#include "fcntl.h"
#include "sys\stat.h"
#include "string.h"
int main(int argc, char* argv[])
{
    // Create a file, setting read and write permissions
    int hFile = _creat("test.txt",_S_IWRITE|_S_IREAD);

    // Write some text to a file
    char* pszTxt = "This is my test text\n";
    _write(hFile,pszTxt,strlen(pszTxt));
    _close(hFile);

    // Re-open the file for reading
    hFile = _open("test.txt",_O_RDONLY);

    // Copy it backwards to the console
    _lseek(hFile,0L,SEEK_END);
    int nLen = _tell(hFile)-1;
    char szBuf;
    while(nLen>=0)
    {
        _lseek(hFile,nLen—,SEEK_SET);
        _read(hFile,&szBuf,1);
        _write(1,&szBuf,1);
    }
    return _close(hFile);
}
						

Stream I/O

The stream I/O functions provide compatibility for C-style I/O. These functions access a handle in the form of pointers to FILE structures called streams. These structures themselves encapsulate the low-level file descriptor handle as the _file member of the FILE structure. The stream I/O functions use the low-level functions (which in turn use the Win32 functions) but provide extended capabilities and built-in buffering.

The standard input, standard output, and standard error file descriptors are mapped to the stdin, stdout, and stderr streams. These streams are just predefined pointers to FILE structures that map to the low-level standard 0, 1, and 2 handles. Therefore, you can use these standard streams for redirected and piped input and output.

You can use a host of stream functions to provide fairly sophisticated buffered file control. The familiar printf() function, for example, is just a special case of fprintf() that always sends its output to stdout.

You can open files using _fopen() to pass a filename and character string indicating the type of access, such as r for reading, w for writing, and r+ for reading and writing. If successful, a pointer to a FILE structure is returned and can be used for subsequent operations. You can find the low-level file descriptor by using the _fileno() function, and you can use an existing descriptor to open a stream via the _fdopen() function.

Other useful functions are _fread(), fgets(), fgetc(), and _fscanf() for reading in various ways; and the corresponding fwrite(), fputs(), fputc(), and fprintf() for writing.

You can position the file using _fseek() and find the position using _ftell(). The _fflush() function flushes any unwritten output, and _fclose() closes the stream.

The IOStream Classes

Similar to the way the Microsoft Foundation Classes wrap much of the underlying Win32 functionality, the IOStream classes are C++ classes that wrap the stream functionality.

The ios class is the base class for the other I/O stream classes and contains a number of common member functions for status testing and buffer manipulation. This class also contains a number of enumerated flag values for manipulating the input and output stream data.

The istream class extends the ios class to provide a number of reading and repositioning functions, such as get(), read(), peek(), putback(), seekg(), and tellg(). You can use the istream class directly, or derive your own classes from istream to add your application-specific functionality.

A corresponding ostream extends the ios class to provide writing-oriented functions such as put(), write(), flush(), seekp(), and tellp(). Once again, you can inherit your own classes from this class to extend the write-only–oriented functionality offered by ostream.

Using the sometimes-frowned-upon (but more widely accepted) practice of multiple inheritance, the iostream class combines the istream and ostream classes to give you a read- and write-capable class.

Before you use this jungle of classes, you'll notice that the constructors require a pointer to a streambuf-derived class. These classes provide the connection to the file, standard I/O channel, or character array in memory via the filebuf, stdiobuf, or strstreambuf subclasses. You must construct an object from one of these three classes (or a derivative) and then pass the pointer to a constructor of a class derived from iostream in order to initialize the manipulator object.

Instead of deriving from or using the istream, ostream, and iostream classes directly, you probably would want to use one of the more specific alternatives, such as fstream, stdiostream, and strstream, which derive from iostream. You could use fstream for file I/O, stdiostream for standard-stream I/O, or strstream for character buffer manipulation. The big advantage of using these classes is that their constructor functions automatically create the filebuf, stdiobuf, or strstreambuf objects required, thus letting you create one-stop–shop objects.

If all this sounds like a lot of hard work, you're right—it is—but you should remember there are a lot of clever member functions of each of these classes that comprise a very sophisticated suite of file and character-manipulation classes.

Like the low-level and stream I/O on which they are based, three corresponding standard predefined objects let you manipulate the standard input, output, and error channels, as Table 8.3 shows.

Table 8.3. IOStream Objects and Their Related Streams

Object Descriptor Class Type Stream File
cin istream stdin 0
cout ostream stdout 1
cerr ostream stderr 2

These cin, cout, and cerr objects actually are created as objects of istream_ withassign and ostream_withassign, which are classes that allow the objects to be assigned to other types of istream and ostream objects for piping and redirection to other program output or disk files.

There is also a clog object that is similar to cerr but is fully buffered, whereas cerr is flushed immediately when data is assigned to it.

This example shows how to perform the equivalent text scrambling with a console application, as in the low-level I/O section earlier, but it uses cin and cout:

#include "stdafx.h"
#include "iostream.h"
int main(int argc, char* argv[])
{
    char szChr;
    cin.flags(0);
    while(cin >> szChr)
    {
        if (szChr>32) szChr++;
        cout << szChr;
    }
    return 0;
}

You'll notice that the cin.flags(0) is used to remove some rather unpleasant text-filtering flags from the input channel. The overloaded >> operator is used to read a character into the szChr single-character buffer, and the << operator writes it out after manipulation.

Share ThisShare This

Informit Network