A Straightforward Stream Class Derivation

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.

The streambuf 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.