From: RB on
I have been experimenting with writing a map object to file
and reading it back. I have gotten it to work, but the document
serialize is in my FormView class can only be called from the
FILE SAVE menu (through the framework code), which is fine
but I might have need to call serialize from another location in
code based on user input to data. I did not know how to supply
the argument (CArchive& ar) so I have been searching and reading
and came up with the following which does compile and does write
the object to file.
But I am wondering is this the way one should do it or is there a
better more appropriate mfc way ?
---------------------------------------------
// prepcode for calling document serialize in ClassWhatever //
CFile FileObj;
FileObj.Open(_T("TheFilename"), CFile::modeCreate | CFile::modeWrite);
CArchive ar(&FileObj, CArchive::store);
pDoc->Serialize(ar); // doc ptr from previous code
ar.Close();
--------------------------------------------
CFile FileObj;
if( FileObj.Open(_T("TheFilename"), CFile::modeRead) == FALSE )
return;
CArchive ar(&FileObj, CArchive::load);
pDoc->Serialize(ar); // doc ptr from previous code
ar.Close();
---------------------------------------------
// serialize in my document class //
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ExpMap1.Serialize(ar); //ExpMap1 is derived from CObject
}
else
{
ExpMap1.Serialize(ar);
}
}
/// ALSO, when MS closes this news group in the coming months
/// is it going to continue it on a web forum somewhere ?


From: Goran on
On May 18, 1:00 am, "RB" <NoMail(a)NoSpam> wrote:
> I have been experimenting with writing a map object to file
> and reading it back. I have gotten it to work, but the document
> serialize is in my FormView class can only be called from the
> FILE SAVE menu (through the framework code), which is fine
> but I might have need to call serialize from another location in
> code based on user input to data.

(Note: short partial Serialization tutorial compiled with head-
compiler and debugged with head-debugger follows).

If all you want is to serialize an object, this is the simplest you
can do:

CSomeObjectWithDECLARE_SERIAL* pObj;

Store:

CFile F(NAME_HERE, CFile::modeWrite|CFile::modeCreate|
CFile::shareDenyWrite);
CArchive ar(&F, CArchive::store);
ar << pObj;

Load:

CFile F(NAME_HERE, CFile::modeRead|CFile::shareDenyWrite);
CArchive ar(&F, CArchive::load);
ar >> pObj;

(make sure that you don't have something you need in pObj prior to
loading.)

The above stores/loads an object that is on heap, along with class/
version information given my DECLARE/IMPLEMENT_SERIAL. Obviously, you
need to override Serialize in your object.

Now, in your case, the object is some map. Here's an example with a
int-to-object map:

class CMyObject : public CObject
{
public:
int member1;
CString member2;
CMyObject() : member1(0) {}
DECLARE_SERIAL(CMyObject);
void Serialize(CArchive& ar)
{
if (ar.IsStoring()) ar << member1 << member2;
else ar >> member1 >> member2;
}
};

IMPLEMENT_SERIAL(CMyObject, CObject, VERSIONABLE_SCHEMA|1);

template<> void AFXAPI SerializeElements(CArchive& ar, CMyObject**
pElements, INT_PTR nCount)
{
if (ar.IsStoring())
{
while (nCount--)
ar << *(pElements++);
}
else
{
while (nCount--)
ar >> *(pElements++);
}
}

(I presume that map holds objects on heap).

Important: SerializeElements must be "visible" by the compiler when
compiling any MFC template container that contains pointers to such
objects.

class CMyMap : public CMap<int, int, CMyObject*, CMyObject*>
{
DECLARE_SERIAL(CMyMap);
};

IMPLEMENT_SERIAL(CMyMap, CObject, VERSIONABLE_SCHEMA|1)

(then load / store as above.)

But that's not what you seem either. You actually want to save a
document at a random place. For that:

class CMyDoc : ...
{...
CMyMap* m_pMap;
void Serialize(CArhcive& ar)
{
if (ar.IsStoring()) ar << m_pMap;
else ar >> m_pMap;
}
};

.... then use OnSaveDocument. Override ReportSaveLoadException to
report any errors that happen during saving. That's it. (There's more
explanation behind what I wrote here, but serialization is not a short
subject).


Goran.

P.S. Document is not saved through operator<<, and hence no class info
nor schema version is saved. Because of that, it's a good idea to put
SerializeClass(RUNTIME_CLASS(CMyDoc)) at the top of Serialize for the
document and so one can use GetObjectSchema in a "standard" manner,
for a document. Documentation for SerializeClass speaks only about
using it for "base" class serialization, but AFAIK, there's nothing
wrong in using it to store "current" class information. That comes in
handy in the document case, and also whenever you have a non-pointer
member in a serializable class.

Goran.
From: RB on
Wow thanks for all the information. I should
have said that currently I was using a CMap object
instead of a map object. Since CMap seems to handle
much of what you told me. But none the less your
information elaborated on some areas I was curious
about, especially if I try a non mfc map class.
So to comment:

> CSomeObjectWithDECLARE_SERIAL* pObj;

Ok currently I am using CMapStringToString which
does have the DECLARE_SERIAL, but I note that if
I were deriving my own generic class from CObject
I would need the above and also IMPLEMENT_SERIAL
for the version wSchema

And I noted you added the CFile::shareDenyWrite
to the CFile modes.

So once I have fetched the ar, instead of
------------
DocPtr->Serialize(ar); // calling the document func

// I could just use the ar straight away in
ar << pExpMap1; //A CMapStringToSting*
ar.Close();

---But also more towards my scenario----

> class CMyDoc : ...
> {...
> CMyMap* m_pMap;
> void Serialize(CArhcive& ar)
> {
> if (ar.IsStoring()) ar << m_pMap;
> else ar >> m_pMap;
> }
> };
--------So then I could do this---------
.....
CMapStringToString* pExpMap1;
//Or another NonMfc class derived from CObject
.....
void CMyDoc::Serialize(CArchive& ar)
{
//allow the base class to serialize along
//with its version information
ar.SerializeClass(RUNTIME_CLASS(CBaseClass));
CBaseClass::Serialize(ar);

if (ar.IsStoring())
{
// previous method which apparently calls
// the CMapStringToSTring.Serialize function
// ExpMap1.Serialize(ar);

ar << pExpMap1 //so here I am using the ar
// fetched thru OnSaveDocument code.
}
else
{
ar >> pExpMap1
}
}
>then use OnSaveDocument. Override ReportSaveLoadException to
>report any errors that happen during saving. That's it. (There's more
>explanation behind what I wrote here, but serialization is not a short
>subject).

Of course I should have thought of this, thank you.
DocPtr->OnSaveDocument(_T("MyFileName");

------------------------------------------------
// Also Goran if you would be so kind, on the subject of
calling the base class function when you have overridden said
function in a derived class. I have read in some texts that
"the base class function should be called first, prior to calling
the overridden function".
However I was unable to decipher the context of that
statement from within the connotation used. So I ask you
when overridding the base class function in a derived class
it appears to me that whether one calls the base class func
prior to calling the overridden function would be implementation
specific, correct ?
Also when MS closes this news group in the coming months
do you know if it's going to continue it on a web forum
somewhere ?


From: Goran on
On May 18, 3:35 pm, "RB" <NoMail(a)NoSpam> wrote:
> Wow thanks for all the information. I should
> have said that currently I was using a CMap object
> instead of a map object. Since CMap seems to handle
> much of what you told me.

Yes, MFC containers do handle serialization (with the help of
SerializeElements for types you define), and due to that they seem
better than standard containers. But it's not that difficult to write
serialization code for any standard container: write/read count, write/
read each element.

> But none the less your
> information elaborated on some areas I was curious
> about, especially if I try a non mfc map class.
> So to comment:
>
> > CSomeObjectWithDECLARE_SERIAL* pObj;
>
> Ok currently I am using CMapStringToString which
> does have the DECLARE_SERIAL,> but I note that if
> I were deriving my own generic class from CObject
> I would need the above and also IMPLEMENT_SERIAL
> for the version wSchema

Yes. This is a good standard practice. It's not necessary if you don't
use CArchive << and >>, but still... I guess CMapStringToString has
no XXX_SERIAL because it's closed for modifications WRT serialization.
What else can it ever do? It serializes count, then each key/value
string. But if you derive from it, you may freely add XXX_SERIAL even
if you don't override Serialize.

>
> And I noted you added the CFile::shareDenyWrite
> to the CFile modes.

Yes. This only gives you a guarantee that no other process will write
to the file while you are accessing it. That will hardly ever happen
in reality, but nonetheless it's a precaution.

> // Also Goran if you would be so kind, on the subject of
> calling the base class function when you have overridden said
> function in a derived class. I have read in some texts that
> "the base class function should be called first, prior to calling
> the overridden function".

What, in serialization? Nah, it's irrelevant which is first, but once
you have saved files where base class data goes first, you can't
change that. But with serialization, you can't change a smallest thing
WRT the store/load order anyhow (or else, already-saved files are
doomed), so base class is tangential.

On a more general note, when documenting how a particular overridable
should be used, all is possible: don't call base, call it after your
code, call it before your code. It all depends on the context and
documentation should clarify that.

> However I was unable to decipher the context of that
> statement from within the connotation used. So I ask you
> when overridding the base class function in a derived class
> it appears to me that whether one calls the base class func
> prior to calling the overridden function would be implementation
> specific, correct ?

Yes.

> Also when MS closes this news group in the coming months
> do you know if it's going to continue it on a web forum
> somewhere ?

No idea. I hate this MS move.

Goran.
From: RB on
Thanks again, and if you have time could you comment on the
items below. I would much appreciate it.

---
( The below happens same with both fetching my own CArchive&
arg for Serialize and also letting CDocument's OnFileSave fetch it )
I checked the file contents in a hex viewer after each method.
on the first method of using the CMapStringToString.Serialize
as with
ExpMap1.Serialize(ar) //ExpMap1 is a CMapStringToString object.
it writes to file:
03 00 01 61 03 57 57 57 01 62 04 5A 5A 5A 5A 01 aWWWbZZZZ
63 04 44 44 44 44 cDDDD
-------------------------
(Note the a, b, and c are Key values, the capital letters are the
map associated stored values)
-------------------------

And then if I write the file using the
CMapStringToString* pExpMap1;
pExpMap1 = &ExpMap1;
if (ar.IsStoring())
{
ar << pExpMap1;
it writes to file:
FF FF 00 00 12 00 43 4D 61 70 53 74 72 69 6E 67 CMapString
54 6F 53 74 72 69 6E 67 03 00 01 61 03 57 57 57 ToStringaWWW
01 62 04 5A 5A 5A 5A 01 63 04 44 44 44 44 bZZZZcDDDD

-------Obviously prepending
FF FF 00 00 12 00 43 4D 61 70 53 74 72 69 6E 67 CMapString
54 6F 53 74 72 69 6E 67 ToString
to the file with this method.
So this is interesting, makes me wonder if my previous method
leaving this added data out is not good ??? I surmise the important thing is
"read" it the method I "write" it.
-----------------------------

However on a separate thread
> it's a good idea to put SerializeClass(RUNTIME_CLASS(CMyDoc))
> at the top of Serialize for the document and so one can use
> GetObjectSchema in a "standard" manner,

I cannot seem to get the correct implementation in my
// allow the base class to serialize along
// with its version information
ar.SerializeClass(RUNTIME_CLASS(CDocument));
CDocument::Serialize(ar);

because this brings up an
"unsupported operation was attempted" when called
from my ar fetch and a
"failed to save document" when called thru
the OnSaveDocument
??? I should have CDocument as the base class correct ?