From: RB on
Well thanks to the help of this group, I feel like I have a version
control and serialize paradigm that is on the way to being something
I can be comfortable with.
I have analyzed the file dump enough to know what is being written
so if I had to I could read it back in if one of MFC's Classes like
CMapStringToString's serialize ever broke. But stepping thru all of the
code that CmStS and CArchive offers shows me it is far surpassed to
anything I could come up with at this point. Experimenting shows it
handles end of file resulting from corrupt unexpected input data etc etc.
In addition I have added my own File ID value at the beginning
of the file just to check right off the bat if the file type is one
previously written.
However if anyone has the time, I would appreciate a final
critique to shoot down any areas where I may be in errant structure,
or destructive coding, since I consider the version and serialization to be
the foundation of an App, and I don't want it foo barred coming out of
the gate.
So blast away at my ignorance, I will learn from it.
Code below.

// in MyDocClass header file
.............
protected:
CMapStringToString ExpMap1;

struct VerStruct
{
CString Ver, CpyRt, Corp;
};
VerStruct VerData;

DWORD FileID;
.............

// in MyDocClass constructor

CFileHandlingDoc::CFileHandlingDoc( )
{
// Fetch stuff from included AppVer.h file that also
// feeds all App requests for version data, that idea
// courtesy of David Webber

VerData.Ver.Format( _T("Version %d.%d.%d.%d"),
VERMAJ, VERMIN, VERFIX, BUILDNUMBER );
VerData.CpyRt = _T(CPY_RIGHT_YR);
VerData.Corp = _T(CORP_NAME);
FileID = 0x1234ABCD;
}


// and in MyDocClass Serialize func

void CFileHandlingDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring( ) )
{
ar << FileID;
ar << VerData.Ver << VerData.CpyRt << VerData.Corp;
ar.SerializeClass(RUNTIME_CLASS(CMapStringToString));
ExpMap1.Serialize(ar);
}
else
{
ar >> FileID;
if (FileID != 0x1234ABCD)
{
CWnd* pWnd = AfxGetMainWnd( )->GetActiveWindow( );
pWnd->MessageBox( _T("Incorrect File format, operation aborted"),
_T("! ALERT !"), MB_OK | MB_ICONINFORMATION );
return;
}
ar >> VerData.Ver >> VerData.CpyRt >> VerData.Corp;
if (VerData.Ver.Right(7) == _T("1.0.0.0"))
{
ar.SerializeClass(RUNTIME_CLASS(CMapStringToString));
ExpMap1.Serialize(ar);
}
if (VerData.Ver.Right(7) == _T("1.0.0.1"))
{
// 1.0.0.1 stuff
}
}
}

---for any who may care or want to see the written file dump it is
below,
The 1st DWORD is my FileID of 1234ABCD (dump little endian)
The 1st line 5th byte (4th byte 0 based) is 0Fh (15d) is the length
of 1st CString, followed by the 15d bytes of string contents.
The 2nd line 5th byte is 12h (18d) is the length of the 2nd CString,
followed by the 18d bytes of string contents.
The 3rd line, 8th byte is 03 is the length of the 3rd
CString, followed by the 3 bytes ofstring contents. then the CmStS
stuff.
Which first starts with an FFFF (new class tag)i.e. different
than the MyDocClass so far(?). Then the 0000 version schema of CmStS.
Then 0012h length of the CmStS's class name, followed by the 18d
bytes if the class name, then what appears to be a count of the
total CmStS keys in the file, a value of 03. Then the value of 01
which appears to denote the size or length of the 1st key value to
follow, then the first key of "a" . Then the value of 03 again which
appears to denote the length of the assoc value which follows and
is "WWW". Then a value of 01 for the length of 2nd key followed by
the key value of "b" . Then a value of 04 for the length of the assoc
value of "ZZZZ", then the value of 01 for the length of the 3rd key
followed by the key value of "c". Then the value of 4 for the length
of the following assoc value which is "DDDD".

CD AB 34 12 0F 56 65 72 73 69 6F 6E 20 31 2E 30 ͫ4Version 1.0
2E 30 2E 30 12 43 6F 70 79 72 69 67 68 74 20 28 .0.0Copyright (
43 29 20 32 30 31 30 03 44 54 4D FF FF 00 00 12 C) 2010RBC��
00 43 4D 61 70 53 74 72 69 6E 67 54 6F 53 74 72 CMapStringToStr
69 6E 67 03 00 01 61 03 57 57 57 01 62 04 5A 5A ingaWWWbZZ
5A 5A 01 63 04 44 44 44 44 ZZcDDDD

0 1 2 3 4 5 6 7 8 9 A B C D E F zero base hex ct
1 2 3 4 5 6 7 8 9 A B C D E F 10 one base hex ct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 one base dec ct

From: Goran on
On Jun 14, 11:31 pm, "RB" <NoMail(a)NoSpam> wrote:
> // in MyDocClass header file
>  .............
> protected:
>  CMapStringToString ExpMap1;
>
>  struct VerStruct
>   {
>    CString Ver, CpyRt, Corp;
>   };
>   VerStruct VerData;
>
>   DWORD FileID;
>   .............
>
> // in MyDocClass constructor
>
> CFileHandlingDoc::CFileHandlingDoc( )
> {
>  // Fetch stuff from included AppVer.h file that also
>  // feeds all App requests for version data, that idea
>  // courtesy of David Webber
>
>    VerData.Ver.Format(  _T("Version %d.%d.%d.%d"),
>                VERMAJ, VERMIN, VERFIX, BUILDNUMBER );
>    VerData.CpyRt = _T(CPY_RIGHT_YR);
>    VerData.Corp = _T(CORP_NAME);
>    FileID = 0x1234ABCD;
>
> }
>
> // and in MyDocClass Serialize func
>
> void CFileHandlingDoc::Serialize(CArchive& ar)
> {
>  if (ar.IsStoring( ) )
>   {
>    ar << FileID;
>    ar << VerData.Ver << VerData.CpyRt << VerData.Corp;
>    ar.SerializeClass(RUNTIME_CLASS(CMapStringToString));
>    ExpMap1.Serialize(ar);
>   }
>  else
>   {
>    ar >> FileID;
>    if (FileID != 0x1234ABCD)
>     {
>      CWnd* pWnd = AfxGetMainWnd( )->GetActiveWindow( );
>      pWnd->MessageBox( _T("Incorrect File format, operation aborted"),
>                  _T("! ALERT !"), MB_OK | MB_ICONINFORMATION );
>      return;
>     }

It's a bad idea to put up a message box in the middle of
serialization. You should throw an exception with appropriate info in
it (that's what's going to happen anyhow for reasons outside your
control, and that's what MFC expects to happen).

This will cause ReportSaveLoadException to be called and error message
will be shown. Document will consequently fail to load. What you did
is, you failed to lad, inform the user, but you continued as if you
did load the document. That's a ___bad___ idea.

>    ar >> VerData.Ver >> VerData.CpyRt >> VerData.Corp;
>    if (VerData.Ver.Right(7) == _T("1.0.0.0"))
>     {
>      ar.SerializeClass(RUNTIME_CLASS(CMapStringToString));
>      ExpMap1.Serialize(ar);
>     }
>    if (VerData.Ver.Right(7) == _T("1.0.0.1"))
>     {
>      // 1.0.0.1 stuff
>     }
>   }
>
> }


Minor point: you should not be using string (text) information for
version. You have numbers anyhow, use them.

Major point: you should also not tie your executable version to these
if-s there. Look at it this way: over the time, you will add features
and bug fixes, and there will hundreds, if not thousands of them. Many/
most of them will not require change to serialization, so your if-s
are simply misguided. If you go on with this, you will repeat all
serialization code in each version. That's not how things usually work
(exception in development, before feature stabilizes). Only from time
to time, you may have significant enough changes to warrant "big"
version switches like you have there.

This is what we do at my work: we have "general" document version (a
number). Each time we change the schema, we bump that number, (as well
the schema number in the appropriate class). Each release knows it's
own "general" schema number, and so, upon load, we can say, whoops,
this file is newer, I don't support that (we only support backwards
compatibility; that works well enough, because normally new files mean
new features mean new code to run them).

e.g.

const int GENERAL_DOC_VERSION=X;

doc::Serialize()
{
load:
...
int generalDocVersion;
ar >> generalDocVersion;
if (generalDocVersion > GENERAL_DOC_VERSION)
ExceptionTooNew();
...
store:
...
ar << GENERAL_DOC_VERSION
...
}

Goran.
From: RB on
> It's a bad idea to put up a message box in the middle of serialization.
> You should throw an exception with appropriate info in it (that's
> what's going to happen anyhow for reasons outside your control, and
> that's what MFC expects to happen).
> This will cause ReportSaveLoadException to be called and error message
> will be shown. Document will consequently fail to load.

Thank you Goran, this reply is exactly what I was hoping for when I
posted. Yes, I agree and will study up on this, though still green in the
implementation of ASSERT, TRY, THROW etc. yet.

> What you did is, you failed to lad, inform the user, but you continued
> as if you did load the document. That's a ___bad___ idea.

A mute point since the context of your proposed use of exception handling
is 100% correct, but however the MessageBox did tell the user the Operation
was aborted, and there would be no loaded document for the user to work
with. Maybe I misunderstood the direction of your statement there.

> Minor point: you should not be using string (text) information for
> version. You have numbers anyhow, use them.

Well I used the string because it was already in existence (in a header file)
for setting the About dialog Statics, and additionally I thought it might be
helpful at some time, to be able to look at a file dump and actually see the
full version number.
If you would be so kind, please elaborate as to why a number would
be better than a string for this.

> Major point: you should also not tie your executable version to these
> if-s there. Look at it this way: over the time, you will add features
> and bug fixes, and there will hundreds, if not thousands of them. Many/
> most of them will not require change to serialization, so your if-s
>..............
>This is what we do at my work: we have "general" document version (a
number). Each time we change the schema, we bump that number, (as
well the schema number in the appropriate class).

Absolutely 100% correct, and the voice of experience again expands
my view from narrow to wide. In retrospect MFC did it's best to alert
me to this with the schema context of serialize but I fooed the version
into the story.
Again thanks for speeding me along over mistakes I would have had
to learn the hard way. Greatly appreciated. If you lived near I would
buy you a round of beer (unless you are like Joe and don't drink).
From: Goran on
On Jun 15, 4:20 pm, "RB" <NoMail(a)NoSpam> wrote:
> > What you did is, you failed to lad, inform the user, but you continued
> > as if you did load the document. That's a ___bad___ idea.
>
> A mute point since the context of your proposed use of exception handling
> is 100% correct, but however the MessageBox did tell the user the Operation
> was aborted, and there would be no loaded document for the user to work
> with. Maybe I misunderstood the direction of your statement there.

Yes, there was the message, but MFC continues with loading (it does
not know anything about your mesage box), which in the end means that
you seemingly start the app with document C:\does\not
\matter.yourfiletype open. But doc is actually empty, because you
didn't really load it. Now imagine that your user forgets the message
and saves what he has (an empty doc). Whoops!

> > Minor point: you should not be using string (text) information for
> > version. You have numbers anyhow, use them.
>
> Well I used the string because it was already in existence (in a header file)
> for setting the About dialog Statics, and additionally I thought it might be
> helpful at some time, to be able to look at a file dump and actually see the
> full version number.  
>    If you would be so kind, please elaborate as to why a number would
> be better than a string for this.

Look at the changes to your code to identify versions. You already
have text.Right(7) that won't work as soon as your version becomes
n.n.n.nn. Do you want to think about that (all the time, really)?
Integral number, OTOH, is an integral. x == y, that's it.

> > Major point: you should also not tie your executable version to these
> > if-s there. Look at it this way: over the time, you will add features
> > and bug fixes, and there will hundreds, if not thousands of them. Many/
> > most of them will not require change to serialization, so your if-s
> >..............
> >This is what we do at my work: we have "general" document version (a
>
> number). Each time we change the schema, we bump that number, (as
> well the schema number in the appropriate class).

Additional comment here: what we do is OK, but the "more" MFC way is
to version-enable your document class and use SerializeClass, e.g.

#define GENERAL_DOC_VERSION X

IMPLEMENT_SERIAL(
CYourDoc,
CYourDocBaseProbablyCDocument,
VERSIONABLE_SCHEMA|GENERAL_DOC_VERSION)

void CYourDoc::Serialize(...)
{
SerializeClass(GetRuntimeClass());
loading:
UINT nSchema Schema = GetObjectSchema();
if (Schema > GENERAL_DOC_VERSION)
ExceptionTooNew();
...
storing:
...
}

Notes:

* this ties serialization with your document class name, so you will
have trouble changing it
* any use of serializeClass or operator<</>> tie serialization with
your class name.
(That might be a problem when/if a major overhaul of serialization
occurs).

> Absolutely 100% correct, and the voice of experience again expands
> my view from narrow to wide. In retrospect MFC did it's best to alert
> me to this with the schema context of serialize but I fooed the version
> into the story.
> Again thanks for speeding me along over mistakes I would have had
> to learn the hard way. Greatly appreciated. If you lived near I would
> buy you a round of beer (unless you are like Joe and don't drink).

;-) Beware, I live in Belgium now, so I am picky about beer ;-)

Goran.
From: RB on
> Goran wrote:
> Yes, there was the message, but MFC continues with loading
> (it does not know anything about your mesage box), which in the
> end means that you seemingly start the app with document
> C:\does\not\matter.yourfiletype open. But doc is actually empty,
> because you didn't really load it. Now imagine that your user forgets
> the message and saves what he has (an empty doc). Whoops!

OHhhhhh! I can now see what you are talking about. He would
have overwritten what "used to be" the file persistance of the doc.
What a shallow oversight on my part. You saved me on this.

>>> Goran wrote:
>>> Minor point: you should not be using string (text) information for
>>> version. You have numbers anyhow, use them.

>> RB wrote:
>> If you would be so kind, please elaborate as to why a number would
>> be better than a string for this.

> Goran wrote:
> Look at the changes to your code to identify versions. You already
> have text.Right(7) that won't work as soon as your version becomes
> n.n.n.nn. Do you want to think about that (all the time, really)?
> Integral number, OTOH, is an integral. x == y, that's it.

Ah yes, I can see your point now, before in my limited foresight I
never envisioned a build number going beyond 1 displacement.
In other words I thought if I reached build number 9, I would change
the version to 2. But I can see the fallacy in that now. I really have
never gotten that far along with anything I write.

>>> Goran wrote:
>>> number). Each time we change the schema, we bump that number,
>>> (as well the schema number in the appropriate class).

> Goran Wrote:
> Additional comment here: what we do is OK, but the "more" MFC
> way is to version-enable your document class and use SerializeClass,
> e.g.
> #define GENERAL_DOC_VERSION X

> IMPLEMENT_SERIAL(
> CYourDoc,
> CYourDocBaseProbablyCDocument,
> VERSIONABLE_SCHEMA|GENERAL_DOC_VERSION)

> void CYourDoc::Serialize(...)
> {
> SerializeClass(GetRuntimeClass( ));
> loading:
> UINT nSchema Schema = GetObjectSchema( );
> if (Schema > GENERAL_DOC_VERSION)
> ExceptionTooNew( );
> ...
> storing:
> ...
> }

>Notes:
>* this ties serialization with your document class name, so you will
> have trouble changing it
>* any use of serializeClass or operator<</>> tie serialization with
> your class name.
> (That might be a problem when/if a major overhaul of serialization
> occurs).

Albeit I do understand what you are saying about tieing to the
serialization, this brings to bear a whole cloud that I have been confused
on (but aware of ) for some time, and I know you explained this to me
before. But I have not completely parameritized all of the logistical
operations in the macros when I would expand them. Please see
below. My input and/or questions are in the // comments
/////////////////////////////////////////////////////
// CFileHandlingDoc
IMPLEMENT_DYNCREATE(CFileHandlingDoc, CDocument)

// Results in below macro, which appears to me to create the
// MyDocDerivative object passing FFFF as the schema (which is not
// a valid schema value but the default of -1 )

#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
CObject* PASCAL class_name::CreateObject( ) \
{ return new class_name; } \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
class_name::CreateObject)

//---------------------------------------------------------------
// So I though all I had to do was set the schema from inside my doc
// as in:
CRuntimeClass* pMyDocRT = GetRuntimeClass( );
pMyDocRT->m_wSchema = 1; // set my schema
//---------------------------------------------------------------

// But if I run the IMPLEMENT_SERIAL macro on on MyDocDerivative
// after it is already in existence, isn't this going to create another occurance
// of it's CRuntimeClass ?
// I feel that I am still missing something in the structure here.

#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject) \
AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \

>> Again thanks for speeding me along over mistakes I would have had
>> to learn the hard way. Greatly appreciated. If you lived near I would
>> buy you a round of beer (unless you are like Joe and don't drink).

> ;-) Beware, I live in Belgium now, so I am picky about beer ;-)
> Goran.

Well if it is terribly expensive, how about a shot of Tequila with lime.
On second thought I owe you what ever beer you ask for.
Hopefully you will have the time and patience to respond to my
confusion above on the CRuntime ramifications..........RB