Solved

use of wSchema in IMPLEMENT_SERIAL

Posted on 1999-01-19
1
947 Views
Last Modified: 2013-11-20
I'd like to know how to use the schema number in in IMPLEMENT_SERIAL. I'd like to use it to be able to read files of various versions, converting each version to the latest version, e.g., read (serialize) a CArchive object. If one of the objects is a prior version, read it the old way, then convert to the latest format, the store it using the latest format.

My documentation says only that a CArchiveException will be thrown when the framework encounters an object with a schema mismatch. I don't know how to recover and 'reread' the object (to convert its format) in the CATCH block.

Any ideas?
0
Comment
Question by:ALBEARD
1 Comment
 

Accepted Solution

by:
ramrocket earned 100 total points
ID: 1327986
Here is an article that will solve your problem from MS Developers Journal.  

Good Luck!
ramrocket

Mismatched schemas

If a file is loaded containing an original CChild object, with its old schema value 0x0099, MFC uses the extraction operator and detects that the old schema value is different from the current code and throws a CArchiveException.

It is a good idea to catch a CArchiveException in the object's Serialize() implementation. This locates the source of a version incompatibility immediately:

if (ar.IsLoading())
{
      try
      {
            ar >> pChild;
      }
      catch(CArchiveException* e)
      {
            if (CArchiveException::badSchema == e->m_cause)
            {
                  // Incompatible schema number!
                  TRACE("Bad schema at %s(%d)\n", __FILE__, __LINE__);
                  // handle accordingly...
            }
            else
            {
                  // hmm, another cause...
            }
      }
}
 


Versionable objects


The basic schema technique is adequate for objects that do not exist in multiple versions. There are two solutions to supporting backward-compatible objects: implement your own versioning scheme or use MFC's VERSIONABLE_SCHEMA mechanism:


MFC versionable objects are easily defined. Just override the VERSIONABLE_SCHEMA macro with your schema number in IMPLEMENT_SERIAL:

      IMPLEMENT_SERIAL(CChild, CObject, VERSIONABLE_SCHEMA | 0x009A)
 


This deceptively simple change makes your objects backward compatible. CChild::Serialize() can now be rewritten as follows:

void CChild::Serialize(CArchive& ar)
{
      CObject::Serialize(ar);            // remember to do this
      if (ar.IsStoring)
      {
            ar << m_dwData;
            ar << m_wId;
      }
      else      // IsLoading()
      {
            switch (ar.GetObjectSchema())
            {
                  case 0x0099:      // load old version
                        ar >> m_dwData;
                        break;
                  case 0x009A:      // load current version
                        ar >> m_dwData;
                        ar >> m_wId;
                        break;
                  default:            // Ouch! Corrupted file!
                        // handle error...
            }
      }
}
 


Note the addition of the CArchive::GetObjectSchema() call. This function returns the schema for the current object.

There is a caveat with GetObjectSchema(): you may call it only once during the serialization of an object; subsequent calls return -1. Therefore, you can't use VERSIONABLE_SCHEMA to make versionable class hierarchies. The following example illustrates this point.

class CBase : public CObject { ... };
class CChild : public CBase { ... };

// Note: base and child classes have different schema numbers
IMPLEMENT_SERIAL(CBase, CObject, VERSIONABLE_SCHEMA | 0x000A);
IMPLEMENT_SERIAL(CChild, CBase, VERSIONABLE_SCHEMA | 0x000B);

void CBase::Serialize(CArchive& ar)
{
      if ( ar.IsLoading() )
      {
            UINT nSchema = ar.GetObjectSchema();      // returns 0x000A
            // load versionable object...
      }
}

void CChild::Serialize(CArchive& ar)
{
      CBase::Serialize(ar);      // call base class Serialize()
      if ( ar.IsLoading() )
      {
            UINT nSchema = ar.GetObjectSchema();      // returns -1
            // second call to GetObjectSchema() FAILS
      }
}
 

The call to GetObjectSchema() in the child class's Serialize() fails because it was already called in the base class code.

Work around this caveat by implementing your own versioning. The following code shows one simple method:

class CBase : public CObject { enum { verBase = 0x0A }; ... };
class CChild : public CBase { enum { verChild = 0x0B }; ... };

void CBase::Serialize(CArchive& ar)
{
      CObject::Serialize(ar);
      if ( ar.IsStoring() )
      {
            ar << verBase;            // explicitly save version
            // ... then save everything else
      }
      else
      {
            UINT nMySchema;            // load version number
            ar >> nMySchema;
            switch ( nSchema )
            {
                  case verBase:      //load current version of object
                        // load CBase data...
                  // add cases for older versions
            }
      }
}

void CChild::Serialize(CArchive& ar)
{
      CBase::Serialize(ar);
      if ( ar.IsStoring() )
      {
            ar << verChild;            // explicitly save version
            // ... then save everything else
      }
      else
      {
            UINT nMySchema;            // load version number
            ar >> nMySchema;
            switch ( nSchema )
            {
                  case verChild:      //load current version of object
                        // load CChild data...
                  // add cases for older versions
            }
      }
}
 


Another GetObjectSchema() caveat: Calling GetObjectSchema() within a direct call to Serialize() will generate a CArchiveException during load. The archiving operators must be used for the MFC version control to work.

It is possible to handle different schema numbers within a class hierarchy without resorting to custom versioning. CArchive contains a method SerializeClass(), which can be used to serialize base class information inside a Serialize() function:

void CChild::Serialize(CArchive& ar)
{
      // serialize base class version and run-time class info
      ar.SerializeClass(RUNTIME_CLASS(CBase));
      // serialize base class data
      CBase::Serialize(ar);
      // etc.
}
 

0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Introduction: Finishing the grid – keyboard support for arrow keys to manoeuvre, entering the numbers.  The PreTranslateMessage function is to be used to intercept and respond to keyboard events. Continuing from the fourth article about sudoku. …
Introduction: Database storage, where is the exe actually on the disc? Playing a game selected randomly (how to generate random numbers).  Error trapping with try..catch to help the code run even if something goes wrong. Continuing from the seve…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

14 Experts available now in Live!

Get 1:1 Help Now