Like any C++ class, a stream class can be derived for the simple purpose of adding new member functions, data members, or manipulators. If you need an input file stream that tokenizes its input data, for example, you can derive from the ifstream class. This new derived class can include a member function that returns the next token by calling its base class's public member functions or extractors. You might need some new data members to hold the stream object's state between operations, but you probably will not need to use the base class's protected member functions or data members.
For the straightforward stream class deriviation, you need only write the necessary constructors and the new member functions.
The class derivation example that follows is more complex because it exploits the relationship between the stream class and the associated stream buffer class.
You have probably noticed some references to a streambuf class. This class is unimportant unless you plan major customizations of the iostream library. As you explore the iostream classes, you will learn that streambuf really does most of the work for the other stream classes. You will create a modified output stream by deriving only a new streambuf class and connecting it to the standard ostream class.
Why Derive a Custom streambuf Class?
The existing output streams communicate to the file system and to in-memory strings. You can invent your own streams that address a memory-mapped video display, a window as defined by Microsoft Windows, some new physical device, and so forth. A simpler goal is the alteration of the byte stream as it goes to a file system device. This customization technique, as you will see, is sufficient to produce an object-oriented interface to laser printers such as the Hewlett-Packard LaserJet series.
A streambuf Derivation Example
Example 4 shows you how to modify the cout object such that prints in two-column landscape (horizontal) mode on a Hewlett-Packard LaserJet printer or other printer that uses the PCL control language. As the test driver program shows, all the member functions and manipulators that work with the original cout also work with the special version. The application programming interface is the same!
Example 4
The example is divided into three source files.
HSTREAM.H—the LaserJet class declaration that must be included in both the implementation file and your application file
HSTREAM.CPP—the LaserJet class implementation that must be linked with your application
EXIOS204.CPP—the test driver program that sends output to a LaserJet printer
The HSTREAM.H file contains only the class declaration for hstreambuf. This class is derived from filebuf and overrides the appropriate filebuf virtual functions.
// htream.h - HP LaserJet output stream header
#include <fstream.h> // Accesses 'filebuf' class
#include <string.h>
#include <stdio.h> // for sprintf
class hstreambuf : public filebuf
{
public:
hstreambuf( int filed );
virtual int sync();
virtual int overflow( int ch );
~hstreambuf();
private:
int column, line, page;
char* buffer;
void convert( long cnt );
void newline( char*& pd, int& jj );
void heading( char*& pd, int& jj );
void pstring( char* ph, char*& pd, int& jj );
};
ostream& und( ostream& os );
ostream& reg( ostream& os );
HSTREAM.CPP contains the the hstreambuf class implementation.
// hstream.cpp - HP LaserJet output stream
#include "hstream.h"
#define REG 0x01 // Regular font code
#define UND 0x02 // Underline font code
#define CR 0x0d // Carriage return character
#define NL 0x0a // Newline character
#define FF 0x0c // Formfeed character
#define TAB 0x09 // Tab character
#define LPP 57 // Lines per Page
#define TABW 5 // Tab width
// Prolog defines printer initialization (font, orientation, etc.
char prolog[] =
{ 0x1B, 0x45, // Reset printer
0x1B, 0x28, 0x31, 0x30, 0x55,// IBM PC char set
0x1B, 0x26, 0x6C, 0x31, 0x4F,// Landscape
0x1B, 0x26, 0x6C, 0x38, 0x44, // 8 lines-per-inch
0x1B, 0x26, 0x6B, 0x32, 0x53}; // Lineprinter font
// Epilog prints the final page and terminates the output
char epilog[] = { 0x0C, 0x1B, 0x45 }; // Formfeed, reset
char uon[] = { 0x1B, 0x26, 0x64, 0x44, 0 }; // Underline on
char uoff[] = { 0x1B, 0x26, 0x64, 0x40, 0 };// Underline off
hstreambuf::hstreambuf( int filed ) : filebuf( filed )
{
column = line = page = 0;
int size = sizeof( prolog );
setp( prolog, prolog + size );
pbump( size ); // Puts the prolog in the put area
filebuf::sync(); // Sends the prolog to the output file
buffer = new char[1024]; // Allocates destination buffer
}
hstreambuf::~hstreambuf()
{
sync(); // Makes sure the current buffer is empty
delete buffer; // Free the memory
int size = sizeof( epilog );
setp( epilog, epilog + size );
pbump( size ); // Puts the epilog in the put area
filebuf::sync(); // Sends the epilog to the output file
}
virtual int hstreambuf::sync()
{
long count = out_waiting();
if ( count ) {
convert( count );
}
return filebuf::sync();
}
virtual int hstreambuf::overflow( int ch )
{
long count = out_waiting();
if ( count ) {
convert( count );
}
return filebuf::overflow( ch );
}
//*** The following code is specific to the HP LaserJet printer ***
// Converts a buffer to HP, then writes it
void hstreambuf::convert( long cnt )
{
char *bufs, *bufd; // Source, destination pointers
int j = 0;
bufs = pbase();
bufd = buffer;
if( page == 0 ) {
newline( bufd, j );
}
for( int i = 0; i < cnt; i++ ) {
char c = *( bufs++ ); // Gets character from source buffer
if( c >= ' ' ) { // Character is printable
* ( bufd++ ) = c;
j++;
column++;
}
else if( c == NL ) { // Moves down one line
*( bufd++ ) = c; // Passes character through
j++;
line++;
newline( bufd, j ); // Checks for page break, etc.
}
else if( c == FF ) { // Ejects paper on formfeed
line = line - line % LPP + LPP;
newline( bufd, j ); // Checks for page break, etc.
}
else if( c == TAB ) { // Expands tabs
do {
*( bufd++ ) = ' ';
j++;
column++;
} while ( column % TABW );
}
else if( c == UND ) { // Responds to 'und' manipulator
pstring( uon, bufd, j );
}
else if( c == REG ) { // Responds to 'reg' manipulator
pstring( uoff, bufd, j ); //
}
}
setp( buffer, buffer + 1024 ); // Sets new put area
pbump( j ); // Indicates the number of characters in the dest buffer
}
// simple manipulators - apply to all ostream classes
ostream& und( ostream& os ) // Turns on underscore mode
{
os << (char) UND; return os;
}
ostream& reg( ostream& os ) // Turns off underscore mode
{
os << (char) REG; return os;
}
void hstreambuf::newline( char*& pd, int& jj )
// Called for each newline character
column = 0;
if ( ( line % ( LPP*2 ) ) == 0 ) // Even page
{
page++;
pstring( "\033&a+0L", pd, jj ); // Set left margin to zero
heading( pd, jj );/* print heading */
pstring( "\033*p0x77Y", pd, jj ); // Cursor to (0,77) dots
}
if ( ( ( line % LPP ) == 0 ) && ( line % ( LPP*2 ) ) != 0 ) // Odd page
{ // prepare to move to right column
page++;
pstring( "\033*p0x77Y", pd, jj ); // Cursor to (0,77) dots
pstring( "\033&a+88L", pd, jj ); // Left margin to 88th column
}
}
void hstreambuf::heading( char*& pd, int& jj ) // Prints page heading
{
char hdg[20];
int i;
if( page > 1 ) {
*( pd++ ) = FF;
jj++;
}
pstring( "\033*p0x0Y", pd, jj ); // Top of page
pstring( uon, pd, jj ); // Underline on
sprintf( hdg, "Page %-3d", page );
pstring( hdg, pd, jj );
for( i=0; i < 80; i++ ) { // Pads with blanks
*( pd++ ) = ' ';
jj++;
}
sprintf( hdg, "Page %-3d", page+1 ) ;
pstring( hdg, pd, jj );
for( i=0; i < 80; i++ ) { // Pads with blanks
*( pd++ ) = ' ';
jj++;
}
pstring( uoff, pd, jj ); // Underline off
}
// Outputs a string to the buffer
void hstreambuf::pstring( char* ph, char*& pd, int& jj )
{
int len = strlen( ph );
strncpy( pd, ph, len );
pd += len;
jj += len;
}
EXIOS204.CPP is the test driver program. It reads text lines from cin and writes them to the modified cout.
// exios204.cpp
// hstream Driver program copies 'cin' to 'cout' until end-of-file
#include "hstream.h"
hstreambuf hsb( 1 ); // 1=stdout
void main()
{
char line[200];
cout = &hsb; // Associates the HP LaserJet streambuf to cout
while( 1 ) {
cin.getline( line, 200 );
if( !cin.good() ) break;
cout << line << endl;
}
}
Here are the main points in the code above:
The new class hstreambuf is derived from filebuf, the buffer class for disk file I/O. The filebuf class does the actual writing to disk in response to commands from its associated ostream class. The hstreambuf constructor takes an argument that corresponds to the operating system file number, in this case 1 for sdtout. This constructor is invoked by the following line:
hstreambuf hsb( 1 );
The hstreambuf object is associated with cout by the ostream_withassign assignment operator:
ostream& operator =( streambuf* sbp );
The following statement in EXIOS204.CPP executes the assignment.
cout = &hsb;
The hstreambuf constructor prints the prolog that sets up the laser printer, and it allocates a temporary print buffer.
The destructor outputs the epilog text and frees the print buffer when the object goes out of scope. In this example, the object goes out of scope after the exit from main.
The streambuf virtual overflow and sync functions do the low-level output. The hstreambuf class overrides these functions in order to gain control of the byte stream. The functions call the private convert member function.
The convert function processes the characters inside the hstreambuf buffer and stores them in the object's temporary buffer. The tempory buffer is then processed by the filebuf functions.
The details of convert relate more to the PCL language than to the iostream library. Note that there are private data members that keep track of column number, line number and page number.
The und and reg manipulators control the underscore print attribute. They simply insert codes 0x02 and 0x03 into the stream. These codes are later translated into the printer-specific sequences by convert.
Example 4 is a useful program now, but you can easily extend it to embellish the heading, add more formatting features, and so forth.
In a more general example, the hstreambuf class would have been derived from streambuf rather than from filebuf. The filebuf derivation gets the most leverage from existing iostream library code, but it makes certain assumptions about the implementation of filebuf, particularly the overflow and sync functions. Thus you could not necessarily expect the example to work with other derived streambuf classes or with the filebuf classes provided by other software publishers.