Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MFC - Multiple inheritance and serialization

0.00/5 (No votes)
29 Oct 2003 1  
Describing a solution to allow namespaces, multiple inheritance, and serialization in an MFC program

Introduction

As a C programmer on Windows since W3.0, I found MFC to be a great wrapper for W32 API in a C++ environment. However, I also found MFC to be almost �poor� in term of C++ implementation because of certain limitations (probably inherited because compatibility with the past).

  • The first is that MFC is completely unaware of namespaces.
  • The second is that it comes with its own collections, and don�t consider STL as a convenient library for this purpose
  • The third is that MFC in not designed for multiple inheritance (certain programmers consider that insane, but they also use it when inheriting form �interface�, that are nothing more than abstract �class� or �struct� �)
  • The fourth id that it makes an exaggerate use of macros, making the use of types with composite names difficult (try to DECLARE_SERIAL a template or a class into a namespace ...)

Going through this article I propose some alternatives to MFC model for type declarations in namespaces, use of templates for serialization, safe serialization with multiple inheritance classes etc.

Background

It is important for the reader an experience in programming with MFC, and also a knowledge on MFC source code. I'm not going to teach MFC or rewrite it. I simply want to go ahead, leaving MFC to do what it is capable to do, and do something else where something more is needed.

In my samples, I�ll make use of namespaces, STL and some �helper classes�. For some of them I also wrote some independent articles. It is not required a knowledge on those articles, apart if you are looking for details. (I�ll remand you to the links where needed).

Limitations of MFC

Mfc and RTTI

In the early days of MS C++, "strange things" like templates and runtime type information where not supported by the language. So MFC, because of the need of a runtime type information mechanism, starts implementing its own.

Today, even with RTTI supported by the language this mechanism still exist, for two reasons. The first is �compatibility with the past�, and the second is �dynamic creation�: in fact what MFC does is not only RTTI, but also a way to dynamically create polymorphic objects.

Although there is a certain area of overlap, both the mechanism defect in something:

  • MFC DECLARE_SERIAL is designed explicitly for single inheritance objects, it is based on macros that makes preprocessing with type names (token pasting is done to transform a class name into the name of a static variable and into a string: it doesn�t work, for example, with templates or with composite names, like calss into namespaces)
  • RTTI is deigned to provide runtime type identification and conversion (dynamic_cast) but cannot � alone � do dynamic creation.

The good news, however, is that the two mechanism don�t conflict: enabling RTTI allow to use dynamic_cast for both single and multiple inheritance object (MFC mechanism never knows about inheritance other than the first) and CObject derivation with DECLARE/IMPLEMENT_SERIAL allows dynamic creation. But it�s up to you to manage the serialization of the bases.

This last fact creates some problem when a base is inherited more than once or is virtually inherited by more than one class.

Hence, we need to complement RTTI and MFC to support dynamic creation and serialization of multiple inheritance objects. Possibly avoiding macros.

MFC and Namespaces

Mfc classes are all defined in the global namespace.

And it�s not easy to place them into a namespace other than global: MFC code is full of function calls like �::function(...)�: move that function in a namespace and you�re stuck. However, even if thinking to let MFC where it is, and define only our classes into namespaces we still have some difficulties: MFC macros.DECLARE_SERIAL, DECLARE_MESSAGEMAP, etc. take a class name as a parameter. IMPLEMENT_SERIAL, BEGIN_MESSAGE_MAP etc. take class name and base class name as parameters. To make all this working, we have to arrange namespaces so that class names in macros will always be �simple names�.

One possible solution is here:

//  AppFrameWnd.h 

#pragma once
    namespace GE_{namespace App{
        class
        CAppFrameWnd : public CFrameWnd
        {
        public:
            CAppFrameWnd(void);
            ~CAppFrameWnd(void); 
 DECLARE_MESSAGE_MAP()
            afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); 
        };

    }} 
    
//AppframeWnd.cpp

#include "StdAfx.h"

#include "appframewnd.h"


namespace GE_{namespace App{
    BEGIN_MESSAGE_MAP(CAppFrameWnd, CFrameWnd) 
        ON_WM_CREATE() 
    END_MESSAGE_MAP()
    
    CAppFrameWnd::CAppFrameWnd(void)
    {   } 
    
    CAppFrameWnd::~CAppFrameWnd(void)
    {    } 
    
    int CAppFrameWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
    { 
        if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1;

        return 0;
    }
}}
    

This will compile correctly. But beware: all wizards tends to put all the generated code at global level, specifying the full name.

When I created the message map (by adding a WM_CREATE handler trough the properties window) the code appeared this way:

//AppframeWnd.cpp

#include "StdAfx.h"

#include "appframewnd.h"


namespace GE_{namespace App{

    CAppFrameWnd::CAppFrameWnd(void)
    {
    }

    CAppFrameWnd::~CAppFrameWnd(void)
    {
    }
}}

BEGIN_MESSAGE_MAP(GE_::App::CAppFrameWnd, CFrameWnd)
    ON_WM_CREATE()
END_MESSAGE_MAP() 

int GE_::App::CAppFrameWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if(CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1;

    return 0;
}

and compiles giving error C2327 on GE_::App::CAppFrameWnd::OnCreate

This is because BEGIN_MESSAGE_MAP doesn�t recognize the "::" as part of the name as a first parameters, and brakes all the compiler preprocessing. To get out from this situation, I removed the name qualifiers and moved the code inside the proper namespace. It should be not necessary for member functions, but I preferred a uniform coding.

Mfc and serialization

There is another problem: serialization. Imagine two classes with the same name in different namespaces, both with DECLARE/IMPLEMENT_SERIAL.

Both the classes will supply the same simple name to the macros, but classes and static members goes in different namespaces. The compiler works correctly and so the execution. But When you serialize a pointer to one of this class here comes the problem: when serializing a pointer to an object, if the object is not yet been serialized, MFC saves on file the type name of the object, in order to know, at load time, which CRuntimeClass to use to call CreateObject (the type name is used as a key).

But the �name� is ... what you write in the macros parameters! There are, in the system, two CRuntimeClass with a same m_lpszClassName. Hence, on loading, MFC can create objects confusing their types.

The proposed solution

Is in �Factory.h� and �Factory.cpp�. A set of RTTI based template classes to manage serialization, rather than macros.

What's SFactory ?!

It�s the �alter ego� of CRuntimeClass. It is a struct, containing a const type_info* (it comes from <typeinfo.h>, RTTI must be enabled) that is used to provide a type name in string form.

SFactory is chained into an internal std::list on construction and destruction, and can be found by the type-name string using the FindFactory static function. It also defines the abstract member function NewObject, but doesn�t implement it.

The full functionality are implemented in a template class, derived from SFactory, STypeFactory<E>. It�s constructor initialize the type_info of the base to point to the typeid(E), and NewObject is overridden to return new E.

Normally, you will never manipulate directly those classes, instead, you will interact with them through other classes: ESerializable and ETypeSerializable<E>.

ESerializable is a class designed to be virtually inherited form every class you may want to be serializable (virtual because your class may have various bases each of which may be itself serializable: but we need only one ESerializable instance per object).

It defines an abstract functions: virtual void Serialize(CArchive& ar) (Do have I already seen it somewhere ?) and a virtual SFactory* GetFactory() (here it comes again!), that by default returns the value of a member variable.

You will not normally take care of this class. Instead, you will take care of its derived template: ETypeSerializable<E>.

It virtually inherit ESerializable, and associate a STypeFactory<E> to the E type, setting the member variable of his base. GetFactory , so, returns the STypeFactory<E>.

Now:

  • To make a class serializable: you must derive that class from ETypeSerilizable<yourclass> as the last base class: this is necessary to make GetFactory() return the correct value (if it is an MFC class,let the first base to be an MFC base) This is the equivalent for DECLARE_SERIAL
  • To make it loadable: you must declare at global level (inside an unnamed namespace it's very good) a variable of type ETypeSerilizable<yourclass>. This is because to make the load mechanism working, at least one factory for your type must exist. Even if you've not yet loaded or created any object of yourclass. This is the equivalent for IMPLEMENT_SERIAL

Here's a sample:

// IN A HEADER (.H) FILE

class YourClass: public YourBase, public ETypeSerializable<YourClass>
//NOTE: MUST

    BE THE
    LAST BASE
{
    // your class members

};

// IN A SOURCE (.CPP) FILE

namespace {
    
    ETypeSerializable<YourClass> g_yourclassfactory;
    
}
        

When you have to serialize a pointer, just call SavePtr (on storing) and LoadPtr (on loading) passing a pointer variable.

NOTE: I didn't define operators like << and >> because they conflict with the pointer save and load operator of MFC.

Because I used templates, not macros, no problem will arise with composite names like names into namespaces or template classes.

However, if you want to make template classes serializable, remember that you need a ETypeSerilizable<yourtemplateclass<yourparameters> > for each template instantiation you have to use in your program.

For example, if this is your header

template<class T>
class YourTemplate:
    public ETypeSerializable<YourTemplate<T> >
{
    // mebers

    // functions (including Serialize)

};
        

and you are going to use YourTemplate for UINT and CString, you should, in a cpp file, at global level, create the following

namespace { //unnamed namespace 

    
    ETypeSerializable<YourTemplate<UINT> > g_yourtemplateForUint;
    ETypeSerializable<YourTemplate<CString> > g_yourtemplateForCString;

}               

Serialization maps

When saving pointers, to avoid to serialize a same object more times and keep track of circularity, I had to associate to the serialization archive some maps associating to an object a tag (and vice versa).

This is (more or less) what MFC does in it�s CArchive implementation, but those maps are designed for CObject derived, not for ... ESerializable virtually derived.

SArchiveMaps implements what is required. It must be instantiated associated to a CArchive before starting to serialize/deserialize ESerializable elements and destroyed after finished.

The simpler way to do this, in MFC application, is inside your derived CDocument::Serialize implementation: Documents, Frames and Views will always be created by MFC via CDocTemplate and CRuntimeClass. When saving and loading CDocument::Serialize is always called. So ...

void CMyDocument::Serialize(CArchive& ar)
{
    GE_::Safe::SArchiveMaps maps(ar); //will exist until return

    if(ar.IsStoring())
    {
        ar << _valueobject;
        ar << _pCobjectDerived;
        GE_::Safe::SavePtr(ar, _pESerializableDerived);
    }
    else
    {
        ar >> _valueobject;
        ar >> _pCobjectDerived;
        GE_::Safe::LoadPtr(ar, _pESerializableDerived);
    }
}        
  • _valueobject is meant to be a member that is always referred by value (like built-in types, or struct or classes for which copy and assignment are defined, and operator >> and << are defined as CArchive& oprator<<(CArchive& ar, const SValueClass& value) and CArchive& operator>>(CArchive& ar, SValueClass& value).
  • _pCObjectDerived is meant to be a pointer to CObject or to a CObject derived class, for which DECLARE_SERIAL and IMPLEMENT_SERIAL are used
  • _pESerializableDerived is meant to be a pointer to a class that derives virtually from ESerializable, because of a derivation from ETypeSerializabe<yourclass>, for which a ETypeSerializable<yourclass> global variable is also defined.

Serialize implementation

CObject and ESerializable serialization don�t conflict: you can have a class that derive from both CObject and ETypeSerilizable (a number of times), however, avoid to implement both DECLARE/IMPLEMENT_SERIAL and also ETypeSerializable instance, because you cannot � for a same object � save it sometimes in a way and sometimes in anoter: the maps that take care of objects identity are different (are embedded in CArchive for CObject* and implemented by SArchiveMaps for ESerializable*) so you risk to save your object twice and � on load � to load two different identical copies.

Apart this warning, however, there is another more serious problem: multiple bases.

Suppose class C deriving from A and B, both deriving from D and virtually from E. Suppose all these classes can exist also as stand alone classes and are all serializable (they all derive from their respective ETypeSerializable<> class).

In the following picture you can see the hierarchy diagram.

sample hierarchy chart

There are two instances of D and only one instance of E (but it is referred twice). This leads to two problems.

First: avoid to serilize the already serialized components

Suppose to implement C::Serialize by calling A::Serialize and B::Serialize.

Suppose A::Serialize calls D::Serialize and E::Serialize. And B::Serialize to call D::Serilize and E::Serilize.

Boom! Serialize C and you wiil serialize E twice.

In other words, we may have the need to control, before serializing something, if that �something� has already been serialized. But this is not the problem of circular pointers: E is not referred by A and B with a pointer.

Of course, you can think to don�t call E::Serialize from B, but what happens when B exist by itself (not as a base for C) ? You will miss it�s E component.

To solve this problem I�d also implemented in SArchiveMaps, a map whose key is formed by a pair of pointers: one to SSerializWatchDoc and the other to ESerializable. The value is an UINT and it�s just a counter.

SSerilizeMaps, that already provides a MapObject function (it is called by �Load� and �Save�, but you can call it as well in the cases that similarly requires to call CArhive::MapObject for CObject derived), also provide a MapWatchDog, taking as arguments a SSerializeWatchDog and an ESerializable.

SSerializeWatchDog, in turn, provides only one function that is

bool Locked(CArchive& ar, LPVOID pInstance).

This function retrieve the maps associated with the archive, and call MapWatchDog passing itself an the supplied LPVOID. MapWatchDog, searches (and allocate) the entry and increments the counter. Locked return true if the counter is returned as greater than 1.

Note that, being the derivations from E virtual, whatever subcomponent of C you cast to E*, you will obtain always the same pointer value (the "this" pointer for E is the same, but there are two different "this" pointers for D)

Multiple serialization of bases can be so avoided by creating a static SSerializeWatchDog variable at the beginning of a Serilize function, and returning immediately if Locked(ar, this) returns true: that means that you already executed that body (represented by the watchdog variable) over that instance (represented by the this, casted to LPVOID)

All the data structure are deleted when SArchiveMaps is destroyed. Here are samples of C B and E serializations

void C::Serialize(CArchive& ar)
{
    static SSerializeWatchDog wd;
    if(wd.Locked(ar, this)) return;
    
    A::Serialize(ar);
    B::Serialize(ar);
    
    if(ar.IsStoring())
        //save C menbers

    else
        //load C menbers

}           

void B::Serialize(BArchive& ar)
{
    static SSerializeWatchDog wd;
    if(wd.Locked(ar, this)) return;
    
    E::Serialize(ar);
    D::Serialize(ar);
    
    if(ar.IsStoring())
        //save B menbers

    else
        //load B menbers

}           

void E::Serialize(BArchive& ar)
{
    static SSerializeWatchDog wd;
    if(wd.Locked(ar, this)) return;

    if(ar.IsStoring())
        //save E menbers

    else
        //load E menbers

}           
            

In a single shot, the macro GE_SERIALIZE_CHECK_MULTIPLE() just implements the first two rows of code of the serialize functions.

Second: distinguish between component of the same type

Suppose you have two D*, pointing respectively to the two different D component of a same C object (the one in the previous picture). When saving the first pointer, the entire C is serialized, while when saving the second only a map tag should be saved.

But when loading back, we cannot load C and then convert to a D*, because this conversion will be ambiguous. For this reasons, map tag cannot be generated only for entire objects (like MFC does) but requires to be generated for every component.

The solution of the problem is to map generic LPVOIDs (the two D* are different) and create a tag for each during C serialization. This is easily done, again, by SArchiveMaps::MapWatchDog: this function is called in a Serialize body passing the �this� pointer: to solve the problem we simply call MapObject(pInstance).

Thus, to solve the multiple base problems, always in every Serialize body, define a static SSrializeWatchDog and call Locked. This will map the object and cheek if the body has been already executed for that instance. If it returns true, return immediately from that body.

If you like, use GE_SERIALIZE_CHECK_MULTIPLE(CArchive) macro. It will do itself those operations.

Sample application

As a sample for all this theory, I designed a very simple application showing the concept here treated.

Of course the application is not designed to be �beautiful�: it simply allows to create object defined as multiple inherited classes and perform serialization to and from file. The application is an MFC application linked statically to MFC, and to another static library I called �Utility�, that contains all the classes not directly pertinent to the application itself, but related to general functionality. This library can be used freely in every application you would like.

It manages object created from this inheritance hierarchy:

Application hierarchy

Green types are the ones that can be instantiated as objects. Dashed lines indicates virtual inheritance.

CIntermediate1 can hold (through a smart pointer) a CBase1 derived object, and CBase1 holds a back-pointer to a CBase3. Same thing for CBase2, CIntermediate2 and CBase3.

The existence of this member smart pointer allow to create a hierarchy of instantiated objects. The application provide commands to allow you to create them in a variety of ways, to display them and to serialize them.

The file demo.test I included in the sample contains data to produce the following object scheme when loaded.

data hierarchy sample

and the debug output, while loading the file, looks like this

. 00E18F40: class GE_::Data::CBase0: CBase0 ctor 
. 00E18D70: class GE_::Data::CBase1: CBase1 ctor 
. 00E18DB0: class GE_::Data::CBase3: CBase3 ctor 
. 00E18D70: class GE_::Data::CIntermediate1: CIntermediate1 ctor 
. 00E18E28: class GE_::Data::CBase2: CBase2 ctor 
. 00E18E68: class GE_::Data::CBase3: CBase3 ctor 
. 00E18E28: class GE_::Data::CIntermediate2: CIntermediate2 ctor 
. 00E18D70: class GE_::Data::CAssembled: CAssembled ctor 
. 00E18D70: class GE_::Data::CAssembled: CAssembled::Serialize 
.. 00E18D70: class GE_::Data::CAssembled: CIntermediate1::Serialize 
... 00E18D70: class GE_::Data::CAssembled: CBase1::Serialize 
.... 00E18F40: class GE_::Data::CAssembled: CBase0::Serialize 
... 00E18DB0: class GE_::Data::CAssembled: CBase3::Serialize 
... 00E19938: class GE_::Data::CBase0: CBase0 ctor 
... 00E19768: class GE_::Data::CBase1: CBase1 ctor 
... 00E197A8: class GE_::Data::CBase3: CBase3 ctor 
... 00E19768: class GE_::Data::CIntermediate1: CIntermediate1 ctor 
... 00E19820: class GE_::Data::CBase2: CBase2 ctor 
... 00E19860: class GE_::Data::CBase3: CBase3 ctor 
... 00E19820: class GE_::Data::CIntermediate2: CIntermediate2 ctor 
... 00E19768: class GE_::Data::CAssembled: CAssembled ctor 
... 00E19768: class GE_::Data::CAssembled: CAssembled::Serialize 
.... 00E19768: class GE_::Data::CAssembled: CIntermediate1::Serialize 
..... 00E19768: class GE_::Data::CAssembled: CBase1::Serialize 
...... 00E19938: class GE_::Data::CAssembled: CBase0::Serialize 
..... 00E197A8: class GE_::Data::CAssembled: CBase3::Serialize 
..... 00E1A244: class GE_::Data::CBase0: CBase0 ctor 
..... 00E1A160: class GE_::Data::CBase1: CBase1 ctor 
..... 00E1A1A0: class GE_::Data::CBase3: CBase3 ctor 
..... 00E1A160: class GE_::Data::CIntermediate1: CIntermediate1 ctor 
..... 00E1A160: class GE_::Data::CIntermediate1: CIntermediate1::Serialize 
...... 00E1A160: class GE_::Data::CIntermediate1: CBase1::Serialize 
....... 00E1A244: class GE_::Data::CIntermediate1: CBase0::Serialize 
...... 00E1A1A0: class GE_::Data::CIntermediate1: CBase3::Serialize 
...... 00E1A974: class GE_::Data::CBase0: CBase0 ctor 
...... 00E1A890: class GE_::Data::CBase1: CBase1 ctor 
...... 00E1A8D0: class GE_::Data::CBase3: CBase3 ctor 
...... 00E1A890: class GE_::Data::CIntermediate1: CIntermediate1 ctor 
...... 00E1A890: class GE_::Data::CIntermediate1: CIntermediate1::Serialize 
....... 00E1A890: class GE_::Data::CIntermediate1: CBase1::Serialize 
........ 00E1A974: class GE_::Data::CIntermediate1: CBase0::Serialize 
....... 00E1A8D0: class GE_::Data::CIntermediate1: CBase3::Serialize 
.... 00E19820: class GE_::Data::CAssembled: CIntermediate2::Serialize 
..... 00E19820: class GE_::Data::CAssembled: CBase2::Serialize 
..... 00E19860: class GE_::Data::CAssembled: CBase3::Serialize 
..... 00E1B340: class GE_::Data::CBase0: CBase0 ctor 
..... 00E1B170: class GE_::Data::CBase1: CBase1 ctor 
..... 00E1B1B0: class GE_::Data::CBase3: CBase3 ctor 
..... 00E1B170: class GE_::Data::CIntermediate1: CIntermediate1 ctor 
..... 00E1B228: class GE_::Data::CBase2: CBase2 ctor 
..... 00E1B268: class GE_::Data::CBase3: CBase3 ctor 
..... 00E1B228: class GE_::Data::CIntermediate2: CIntermediate2 ctor 
..... 00E1B170: class GE_::Data::CAssembled: CAssembled ctor 
..... 00E1B170: class GE_::Data::CAssembled: CAssembled::Serialize 
...... 00E1B170: class GE_::Data::CAssembled: CIntermediate1::Serialize 
....... 00E1B170: class GE_::Data::CAssembled: CBase1::Serialize 
........ 00E1B340: class GE_::Data::CAssembled: CBase0::Serialize 
....... 00E1B1B0: class GE_::Data::CAssembled: CBase3::Serialize 
....... 00E1BD30: class GE_::Data::CBase0: CBase0 ctor 
....... 00E1BB60: class GE_::Data::CBase1: CBase1 ctor 
....... 00E1BBA0: class GE_::Data::CBase3: CBase3 ctor 
....... 00E1BB60: class GE_::Data::CIntermediate1: CIntermediate1 ctor 
....... 00E1BC18: class GE_::Data::CBase2: CBase2 ctor 
....... 00E1BC58: class GE_::Data::CBase3: CBase3 ctor 
....... 00E1BC18: class GE_::Data::CIntermediate2: CIntermediate2 ctor 
....... 00E1BB60: class GE_::Data::CAssembled: CAssembled ctor 
....... 00E1BB60: class GE_::Data::CAssembled: CAssembled::Serialize 
........ 00E1BB60: class GE_::Data::CAssembled: CIntermediate1::Serialize 
......... 00E1BB60: class GE_::Data::CAssembled: CBase1::Serialize 
.......... 00E1BD30: class GE_::Data::CAssembled: CBase0::Serialize 
......... 00E1BBA0: class GE_::Data::CAssembled: CBase3::Serialize 
........ 00E1BC18: class GE_::Data::CAssembled: CIntermediate2::Serialize 
......... 00E1BC18: class GE_::Data::CAssembled: CBase2::Serialize 
......... 00E1BC58: class GE_::Data::CAssembled: CBase3::Serialize 
...... 00E1B228: class GE_::Data::CAssembled: CIntermediate2::Serialize 
....... 00E1B228: class GE_::Data::CAssembled: CBase2::Serialize 
....... 00E1B268: class GE_::Data::CAssembled: CBase3::Serialize 
.. 00E18E28: class GE_::Data::CAssembled: CIntermediate2::Serialize 
... 00E18E28: class GE_::Data::CAssembled: CBase2::Serialize 
... 00E18E68: class GE_::Data::CAssembled: CBase3::Serialize 
... 00E1CC6C: class GE_::Data::CBase0: CBase0 ctor 
... 00E1CB88: class GE_::Data::CBase2: CBase2 ctor 
... 00E1CBC8: class GE_::Data::CBase3: CBase3 ctor 
... 00E1CB88: class GE_::Data::CIntermediate2: CIntermediate2 ctor 
... 00E1CB88: class GE_::Data::CIntermediate2: CIntermediate2::Serialize 
.... 00E1CB88: class GE_::Data::CIntermediate2: CBase2::Serialize 
..... 00E1CC6C: class GE_::Data::CIntermediate2: CBase0::Serialize 
.... 00E1CBC8: class GE_::Data::CIntermediate2: CBase3::Serialize 
.... 00E1D39C: class GE_::Data::CBase0: CBase0 ctor 
.... 00E1D2B8: class GE_::Data::CBase2: CBase2 ctor 
.... 00E1D2F8: class GE_::Data::CBase3: CBase3 ctor 
.... 00E1D2B8: class GE_::Data::CIntermediate2: CIntermediate2 ctor 
.... 00E1D2B8: class GE_::Data::CIntermediate2: CIntermediate2::Serialize 
..... 00E1D2B8: class GE_::Data::CIntermediate2: CBase2::Serialize 
...... 00E1D39C: class GE_::Data::CIntermediate2: CBase0::Serialize 
..... 00E1D2F8: class GE_::Data::CIntermediate2: CBase3::Serialize 
        

Note that:

  • CBase1, CIntermediate1 and CAssembled, in a same instance have the same address, but CBase0 (that's virtual) and CBase3 (and also CBase2 and CIntermediate2 in a CAssembled) have not (they're not the first bases: having all the bases a same address is what MFC expect when creating an polimorphic object: that's why we needed separate maps)
  • In a CAssebled there are two CBase3, but they've different addresses
  • Constructors appear to be "flat" (not indented): this is because base constructor are called automatically before entering derived class constructors. Serialize calls appear to be indented because they are called in a nested way.

As I told in the beginning, I didn�t rewrite MFC. I went ahead with MFC to do something more of what MFC does, still relying on MFC for window classes, Application framework and Doc / View architecture.

May be the subject it�s not easy, but the usage of the solution, in fact should make it easy. After all, everything reduces to very few coding in your serialize functions (just add two rows or call one macro: whatever you prefer)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here