Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

Implementing the Serialization Interface

355

You must create the actual interface class and identify all areas in which you need input from the derived class. At this stage, you’re implementing all the functionality for the interface class — creating it as if it were a main class for your application.

After you’ve created the interface class, you set up your derived class to inherit from it (and to implement any virtual functions the interface requires).

Implementing the Serialization

Interface

The most popular interface is the serialization interface. This interface, used by many of the popular class libraries, such as MFC, allows an object to be written to persistent storage (such as a file) so long as it implements a consistent interface. In this example, we will explore how to create a serialization interface and apply that interface to a given class or classes.

1. In the code editor of your choice, create a new file to hold the code for the interface definition of the technique.

In this example, the file is named ch59.h, although you can use whatever you choose. This file will contain the class definition for your serialization object.

2. Type the code from Listing 59-1 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 59-1: THE SERIALIZATION INTERFACE CODE

#ifndef _SERIALIZE_H_ #define _SERIALIZE_H_

#include <string> #include <vector>

#include <iostream> #include <fstream>

using namespace std;

class SerializeEntry

{

string _name; string _value;

public:

SerializeEntry(void)

{

}

SerializeEntry( const char *name, const char *value )

{

setName( name ); setValue( value );

}

SerializeEntry( const char *name, int iValue )

{

setName( name ); setValue( iValue );

}

SerializeEntry( const char *name, double dValue )

{

setName( name ); setValue( dValue );

}

SerializeEntry( const SerializeEntry& aCopy )

{

setName( aCopy.getName().c_str() ); setValue( aCopy.getValue().c_str()

);

}

SerializeEntry operator=(const SerializeEntry& aCopy)

{

setName( aCopy.getName().c_str() ); setValue( aCopy.getValue().c_str()

);

return *this;

}

(continued)

356 Technique 59: Implementing a Serialization Interface

LISTING 59-1 (continued)

void setName( const char *name )

{

if ( name ) _name = name;

else

_name = “”;

}

void setValue( const char *value )

{

if ( value ) _value = value;

else

_value = “”;

}

void setValue( int iValue )

{

char szBuffer[ 20 ]; sprintf(szBuffer, “%d”, iValue ); setValue ( szBuffer );

}

void setValue( double dValue )

{

char szBuffer[ 20 ]; sprintf(szBuffer, “%lf”, dValue ); setValue ( szBuffer );

}

string getName(void) const

{

return _name;

}

string getValue(void) const

{

return _value;

}

};

class Serialization

{

private:

long _majorVersion; long _minorVersion;

protected:

virtual bool getEntries( vector< SerializeEntry >& entries )

{

return true;

}

virtual string getClassName()

{

return “None”;

}

 

public:

 

Serialization(void)

 

{

 

_majorVersion =

0;

_minorVersion =

0;

}

 

Serialization( long

major,

{ long minor )

2

_majorVersion =

major;

_minorVersion =

minor;

}

Serialization( const Serialization& aCopy )

{

_majorVersion = aCopy._majorVersion; _minorVersion = aCopy._minorVersion;

}

Serialization operator=( const Serialization& aCopy )

{

_majorVersion = aCopy._majorVersion; _minorVersion = aCopy._minorVersion; return *this;

}

// Accessors

void setMajorVersion( long major )

{

_majorVersion = major;

}

long getMajorVersion( void )

{

return _majorVersion;

}

void setMinorVersion( long minor )

{

_minorVersion = minor;

}

long getMinorVersion( void )

{

return _minorVersion;

}

 

 

// Functionality

 

1

bool write( const char *strFileName )

 

{

 

Implementing the Serialization Interface

357

//Note the call to the virtual method. This must

//be overridden in the derived class code.

vector< SerializeEntry >

entries;

getEntries( entries

);

 

3

if ( entries.size()

==

0

)

return false;

 

 

 

//Try opening the output file. ofstream out( strFileName,

ofstream::out | ofstream::app ); if ( out.fail() )

return false;

//Write out the class name.

out << “<” << getClassName() << “>”

<<endl;

//Write out the version informa-

tion.

out << “\t<VERSION>” << _majorVersion << “:” << _minorVersion << “</VERSION>” << endl;

vector< SerializeEntry >::iterator iter;

for ( iter = entries.begin(); iter != entries.end(); ++iter )

{

out << “\t<” << (*iter).getName().c_str() << “>” << endl;

out << “\t\t” << (*iter).getValue().c_str() << endl;

out << “\t</” << (*iter).getName().c_str() << “>” << endl;

}

out << “</” << getClassName() << “>” << endl;

return true;

}

};

#endif

This code saves a given class to an output stream in standard XML format. It would be simple enough, of course, to modify the class to output in some other format, but for simplicity we will use XML. The important member of the class is the write method, shown at 1. This method writes out the members of a class in XML format, using the member variables of the class to determine the output file and version information. Note that the class keeps track of its own version information so that you can use it to determine whether a persistent version of the class is of the proper version for your application. The version information is defined in the constructor, as shown at 2.

Take a look at the write method; it appears that the code does everything you would expect a

serialization object to do. The important part

of the function is shown at 3, which is a call

to a virtual method called getEntries

. This

method, which must be overridden by any class that uses this interface, returns the individual member variables of the class as a string array. After this method is created for your class, the rest of the functionality of the serialization interface will work no matter what the content of the derived classes.

3. Save the source file and close the file in the code editor.

As you can see, the serialization class does most of the work by itself. It requires the implementation of only two virtual methods:

The getElements method returns all internal elements of the class that you want saved.

The getClassName method returns the name of the class to use as the root of the XML tree that receives the element data we write out.

358 Technique 59: Implementing a Serialization Interface

In addition, you can specify major and minor versions for the serialized file, so that if you wish to import existing serialized files, you will later on be able to import data created by older versions of the source code and properly default missing values if new ones have been added in the meantime.

If you are persistently storing data for a class, make sure that you store version information with the data, so you can always know exactly when the data was written and what might be missing or superfluous. As classes change over time, member variables are added or removed. The data stored in a serialized version of older classes, therefore, may have additional or missing data. If you know what version of the class created the serialized data, you will know what data to map into your current member variable set.

Testing the Serialization

Interface

After we have defined the serialization interface, the next step is to implement that interface for a real class. This allows you to see how the serialization process is used and how much time the interface concept will save in future class implementations. Let’s take a look at creating a class with members that need to be stored persistently.

1. In the code editor, create a new file to hold the code for the test class of the technique.

In this example, the file is named ch59.cpp, although you can use whatever you choose. This file will contain the test code for the technique.

2. Type the code from Listing 59-2 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 59-2: TESTING THE SERIALIZATION INTERFACE

#include “serialize.h”

class MyClass

: public Serialization

{

 

private:

 

int

_iValue;

string

_sValue;

double

_dValue;

protected:

 

virtual bool getEntries( vector

 

4

< SerializeEntry >& entries )

 

{

entries.insert( entries.end(),

 

 

 

 

 

SerializeEntry( “iValue”,

 

5

 

_iValue ) );

 

 

entries.insert( entries.end(),

 

 

SerializeEntry( “sValue”,

 

 

 

_sValue.c_str() ) );

 

 

 

entries.insert( entries.end(),

 

 

 

SerializeEntry( “dValue”, _dValue

 

}

) );

 

 

 

 

 

virtual string getClassName( void )

 

 

{

return “MyClass”;

 

 

}

 

 

 

 

 

public:

 

 

 

MyClass(void)

6

{

: Serialization(1,0)

_iValue = 0; _dValue = 0.0; _sValue = “”;

}

MyClass(int iVal, double dVal, const char *sVal)

: Serialization(1,0)

{

_iValue = iVal; _dValue = dVal; _sValue = sVal;

}

virtual ~MyClass()

{

Save();

}

virtual void Save()

{

Testing the Serialization Interface

359

write( “MyClass.xml” );

}

};

int main()

{

MyClass m1(1,2.0,”One”);

return 0;

}

This class simply implements a collection of data, with a string value, an integer value, and a floating point (double) value. The important work takes place in the getEntries virtual function, which builds an array of elements to output for serialization. This method, shown at 4, overrides the serialization interface method

and will be used for output. After this method is overridden, the remainder of the code works as expected within the interface. Note the use of the SerializeEntry class (shown at 5) to hold the data for each element. Because this class can be derived to utilize other data types, the interface concept works even into the future.

3. Save the source code as a file in your editor and close the editor application.

4. Compile the source code with your favorite compiler on your favorite operating system.

5. Run the program on your favorite operating system.

If you have done everything right, the program will create an output file called MyClass.xml in your operating system’s file system. This file should contain the following output after the application is run:

$ cat MyClass.xml <MyClass>

<VERSION>1:0</VERSION>

<iValue>

1

</iValue>

<sValue>

One </sValue> <dValue>

2.000000

</dValue>

</MyClass>

In the output, you can see that all of the data elements specified in the MyClass class have been output via the serialization interface. This shows that our code works as we specified. Also note that the version information, which we specified in the constructor for the MyClass class (shown at 6), is output in the persistent file, so that we can utilize it if we add new members to the MyClass class.

As you can see, the serialization class did exactly what it was supposed to do. In addition, note that the code for the serialization class has virtually no relation to the class from which it is called. This lack of specialization allows us to easily reuse the class as an interface to as many other classes as we want — allowing each of them to serialize the data efficiently.

Also note that if you were suddenly instructed to change the output to a format other than XML, the code needs adjustment in only one place. This saves you time, avoids mistakes, and is in keeping with the best strengths of object-oriented programming.

60 Creating a Generic

Buffer Class

Technique

Save Time By

Understanding buffer overflow errors

Preventing buffer overflow errors from being exploited as a security risk

Creating a Buffer class that deals with these errors

Testing your class

Buffer overlows occur in a majority of C or C++ programs. Imagine, for a moment, reading data from an input file. The typical C or C++ code might look something like this:

char szBuffer[80]; int nPos = 0; while ( !eof(fp) )

{

szBuffer[nPos] = fgetc(fp); if ( szBuffer[nPos] == ‘|’ )

break;

nPos++;

}

This routine is supposed to read in a string from the file, reading until it hits a pipe (|) character or the end of the file. But what happens if the string is longer than 80 characters? Well, in that case, the routine continues to read in the string information, overwriting the memory locations that follow the szBuffer variable. We refer to this problem as a buffer overflow. What information is stored in those memory locations? Well, that’s hard to say: Maybe there is nothing important there — or maybe an important return address for a function is being overwritten. In the former case, you might never see a problem. In the latter case, the program could easily crash, exposing a serious vulnerability to the outside world.

Problems like this, known as buffer overflow, are really quite widespread in the software-development world — but unless they’re causing major issues (or haven’t been discovered yet), programmers tend to overlook them. Even so, buffer overflow is considered the number-one security problem in software today. Depending on the application, a buffer overflow can crash the application, or simply destroy some vital part of the security wall that prevents an outside user from modifying data within a program. So why are we not doing something about it?