Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

Serialization and MFC


APR95: Serialization and MFC

Extending MFC for cross-platform portability

Chane is the product manager for Wind/U at Bristol Technology and can be contacted at [email protected].


Serialization is the process of writing or reading one or more objects to or from a persistent-storage medium, such as a disk file, which is generally referred to as the "application database." The Microsoft Foundation Class Library (MFC) supplies built-in support for serialization in the class CObject. Thus, all classes derived from CObject can take advantage of CObject's serialization protocol.

The basic idea of serialization is that an object should be able to write its current state, usually indicated by the value of its member variables, to persistent storage. Later, the object can be re-created by reading (or "deserializing") the object's state from the storage. Serialization handles all the details of object pointers and circular references to objects that are used when you serialize an object. A key point is that the object itself is responsible for reading and writing its own state; thus, the object is responsible for implementing most of the cross-platform portability.

MFC uses an object of the CArchive class as an intermediary between the object to be serialized and the storage medium. This object is always associated with a CFile object, from which it obtains the necessary information for serialization, including the filename and whether or not the requested operation is a read or write. The object that performs a serialization operation can use the CArchive object without regard to the nature of the storage medium. A CArchive object uses overloaded insertion (<<) and extraction (>>) operators to perform writing and reading operations.

In this article, I'll examine how MFC's serialization mechanism allows objects to persist between runs of the program in a cross-platform environment. While the MFC serialization mechanism was not specifically designed to support cross-platform development, it has been extended by Microsoft and other WinAPI/MFC suppliers (including Wind/U on UNIX from Bristol Technology, the company I work for) to support a wide variety of non-Intel platforms.

Cross-Platform Issues

The big problem in designing a cross-platform application database is coping with the byte-ordering differences (Little-versus Big-endian) of various operating systems and CPUs. Since some newer CPUs allow byte ordering to be specified by the operating system, you should never assume a byte ordering based only on the CPU type. For example, when running NT on the MIPS CPU, byte ordering matches Intel x86 style; when running UNIX, byte ordering is swapped. Table 1 lists the processors on which Win32 is available, along with their byte ordering, and other CPU/OScombinations.

Little-endian byte order places the least-significant byte (LSB) first and the most-significant byte (MSB) last. In Figure 1, which shows the bit and byte layout for four bytes of data, a pointer to a 4-byte integer contains the address of the LSB of that integer (bits 0--7). Adding 1 to the pointer value causes it to point to the next-higher byte of the value (bits 8--15), and so forth.

Big-endian byte ordering places the MSB first, followed by the next MSB, and so on, with the LSB last. In Figure 2, which shows this Big-endian layout, a pointer to an integer value contains the address of the integer's MSB. The individual ordering of bits within each byte does not differ between processors. Therefore, writing a single byte of data is the same on each machine, but if you write a larger object (such as a 4-byte integer) the bytes will be swapped.

There are several ways to address the byte-ordering issue. One is to write ASCII data to tagged streams, where every item is marked with type, size, and byte-ordering information. Another option is to convert the binary data to an architecture-neutral format in the application database. Then, convert the data to an architecture-specific format in the application. In an MFC application, a variation of this architecture-neutral format can be used where the neutral format is Little-endian byte ordering. This way, no additional code is needed for Windows NT or Windows 95 applications, but UNIX or Macintosh applications rely on MFC to byte-swap data that is being read or written.

MFC Objects and Serialization

MFC supports serialization of CObject-derived objects, MFC objects not derived from CObject, and most native C++ types. Table 2 lists the MFC objects that support serialization.

On the Macintosh and UNIX RISC systems, MFC archives are byte-swapped by default. Byte-swapped archives are always kept in Little-endian order. Byte-swapping is performed whenever a WORD, DWORD, LONG, float, or double is read from or written to an archive--unless the Read or Write member functions are used. Read and Write never byte-swap. To prevent archives from being byte-swapped, use the bNoByteSwap mode flag when creating the archive; for instance, CArchive ar(pfile, CArchive::store | CArchive::bNoByteSwap);. Using this statement on UNIX or the Macintosh creates an archive that stores data in the Big-endian byte order, rather than swapping bytes and storing them in the Little-endian order.

Fundamental Types

On Little-endian architectures, the serialization functions for the fundamental types are implemented as inline functions in afx.inl; see Listing One . Thus, on Little-endian architectures, the bytes for the fundamental types are efficiently inserted or extracted into the archive buffer. Since BYTE data is not impacted by the architecture, it is always inlined.

For Big-endian architectures, most serialization functions are implemented in arccore.cpp, and the bytes must be swapped both before the data is inserted into the archive buffer and after the data is extracted from it. To improve efficiency and readability, static arrays are defined at the top of arccore.cpp to serve as temporary storage for the swapped bytes; see Listing Two .

The 16-bit WORD type is byte-swapped using the functions in Listing Three . In both functions, the statement *(WORD*)&wAfx=w; fills the static structure with the unswapped bytes. Then the bytes are swapped one at a time using the WordBits array within the static structure.

The 32-bit LONG type is byte-swapped using the functions in Listing Four , in which the DWORD CArchive operator is called, since the DWORD is also a 32-bit integer.

The 32-bit DWORD type is byte-swapped using the two functions in Listing Five . As with the WORD implementations, the statement *(DWORD*)&dwAfx=dw; is used to fill the static structure with the four unswapped bytes. Then the bytes are swapped one at a time using the DwordBits array within the static structure.

The 32-bit float type is byte-swapped using the two functions in Listing Six . Like the DWORD implementations, the statement *(float*)&fAfx=f; fills the static structure with the four unswapped bytes. Then the bytes are swapped one at a time using the FloatBits array within the static structure.

The 64-bit double type is byte-swapped using the two functions in Listing Seven . Again, the statement *(double*)&dAfx=d; fills the static structure with the four unswapped bytes. Then the bytes are swapped one at a time using the DoubleBits array within the static structure. This code allows you to create cross-platform apps with the major fundamental data types: WORD, LONG, DWORD, float, and double.

MFC Simple Value Types

Along with the fundamental data types, most framework applications use MFC-supplied simple value types; see Table 3. To archive these simple value types, the framework builds on the byte-swapped fundamental types already discussed. This layering on top of the fundamental types is the key to serializing your own objects in a cross-platform application database.

The CPoint class is derived from the POINT structure, and the archive operators are implemented for the Windows C POINT struct. Since CPoint objects are derived from POINT structures, CPoint objects are archived using the POINT operators. Listing Eight contains the portable POINT input and output operators.

The portable version of these operators simply serializes two 4-byte LONG types (x and y) instead of directly serializing eight bytes of untyped data using the CArchive Read and Write members. The implementations of RECT and SIZE are similar to POINT in Listing Nine . The portable version for RECT structures simply serializes four 4-byte LONG types; the nonportable version serializes 16 bytes of untyped data. Similarly, the portable version of SIZE serializes two 4-byte LONG types, and the nonportable version serializes eight bytes of untyped data.

At first glance, CString objects seem inherently portable, but the object contains a length that needs to be byte-swapped, and serialization must deal with the interoperability of ANSI and Unicode strings. (See arccore.cpp on the MFC source distribution for the nifty serialization implementation of CString.)

The header in a serialized CString contains information on both the length and type of the string, ANSI or Unicode. This information is encoded in the first few bytes of the archive as in Table 4. Unicode strings contain a leading 0xFF, 0xFFFe before the length information. (Unicode generally should not be used in cross-platform applications since it is not supported all on architectures.)

The CTime and CTimeSpan objects contain the time component time_t type, which is defined as a long. Thus, serializing these time objects is as easy as casting the time component to a portable fundamental data type, DWORDs.

When serializing these MFC simple value types, your application database remains cross-platform compatible. Any required byte-swapping is handled by the framework, hidden from the application developer.

MFC Collection Classes

Collection classes (including serialization), are used throughout MFC and by most developers using MFC. MFC has a wide variety of collection classes--arrays, lists, and maps--in both template-based (MFC 3.0) and non-template-based implementations. The cross-platform serialization portability of these classes varies. Table 5 enumerates the template classes, noting if they should be used for architecture-independent application databases. Note that all MFC 3.0 collection classes typecast the integer number of elements in the collection m_nSize to a 16-bit unsigned short (WORD) when archiving. Therefore, serialization of collections with more than 64K items will fail.

Template Collection Classes

Template-based collection classes introduced with MFC 3.0 can hold a variety of objects in arrays, lists, and maps. These collection classes are templates whose parameters determine the types of the objects stored in the aggregates. To make these classes serializable, the Serialize and SerializeElements helper functions have been incorporated into the template implementations. Table 6 lists the MFC template collection classes.

The CArray, CList, and CMap classes call SerializeElements to store collection elements to or read them from an archive. The default implementation of the SerializeElements helper function is not portable and does either a bitwise write from the objects to the archive or a bitwise read from the archive to the objects, depending on whether the objects are being stored in or retrieved from the archive. For a cross-platform application database, you must override SerializeElements. Listing Ten is the default, nonportable SerializeElements function from afxtempl.h.

When serializing CObject-derived objects that include IMPLEMENT_SERIAL, an implementation modeled after Listing Eleven will keep your database portable. Instead of a single untyped ar.Read or ar.Write, each object in Listing Eleven is serialized using its own serialization ability. In this case, the overridden input or output operator is used to create a portable database for each object in the array. While Listing Eleven makes sense for CArray template collections where pKids is the head of the array, it is not obvious that this could work for CList or CMap collections where there is not an array of elements. Yet, work it does! The implementations of CArray, CList, and CMap Serialize are good examples of portable Serialize functions. For a closer look on how SerializeElements can work for CList and CMap and demonstrate portable Serialize functions, see Listing Twelve and afxtempl.cpp in the MFC source directory.

The three basic steps in CArray::Serialize are:

  • Serialize the parent class, CObject, for CArray.
  • Serialize local member data, the array size m_nSize.
  • Serialize the element data using SerializeElements.
While SerializeElements is called a different number of times for each collection type (CArray=once, CList=number of items, CMap=2xthe number of items), the purpose is the same--to let you control what is archived. Thus, a cross-platform database can be created using the MFC template-based collections.

Modifying Scribble for Cross-Platform Serialization

Scribble is a sample application found in the samples/mfc/scribble/step6 directory on the MFC 3.0 distribution. Since Scribble uses the MFC serialization architecture, you can modify it for portability. The main change required is to override the SerializeElements helper function for the CArray<CPoint, CPoint> template array; see Listing Thirteen . Since the standard Scribble application does not define a SerializeElements for a CArray of CPoints, the default SerializeElements is used. It simply saves an untyped, nonportable byte stream. The locations in the CPoint structures need to be byte-swapped. Creating the SerializeElements function to serialize the CPoint array point-by-point allows for byte-swapping. While this function has no direct dependencies to Scribble, it can be added to the bottom of scripdoc.cpp.

With the addition of a single function, Scribble's application database has become a cross-platform application database. Now, scribbles created on Windows 95, Windows NT, Macintosh, and UNIX share a common database format--one that the Windows versions have already been writing. Furthermore, this format requires no changes in the MFC DLL shipped with Visual C++ on Windows.

Summary

Creating a cross-platform application database has always been a tedious task full of trade-offs. However, using the MFC serialization functions and creating a compact binary database with Little-endian byte ordering has never been easier. Not only can you create a portable database with MFC serialization on Windows with few changes to your source code, but you can also use that same portable database for UNIX and Macintosh applications.

Figure 1: Little-endian byte ordering for four bytes of data.

bits: [  7  6  5  4  3  2  1  0 ]  Byte 0    < Address
bits: [ 15 14 13 12 11 10  9  8 ]  Byte 1
bits: [ 23 22 21 20 19 18 17 16 ]  Byte 2
bits: [ 31 30 29 28 27 26 25 24 ]  Byte 3

Figure 2: Big-endian byte ordering for four bytes of data.

bits: [ 31 30 29 28 27 26 25 24 ]  Byte 0    < Address
bits: [ 23 22 21 20 19 18 17 16 ]  Byte 1
bits: [ 15 14 13 12 11 10  9  8 ]  Byte 2
bits: [  7  6  5  4  3  2  1  0 ]  Byte 3

Table 1: Byte-ordering for different CPU/OS combinations.

Processor        OS          Order   
Alpha            All         Little-endian
HP-PA            NT          Little-endian
HP-PA            UNIX        Big-endian
Intel x86        All         Little-endian
Motorola 680x0   All         Big-endian
MIPS             NT          Little-endian
MIPS             UNIX        Big-endian
PowerPC          NT          Little-endian
PowerPC          non-NT      Big-endian
RS/6000          UNIX        Big-endian
SPARC            UNIX        Big-endian

Table 2: MFC Objects that support serialization. (The initial version of MFC 3.0 for the Macintosh does not support byte-swapping in the CPoint, CRect, or CSize serialization functions.)

Object                   Win32    Macintosh UNIX  
BYTE (unsigned char)     Yes      Yes       Yes
double                   Yes      Yes       Yes
DWORD (unsigned long)    Yes      Yes       Yes
float                    Yes      Yes       Yes
LONG (long)              Yes      Yes       Yes
WORD (unsigned short)    Yes      Yes       Yes
int                      No       No        No
CObject                  Yes      Yes       Yes
CPoint                   Yes      Partial   Yes
CRect                    Yes      Partial   Yes
CSize                    Yes      Partial   Yes
CString                  Yes      Yes       Yes
CTime                    Yes      Yes       Yes
CTimeSpan                Yes      Yes       Yes

Table 3: MFC simple value types.

Function    Description       
CPoint      Encapsulates the Window POINT structure which
            contains x- and y-coordinates.
CRect       Encapsulates the Window RECT structure which
            contains left, top, right, and bottom coordinates.
CSize       Encapsulates the Window SIZE structure which
            contains a relative coordinate or position.
CString     A variable-length sequence of characters. The
            characters are of type TCHAR, which is a 16-bit
            character for Unicode and an 8-bit character
            for normal ASCII applications.
CTime       Represents an absolute time and date.
CTimeSpan   Represents a relative time span of approximately
            68 years.

Table 4: CString header information.

String length        Encoding   
Less than 255        Byte 1 contains string length.
Less than 65535      Byte 1=0xFF; bytes 2 and 3 are a WORD
                     containing the string length.
Greater than 65534   Byte 1=0xFF; bytes 2 and 3=0xFFFF;
                     bytes 4, 5, 6, and 7 are a DWORD
                     containing the string length.

Table 5: Template classes.

Type                 Portable  Reason   
CByteArray           Yes       Reads/writes byte-array bits. Since
                               the data is bytes, it is not
                               impacted by byte-swapping.
CDWordArray          Partial   Reads/writes DWORD array bits. Since
                               DWORD storage is architecture
                               dependent, data must be
                               byte-swapped.*
CObArray             Yes       Loops through all objects in the
                               array serialization, each
                               object in turn.
CPtrArray            NA        Does not support serialization.
CStringArray         Yes       Loops through all CString objects
                               in the array serialization,
                               each object in turn.
CDWordArray          Partial   Reads/writes WORD array bits. Since
                               WORD storage is architecture
                               dependent, data must be
                               byte-swapped.
CUIntArray           NA        Does not support serialization.
CObList              Yes       Loops over all CObject pointers
                               in the list serialization,
                               each object in turn. The
                               CObject-derived object is
                               responsible for portability.
CStringList          Yes       Loops through all CString objects
                               in the arrayserialization,
                               each object in turn.
CPtrList             NA        Does not support serialization.
CMapPtrToPtr         NA        Does not support serialization.
CMapPtrToWord        NA        Does not support serialization.
CMapStringToOb       Yes       Loops over all elements in the
                               map, serializing each key and
                               value individually. The CObject
                               value object relies on object's
                               serialization to the portable.
CMapStringToPtr      NA        Does not support serialization.
CMapStringToString   Yes       Loops over all elements in the
                               map, serializing each key and
                               value CString individually.
CMapWordToOb         Yes       Loops over all elements in the
                               map, serializing each key and
                               value CString individually.
CMapWordToPtr        NA        Does not support serialization.
* Check latest production documentation. The initial release of MFC 3.0 on the Macintosh did not byte-swap the array, but MFC 3 on UNIX did.

Table 6: MFC template collection classes.

Class            Description  
CArray           Stores elements in an array.
CMap             Maps keys to values.
CList            Stores elements in a linked list.
CTypedPtrList    Typesafe collection that stores pointers to
                 objects in a linked list.
CTypedPtrArray   Typesafe collection that stores pointers to
                 objects in an array.
CTypedPtrMap     Typesafe collection that maps keys to values;
                 both keys and values are pointers.

Listing One


_AFX_INLINE CArchive& CArchive::operator<<(BYTE by)
 { if (m_lpBufCur + sizeof(BYTE) > m_lpBufMax) Flush();
  *(UNALIGNED BYTE*)m_lpBufCur = by; m_lpBufCur += sizeof(BYTE); return *this;}
#ifndef _MAC || _WU_BIG_ENDIAN
_AFX_INLINE CArchive& CArchive::operator<<(WORD w)
 { if (m_lpBufCur + sizeof(WORD) > m_lpBufMax) Flush();
  *(UNALIGNED WORD*)m_lpBufCur = w; m_lpBufCur += sizeof(WORD); return *this; }
_AFX_INLINE CArchive& CArchive::operator<<(LONG l)
 { if (m_lpBufCur + sizeof(LONG) > m_lpBufMax) Flush();
  *(UNALIGNED LONG*)m_lpBufCur = l; m_lpBufCur += sizeof(LONG); return *this; }
_AFX_INLINE CArchive& CArchive::operator<<(DWORD dw)
 { if (m_lpBufCur + sizeof(DWORD) > m_lpBufMax) Flush();
  *(UNALIGNED DWORD*)m_lpBufCur=dw;m_lpBufCur += sizeof(DWORD); return *this;}
_AFX_INLINE CArchive& CArchive::operator<<(float f)
 { if (m_lpBufCur + sizeof(float) > m_lpBufMax) Flush();
  *(UNALIGNED _AFX_FLOAT*)m_lpBufCur = *(_AFX_FLOAT*)&f; 
                                     m_lpBufCur += sizeof(float); return *this;
    }
_AFX_INLINE CArchive& CArchive::operator<<(double d)
 { if (m_lpBufCur + sizeof(double) > m_lpBufMax) Flush();
  *(UNALIGNED _AFX_DOUBLE*)m_lpBufCur = *(_AFX_DOUBLE*)&d; 
                                  m_lpBufCur += sizeof(double); return *this; }
#endif
_AFX_INLINE CArchive& CArchive::operator>>(BYTE& by)
 { if (m_lpBufCur + sizeof(BYTE) > m_lpBufMax)
            FillBuffer(sizeof(BYTE) - (UINT)(m_lpBufMax - m_lpBufCur));
 by = *(UNALIGNED BYTE*)m_lpBufCur; m_lpBufCur += sizeof(BYTE); return *this; }
#ifndef _MAC || WU_BIG_ENDIAN
_AFX_INLINE CArchive& CArchive::operator>>(WORD& w)
 { if (m_lpBufCur + sizeof(WORD) > m_lpBufMax)
            FillBuffer(sizeof(WORD) - (UINT)(m_lpBufMax - m_lpBufCur));
  w = *(UNALIGNED WORD*)m_lpBufCur; m_lpBufCur += sizeof(WORD); return *this; }
_AFX_INLINE CArchive& CArchive::operator>>(DWORD& dw)
  { if (m_lpBufCur + sizeof(DWORD) > m_lpBufMax)
            FillBuffer(sizeof(DWORD) - (UINT)(m_lpBufMax - m_lpBufCur));
  dw = *(UNALIGNED DWORD*)m_lpBufCur;m_lpBufCur += sizeof(DWORD);return *this;}
_AFX_INLINE CArchive& CArchive::operator>>(float& f)
 { if (m_lpBufCur + sizeof(float) > m_lpBufMax)
            FillBuffer(sizeof(float) - (UINT)(m_lpBufMax - m_lpBufCur));
 *(_AFX_FLOAT*)&f = *(UNALIGNED _AFX_FLOAT*)m_lpBufCur; 
                                   m_lpBufCur += sizeof(float); return *this; }
_AFX_INLINE CArchive& CArchive::operator>>(double& d)
 { if (m_lpBufCur + sizeof(double) > m_lpBufMax)
            FillBuffer(sizeof(double) - (UINT)(m_lpBufMax - m_lpBufCur));
  *(_AFX_DOUBLE*)&d = *(UNALIGNED _AFX_DOUBLE*)m_lpBufCur; 
                                  m_lpBufCur += sizeof(double); return *this; }
_AFX_INLINE CArchive& CArchive::operator>>(LONG& l)
  { if (m_lpBufCur + sizeof(LONG) > m_lpBufMax)
            FillBuffer(sizeof(LONG) - (UINT)(m_lpBufMax - m_lpBufCur));
  l = *(UNALIGNED LONG*)m_lpBufCur; m_lpBufCur += sizeof(LONG); return *this; }
#endif


Listing Two


#ifdef _MAC || WU_BIG_ENDIAN
struct _AFXWORD
{
    BYTE WordBits[sizeof(WORD)];
};
struct _AFXDWORD
{
    BYTE DwordBits[sizeof(DWORD)];
};
struct _AFXFLOAT
{
    BYTE FloatBits[sizeof(float)];
};
struct _AFXDOUBLE
{
    BYTE DoubleBits[sizeof(double)];
};


Listing Three


CArchive& CArchive::operator<<(WORD w)
{
    if (m_lpBufCur + sizeof(WORD) > m_lpBufMax)
        Flush();

    if (!(m_nMode & bNoByteSwap))
    {
        _AFXWORD wAfx;
        *(WORD*)&wAfx = w;

        ASSERT(sizeof(WORD) == 2);

        BYTE* pb = m_lpBufCur;
        *pb++ = wAfx.WordBits[1];
        *pb = wAfx.WordBits[0];
    }
    else
    {
        *(WORD FAR*)m_lpBufCur = w;
    }
    m_lpBufCur += sizeof(WORD);
    return *this;
}
CArchive& CArchive::operator>>(WORD& w)
{
    if (m_lpBufCur + sizeof(WORD) > m_lpBufMax)
        FillBuffer(sizeof(WORD) - (UINT)(m_lpBufMax - m_lpBufCur));
    w = *(WORD FAR*)m_lpBufCur;
    m_lpBufCur += sizeof(WORD);

    if (!(m_nMode & bNoByteSwap))
    {
        _AFXWORD wAfx;
        *(WORD*)&wAfx = w;

        ASSERT(sizeof(WORD) == 2);

        (*(_AFXWORD*)&w).WordBits[0] = wAfx.WordBits[1];
        (*(_AFXWORD*)&w).WordBits[1] = wAfx.WordBits[0];
    }
    return *this;
}


Listing Four


CArchive& CArchive::operator<<(LONG l)
{
    ASSERT(sizeof(LONG) == sizeof(DWORD));
    return operator<<((DWORD) l);
}
CArchive& CArchive::operator>>(LONG& l)
{
    ASSERT(sizeof(LONG) == sizeof(DWORD));
    return operator>>((DWORD&) l);
}


Listing Five


CArchive& CArchive::operator<<(DWORD dw)
{
    if (m_lpBufCur + sizeof(DWORD) > m_lpBufMax)
        Flush();

    if (!(m_nMode & bNoByteSwap))
    {
        _AFXDWORD dwAfx;
        *(DWORD*)&dwAfx = dw;

        ASSERT(sizeof(DWORD) == 4);

        BYTE* pb = m_lpBufCur;
        *pb++ = dwAfx.DwordBits[3];
        *pb++ = dwAfx.DwordBits[2];
        *pb++ = dwAfx.DwordBits[1];
        *pb = dwAfx.DwordBits[0];
    }
    else
    {
        *(DWORD FAR*)m_lpBufCur = dw;
    }
    m_lpBufCur += sizeof(DWORD);
    return *this;
}
CArchive& CArchive::operator>>(DWORD& dw)
{
    if (m_lpBufCur + sizeof(DWORD) > m_lpBufMax)
        FillBuffer(sizeof(DWORD) - (UINT)(m_lpBufMax - m_lpBufCur));

    dw = *(DWORD FAR*)m_lpBufCur;
    m_lpBufCur += sizeof(DWORD);

    if (!(m_nMode & bNoByteSwap))
    {
        _AFXDWORD dwAfx;
        *(DWORD*)&dwAfx = dw;

        ASSERT(sizeof(DWORD) == 4);

        (*(_AFXDWORD*)&dw).DwordBits[0] = dwAfx.DwordBits[3];
        (*(_AFXDWORD*)&dw).DwordBits[1] = dwAfx.DwordBits[2];
        (*(_AFXDWORD*)&dw).DwordBits[2] = dwAfx.DwordBits[1];
        (*(_AFXDWORD*)&dw).DwordBits[3] = dwAfx.DwordBits[0];
    }

    return *this;
}


Listing Six


CArchive& CArchive::operator<<(float f)
{
    if (m_lpBufCur + sizeof(float) > m_lpBufMax)
        Flush();

    if (!(m_nMode & bNoByteSwap))
    {
        _AFXFLOAT fAfx;
        *(float*)&fAfx = f;

        ASSERT(sizeof(float) == 4);

        BYTE* pb = m_lpBufCur;
        *pb++ = fAfx.FloatBits[3];
        *pb++ = fAfx.FloatBits[2];
        *pb++ = fAfx.FloatBits[1];
        *pb = fAfx.FloatBits[0];
    }
    else
    {
        *(_AFXFLOAT FAR*)m_lpBufCur = *(_AFXFLOAT FAR*)&f;
    }
    m_lpBufCur += sizeof(float);
    return *this;
}
CArchive& CArchive::operator>>(float& f)
{
    if (m_lpBufCur + sizeof(float) > m_lpBufMax)
        FillBuffer(sizeof(float) - (UINT)(m_lpBufMax - m_lpBufCur));

    *(_AFXFLOAT FAR*)&f = *(_AFXFLOAT FAR*)m_lpBufCur;
    m_lpBufCur += sizeof(float);

    if (!(m_nMode & bNoByteSwap))
    {
        _AFXFLOAT fAfx;
        *(float*)&fAfx = f;

        ASSERT(sizeof(float) == 4);

        (*(_AFXFLOAT*)&f).FloatBits[0] = fAfx.FloatBits[3];
        (*(_AFXFLOAT*)&f).FloatBits[1] = fAfx.FloatBits[2];
        (*(_AFXFLOAT*)&f).FloatBits[2] = fAfx.FloatBits[1];
        (*(_AFXFLOAT*)&f).FloatBits[3] = fAfx.FloatBits[0];
    }

    return *this;
}


Listing Seven


CArchive& CArchive::operator<<(double d)
{
    if (m_lpBufCur + sizeof(double) > m_lpBufMax)
        Flush();

    if (!(m_nMode & bNoByteSwap))
    {
        _AFXDOUBLE dAfx;
        *(double*)&dAfx = d;

        ASSERT(sizeof(double) == 8);

        BYTE* pb = m_lpBufCur;
        *pb++ = dAfx.DoubleBits[7];
        *pb++ = dAfx.DoubleBits[6];
        *pb++ = dAfx.DoubleBits[5];
        *pb++ = dAfx.DoubleBits[4];
        *pb++ = dAfx.DoubleBits[3];
        *pb++ = dAfx.DoubleBits[2];
        *pb++ = dAfx.DoubleBits[1];
        *pb = dAfx.DoubleBits[0];
    }
    else
    {
        *(_AFXDOUBLE FAR*)m_lpBufCur = *(_AFXDOUBLE FAR*)&d;
    }

    m_lpBufCur += sizeof(double);
    return *this;
}
CArchive& CArchive::operator>>(double& d)
{
    if (m_lpBufCur + sizeof(double) > m_lpBufMax)
        FillBuffer(sizeof(double) - (UINT)(m_lpBufMax - m_lpBufCur));

    *(_AFXDOUBLE FAR*)&d = *(_AFXDOUBLE FAR*)m_lpBufCur;
    m_lpBufCur += sizeof(double);

    if (!(m_nMode & bNoByteSwap))
    {
        _AFXDOUBLE dAfx;
        *(double*)&dAfx = d;

        ASSERT(sizeof(double) == 8);

        (*(_AFXDOUBLE*)&d).DoubleBits[0] = dAfx.DoubleBits[7];
        (*(_AFXDOUBLE*)&d).DoubleBits[1] = dAfx.DoubleBits[6];
        (*(_AFXDOUBLE*)&d).DoubleBits[2] = dAfx.DoubleBits[5];
        (*(_AFXDOUBLE*)&d).DoubleBits[3] = dAfx.DoubleBits[4];
        (*(_AFXDOUBLE*)&d).DoubleBits[4] = dAfx.DoubleBits[3];
        (*(_AFXDOUBLE*)&d).DoubleBits[5] = dAfx.DoubleBits[2];
        (*(_AFXDOUBLE*)&d).DoubleBits[6] = dAfx.DoubleBits[1];
        (*(_AFXDOUBLE*)&d).DoubleBits[7] = dAfx.DoubleBits[0];
    }

    return *this;
}


Listing Eight


_AFXWIN_INLINE CArchive& AFXAPI operator<<(CArchive& ar, POINT point)
{
#ifndef _MAC || WU_BIG_ENDIAN
    ar.Write(&point, sizeof(POINT));
#else 
    ar << point.x << point.y;
#endif
    return ar; 
}
_AFXWIN_INLINE CArchive& AFXAPI operator>>(CArchive& ar, POINT& point)
{
#ifndef _MAC || WU_BIG_ENDIAN
    ar.Read(&point, sizeof(POINT));
#else
    ar >> point.x >> point.y;
#endif
    return ar;
}


Listing Nine


_AFXWIN_INLINE CArchive& AFXAPI operator<<(CArchive& ar, RECT rect)
{
#ifndef _MAC || WU_BIG_ENDIAN
    ar.Write(&rect, sizeof(RECT));
#else 
    ar << rect.left << rect.top << rect.right << rect.bottom;
#endif
    return ar; 
}
_AFXWIN_INLINE CArchive& AFXAPI operator>>(CArchive& ar, RECT& rect)
{
#ifndef _MAC || WU_BIG_ENDIAN
    ar.Read(&rect, sizeof(RECT));
#else
    ar >> rect.left >> rect.top >> rect.right >> rect.bottom;
#endif
    return ar;
}
_AFXWIN_INLINE CArchive& AFXAPI operator<<(CArchive& ar, SIZE size)
{
#ifndef _MAC || WU_BIG_ENDIAN
    ar.Write(&size, sizeof(SIZE));
#else 
    ar << size.cx << size.cy;
#endif
    return ar; 
}
_AFXWIN_INLINE CArchive& AFXAPI operator>>(CArchive& ar, SIZE& size)
{
#ifndef _MAC || WU_BIG_ENDIAN
    ar.Read(&size, sizeof(SIZE));
#else
    ar >> size.cx >> size.cy;
#endif
    return ar;
}


Listing Ten


template<class TYPE>
void AFXAPI SerializeElements(CArchive& ar, TYPE* pElements, int nCount)
{
    ASSERT(AfxIsValidAddress(pElements, nCount * sizeof(TYPE)));

    // default is bit-wise read/write
    if (ar.IsStoring())
        ar.Write((void*)pElements, nCount * sizeof(TYPE));  // untyped write
    else
        ar.Read((void*)pElements, nCount * sizeof(TYPE)); // untyped read
}


Listing Eleven


CStroke : public CObject { . . . };
CArray< CMyKidsObject, CMyKidsObject& > kidsArray;

void SerializeElements( CArchive& ar, CMyKidsObject* pKids, int nCount )
{
    for ( int i = 0; i < nCount; i++, pKids++ )
    {
        // Serialize each CMyKidsObject object
        if ( ar.IsStoring() )
            ar << pKids;
        else
            ar >> pKids;
    }
}


Listing Twelve


template<class TYPE, class ARG_TYPE>
void CArray<TYPE, ARG_TYPE>::Serialize(CArchive& ar)
{
    ASSERT_VALID(this);

    CObject::Serialize(ar);
    if (ar.IsStoring())
    {
        ar << (WORD) m_nSize;
    }
    else
    {
        WORD nOldSize;
        ar >> nOldSize;
        SetSize(nOldSize);
    }
    SerializeElements(ar, m_pData, m_nSize);
}


Listing Thirteen


void SerializeElements( CArchive& ar, CPoint* pPoints, int nCount )
{
    for ( int i = 0; i < nCount; i++, pPoints++ )
    {
        // Serialize each CPoint object
        if ( ar.IsStoring() )
            ar << pPoints;
        else
            ar >> pPoints;
    }
}


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.