本文出处:http://old.blog.edu.cn/user2/hmf3000/archives/2006/1528512.shtml
MFC之所以为Application Framework,最重要的一个特征是它能够将管理数据的程序代码和负责数据显示的程序代码分离开来。这种能力由MFC的Document/View提供。
其实Document / View并不是什么新主意,它是MVC的一种实现,其中Model就是MFC的Document,而Controller相当于MFC的Document Template。
Windows程序不仅要做数据管理,更要做“与数据类型相对应的UI”的管理。这正正是Document / View能够实现的。
Document负责管理数据。
View负责数据的显示。
Document Frame负责管理UI,不同的数据类型可能需要不同的使用者界面(UI)。
Document Template:MFC把Document / View / Frame视为三位一体,每当使用者打开一份文件,程序应该做出Document、View、Frame各一份,这个三口组成为了一个运行单元,由所谓的Document Template掌管。
Document Template管理三口组,谁又来管理Document Template呢?答案是CwinApp。
BOOL CScribbleApp::InitInstance()
{
…
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_SCRIBTYPE,
RUNTIME_CLASS(CScribbleDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CScribbleView));
AddDocTemplate(pDocTemplate);
…
}
使用者单击[File / New]或者[File / Open],然后在View窗口内展现出来,我们很容易误以为是CwinApp直接产生Document:
BEGIN_MESSAGE_MAP(CScribbleApp, CWinApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
灰色部分正是Document Template动态产生“三位一体Document / View / Frame”的行动。关于Document Template有两个派生类,CsingleDocTemplate和CMultiDocTemplate。前者每次只能打开一个文档,后者每次可以同时打开多份文档。下面讨论主要针对CMultiDocTemplate。
当使用者单击[File/ New],根据AppWizard为我们所做的Message Map,此一命令由CwinApp::OnFileNew接手处理。后者调用CdocManager::OnFileNew,后者再调用CmultiDocTemplate::OpenDocumentFile,
class CDocTemplate : public CCmdTarget
{
UINT m_nIDResource; // IDR_ for frame/menu/accel as well
CRuntimeClass* m_pDocClass; // class for creating new documents
CRuntimeClass* m_pFrameClass; // class for creating new frames
CRuntimeClass* m_pViewClass; // class for creating new views
…
}
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible)
{
CDocument* pDocument = CreateNewDocument();
。。。
CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);
。。。
if (lpszPathName == NULL)
{
。。。
}
else
{
。。。
}
InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
return pDocument;
}
CreateNewDocment动态产生Document,CreateNewFrame动态产生Document Frame,它们利用CRuntimeClass的CreateObject进行动态创建操作。
CDocument* CDocTemplate::CreateNewDocument()
{
// default implementation constructs one from CRuntimeClass
。。。
CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
。。。
AddDocument(pDocument);
return pDocument;
}
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)
{
// create a frame wired to the specified document
CCreateContext context;
context.m_pCurrentFrame = pOther;
context.m_pCurrentDoc = pDoc;
context.m_pNewViewClass = m_pViewClass;
context.m_pNewDocTemplate = this;
。。。
CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
。。。
if (!pFrame->LoadFrame(m_nIDResource,
WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles
NULL, &context))
{
TRACE0("Warning: CDocTemplate couldn't create a frame./n");
// frame will be deleted in PostNcDestroy cleanup
return NULL;
}
return pFrame;
}
在CreateNewFrame中,不仅Frame被动态创建出来了,其对应的窗口也以LoadFrame产生出来了。调用LoadFrame,产生主窗口并加载菜单等诸元素,并指定窗口标题、文件标题、文件扩展名等。LoadFrame内部将调用CFrameWnd::Create,后者将调用CWnd::CreateEx,后者又调用::CreateWindowEx,于是触发WM_CREATE消息,从而调用CMainFrame::OnCreate,后者又调用CFrameWnd::OnCreate,后者调用CFrameWnd::OnCreateHelper,后者调用CFrameWnd::OnCreateClient,后者又调用CFrameWnd::CreateView,在此函数里面有
CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)
{
。。。
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
。。。
if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
CRect(0,0,0,0), this, nID, pContext))
{
TRACE0("Warning: could not create view for frame./n");
return NULL; // can't continue without a view
}
if (afxData.bWin4 && (pView->GetExStyle() & WS_EX_CLIENTEDGE))
{
// remove the 3d style from the frame, since the view is
// providing it.
// make sure to recalc the non-client area
ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);
}
return pView;
}
这幅图解释了CdocTemplate、Cdocument、Cview、CframeWnd之间的关系,下面则是一份文字整理:
(1) CwinApp拥有一个对象指针,CdocManager *m_pDocManager。
(2) CdocManager拥有一个指针链表CptrList m_templateList,用来维护一系列的Document Template。一个程序若支持两种文件类型,就应该有两份Document Template。应用程序应该在程序初始化的时候把这些Document Template添加到由CdocManager所维护的链表中。
(3) CdocTemplate拥有三个成员变量,分别持有Document、View、Frame的CruntimeClass指针,另有一个成员变量m_nIDResource,用来表示此Document显示时应该采用的UI对象。这四份数据应该在程序初始化中构造CdocTemplate的时候指定之,作为构造函数的参数。
(4) Cdocument有一个成员变量CdocTemplate* m_pDocTemplate,回指其Document Template;另有一个成员变量CptrList m_viewList;表示他可以同时维护的一系列的Views。
(5) CframeWnd有一个成员变量Cview* m_pViewActive,指向当前活动的View
(6) Cview有一个成员变量Cdocument* m_Document;指向相关的Document。
MFC Collection Classes的选用
n Array:数组,有次序性(需依序处理),可动态增减大小,索引值为整数。
n List:双向链表,有次序性,无索引,链表有头尾,可从头尾或者从链表的任何位置插入元素,速度极快。
n Map:又称为Dictionary,其内对象成对存在,一为健值对象(Key Object),一为实值对象(Value Object)。
MFC Collection Classes所支持的对象中,有两种特别需要说明,一是Ob,一是Ptr:
n Ob表示派生自Cobject的任何对象,MFC提供Coblist、CobArray
n Ptr表示对象指针,MFC提供CptrList、CptrArray
Template-Based Classes,Collection Class里头有一些是Template-based的,对于类型检验的功夫做得比较好
n 简单型:CArray、CList、Cmap,它们都派生自CObject,所以它们都具有文件读写,运行时类型识别,动态创建等性质。
n 类型指针型:CtypedPtrArray、CtypedPtrList和CtypedPtrMap。这些类要求你在参数中指定基类,而基类必须是MFC之中的Non-template pointer collection,例如CobList、CptrArray。你的新类将继承基类的所有性质。
台面上的Serialize操作:
FrameWork调用CscribbleDoc::Serialize,用以对付文件。
CscribbleDoc再往下调用Cstroke::Serialize,用以对付线条。
Cstroke再往下调用Carray::Serialize,用以对付点数组。
void CObList::Serialize(CArchive& ar)
{
ASSERT_VALID(this);
CObject::Serialize(ar);
if (ar.IsStoring())
{
ar.WriteCount(m_nCount);
for (CNode* pNode = m_pNodeHead; pNode != NULL; pNode = pNode->pNext)
{
ASSERT(AfxIsValidAddress(pNode, sizeof(CNode)));
ar << pNode->data; //这里将引发CArchive的重载运算符。
}
}
else
{
DWORD nNewCount = ar.ReadCount();
CObject* newData;
while (nNewCount--)
{
ar >> newData; //这里将引发CArchive的重载运算符。
AddTail(newData);
}
}
}
void CDWordArray::Serialize(CArchive& ar)
{
ASSERT_VALID(this);
CObject::Serialize(ar);
if (ar.IsStoring())
{
ar.WriteCount(m_nSize); //把数组大小写入ar
ar.Write(m_pData, m_nSize * sizeof(DWORD)); //把整个数组写入ar
}
else
{
DWORD nOldSize = ar.ReadCount(); //从ar中读出数组大小
SetSize(nOldSize); //改变m_nSize
ar.Read(m_pData, m_nSize * sizeof(DWORD)); //从ar中读出整个数组
}
台面下的Serialize写文件奥秘:
/////////////////////////////////////////////////////////////////////////////
// CDocument
BEGIN_MESSAGE_MAP(CDocument, CCmdTarget)
//{{AFX_MSG_MAP(CDocument)
ON_COMMAND(ID_FILE_CLOSE, OnFileClose)
ON_COMMAND(ID_FILE_S***E, OnFileS***e)
ON_COMMAND(ID_FILE_S***E_AS, OnFileS***eAs)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
果然S***e as就是引发了CDocument::OnFileS***eAs
void CDocument::OnFileS***eAs()
{
if (!DoS***e(NULL))
TRACE0("Warning: File s***e-as failed./n");
}
BOOL CDocument::DoS***e(LPCTSTR lpszPathName, BOOL bReplace)
{
CString newName = lpszPathName;
if (newName.IsEmpty())
{
CDocTemplate* pTemplate = GetDocTemplate();
newName = m_strPathName;
。。。
if (!AfxGetApp()->DoPromptFileName(newName, //产生对话框
bReplace ? AFX_IDS_S***EFILE : AFX_IDS_S***EFILECOPY,
OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, FALSE, pTemplate))
return FALSE; // don't even attempt to s***e
}
CWaitCursor wait;
if (!OnS***eDocument(newName))
{
。。。
}
。。。
return TRUE; // success
}
BOOL CDocument::OnS***eDocument(LPCTSTR lpszPathName)
{
CFileException fe;
CFile* pFile = NULL;
pFile = GetFile(lpszPathName, CFile::modeCreate |
CFile::modeReadWrite | CFile::shareExclusive, &fe);
。。。
CArchive s***eArchive(pFile, CArchive::store | CArchive::bNoFlushOnDelete);
s***eArchive.m_pDocument = this;
s***eArchive.m_bForceFlat = FALSE;
TRY
{
CWaitCursor wait;
Serialize(s***eArchive); // s***e me //这是一个虚函数,CScribbleDoc已改写了它
s***eArchive.Close();
ReleaseFile(pFile, FALSE);
}
。。。
return TRUE; // success
}
void CScribbleDoc::Serialize(CArchive& ar)
{
。。。
m_strokeList.Serialize(ar);
}
void CObList::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsStoring())
{
ar.WriteCount(m_nCount);
for (CNode* pNode = m_pNodeHead; pNode != NULL; pNode = pNode->pNext)
{
ar << pNode->data; //CArchive已针对 << 运算符作了重载操作。在AFX.INL中定义
}
}
。。。
}
_AFX_INLINE CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb) //事实上,该函数不属于CArchive类,CArchive类中的 operator<<是friend 类型的函数。调用的是AFX.INL中的inline函数。
{ ar.WriteObject(pOb); return ar; }
void CArchive::WriteObject(const CObject* pOb)
{
DWORD nObIndex;
// make sure m_pStoreMap is initialized
MapObject(NULL);
if (pOb == NULL)
{
。。。
}
else if ((nObIndex = (DWORD)(*m_pStoreMap)[(void*)pOb]) != 0)
// assumes initialized to 0 map
{
。。。
}
else
{
// write class of object first
CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
WriteClass(pClassRef);
// enter in stored object table, checking for overflow
CheckCount();
(*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;
// cause the object to serialize itself
((CObject*)pOb)->Serialize(*this); //虚拟多态,调用具体的对象的Serialize函数
}
}
void CArchive::WriteClass(const CRuntimeClass* pClassRef)
{
if (pClassRef->m_wSchema == 0xFFFF)
{
TRACE1("Warning: Cannot call WriteClass/WriteObject for %hs./n",
pClassRef->m_lpszClassName);
AfxThrowNotSupportedException();
}
。。。
DWORD nClassIndex;
if ((nClassIndex = (DWORD)(*m_pStoreMap)[(void*)pClassRef]) != 0)
{
// previously seen class, write out the index tagged by high bit //碰到旧类
if (nClassIndex < wBigObjectTag)
*this << (WORD)(wClassTag | nClassIndex); // #define wClassTag ((WORD)0x8000)
else
{
*this << wBigObjectTag;
*this << (dwBigClassTag | nClassIndex);
}
}
else
{
// store new class //碰到新类
*this << wNewClassTag; // #define wNewClassTag ((WORD)0xFFFF)
pClassRef->Store(*this);
。。。
}
}
void CRuntimeClass::Store(CArchive& ar) const
// stores a runtime class description
{
WORD nLen = (WORD)lstrlenA(m_lpszClassName);
ar << (WORD)m_wSchema << nLen; //存储类结构的版本,以及类名称的长度
ar.Write(m_lpszClassName, nLen*sizeof(char)); //存储类名称
}
在CScribbleApp中的Message Map中指定由CWinApp::OnFileOpen()拦截[File / Open]命令消息。
/////////////////////////////////////////////////////////////////////////////
// CScribbleApp
BEGIN_MESSAGE_MAP(CScribbleApp, CWinApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
void CWinApp::OnFileOpen()
{
ASSERT(m_pDocManager != NULL);
m_pDocManager->OnFileOpen();
}
void CDocManager::OnFileOpen()
{
// prompt the user (with all document templates)
CString newName;
if (!DoPromptFileName(newName, AFX_IDS_OPENFILE, //产生对话框
OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))
return; // open cancelled
AfxGetApp()->OpenDocumentFile(newName);
// if returns NULL, the user has already been alerted
}
CDocument* CWinApp::OpenDocumentFile(LPCTSTR lpszFileName)
{
ASSERT(m_pDocManager != NULL);
return m_pDocManager->OpenDocumentFile(lpszFileName); //本来这些操作都是由APP来做,不过自从MFC4.0以后由DocManager隔离开来自己负责。
}
CDocument* CDocManager::OpenDocumentFile(LPCTSTR lpszFileName)
{
// find the highest confidence
POSITION pos = m_templateList.GetHeadPosition();
CDocTemplate::Confidence bestMatch = CDocTemplate::noAttempt;
CDocTemplate* pBestTemplate = NULL;
CDocument* pOpenDocument = NULL;
TCHAR szPath[_MAX_PATH];
//从Document Template链表中找出最适当的template,放到pBestTemplate中。
。。。
while (pos != NULL)
{
CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetNext(pos);
ASSERT_KINDOF(CDocTemplate, pTemplate);
CDocTemplate::Confidence match;
ASSERT(pOpenDocument == NULL);
match = pTemplate->MatchDocType(szPath, pOpenDocument);
if (match > bestMatch)
{
bestMatch = match;
pBestTemplate = pTemplate;
}
if (match == CDocTemplate::yesAlreadyOpen)
break; // stop here
}
。。。
//找到了最佳的Template
return pBestTemplate->OpenDocumentFile(szPath);
}
由于CMultiDocTemplate改写了OpenDocumentFile接口,所以调用的是CMultiDocTemplate::OpenDocumentFile,其中CDocTemplate:: OpenDocumentFile是个纯虚函数。
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
BOOL bMakeVisible)
{
CDocument* pDocument = CreateNewDocument(); //详细代码在第3页。
。。。
CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL); //详细代码在第3页。
。。。
if (lpszPathName == NULL)
{
// create a new document - with default document name
。。。
}
else
{
// open an existing document
CWaitCursor wait;
if (!pDocument->OnOpenDocument(lpszPathName)) //OnOpenDocument是个虚函数
{
。。。
}
pDocument->SetPathName(lpszPathName);
}
InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
return pDocument;
}
BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName)
{
CFileException fe;
CFile* pFile = GetFile(lpszPathName,
CFile::modeRead|CFile::shareDenyWrite, &fe);
DeleteContents();
SetModifiedFlag(); // dirty during de-serialize
CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete);
loadArchive.m_pDocument = this;
loadArchive.m_bForceFlat = FALSE;
TRY
{
CWaitCursor wait;
if (pFile->GetLength() != 0)
Serialize(loadArchive); // load me //虚函数,CScribbleDoc改写了Serialize,所以调用的是CScribbleDoc::Serialize
loadArchive.Close();
ReleaseFile(pFile, FALSE);
}
。。。
return TRUE;
}
void CObList::Serialize(CArchive& ar)
{
CObject::Serialize(ar);
if (ar.IsStoring())
{
。。。
}
else
{
DWORD nNewCount = ar.ReadCount();
CObject* newData;
while (nNewCount--)
{
ar >> newData; //CArchive已针对 >> 运算符作了重载操作。在AFX.INL中定义
AddTail(newData);
}
}
}
在AFX.INL中:
_AFX_INLINE CArchive& AFXAPI operator>>(CArchive& ar, CObject*& pOb)
{ pOb = ar.ReadObject(NULL); return ar; }
//事实上,该函数不属于CArchive类,CArchive类中的 operator>>是friend 类型的函数。调用的是AFX.INL中的inline函数。
CObject* CArchive::ReadObject(const CRuntimeClass* pClassRefRequested)
{
。。。
// attempt to load next stream as CRuntimeClass
UINT nSchema;
DWORD obTag;
CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);
// check to see if tag to already loaded object
CObject* pOb;
if (pClassRef == NULL)
{
。。。
}
else
{
// allocate a new object based on the class just acquired
pOb = pClassRef->CreateObject(); //IMPLEMENT_SERIAL宏使得此函数有效(new className)。
// Add to mapping array BEFORE de-serializing
CheckCount();
m_pLoadArray->InsertAt(m_nMapCount++, pOb);
// Serialize the object with the schema number set in the archive
UINT nSchemaS***e = m_nObjectSchema;
m_nObjectSchema = nSchema;
pOb->Serialize(*this);
m_nObjectSchema = nSchemaS***e;
ASSERT_VALID(pOb);
}
return pOb;
}
CRuntimeClass* CArchive::ReadClass(const CRuntimeClass* pClassRefRequested,
UINT* pSchema, DWORD* pObTag)
{
// read object tag - if prefixed by wBigObjectTag then DWORD tag follows
DWORD obTag;
WORD wTag;
*this >> wTag;
。。。
if (wTag == wNewClassTag) //#define wNewClassTag ((WORD)0xFFFF)
{
// new object follows a new class id
if ((pClassRef = CRuntimeClass::Load(*this, &nSchema)) == NULL)
AfxThrowArchiveException(CArchiveException::badClass, m_strFileName);
。。。
}
else
{
// existing class index in obTag followed by new object
DWORD nClassIndex = (obTag & ~dwBigClassTag);
if (nClassIndex == 0 || nClassIndex > (DWORD)m_pLoadArray->GetUpperBound())
AfxThrowArchiveException(CArchiveException::badIndex,
m_strFileName);
//判断nClassIndex为旧类,于是从类别型录网中取出其CRuntimeClass,放在pClassRef中。
pClassRef = (CRuntimeClass*)m_pLoadArray->GetAt(nClassIndex);
。。。
}
。。。
return pClassRef;
}
CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum)
// loads a runtime class description
{
WORD nLen;
char szClassName[64];
CRuntimeClass* pClass;
WORD wTemp;
ar >> wTemp; *pwSchemaNum = wTemp; //读出类结构的版本号
ar >> nLen; //读出类名称的长度
if (nLen >= _countof(szClassName) ||
ar.Read(szClassName, nLen*sizeof(char)) != nLen*sizeof(char)) //把类的名字读进来
{
return NULL;
}
szClassName[nLen] = '/0';
。。。
AfxLockGlobals(CRIT_DYNLINKLIST);
for (CDynLinkLibrary* pDLL = pModuleState->m_libraryList; pDLL != NULL;
pDLL = pDLL->m_pNextDLL)
{
for (pClass = pDLL->m_classList; pClass != NULL;
pClass = pClass->m_pNextClass)
{
if (lstrcmpA(szClassName, pClass->m_lpszClassName) == 0)
{
AfxUnlockGlobals(CRIT_DYNLINKLIST);
return pClass;
}
}
}
。。。
}
至此Document的Serialize机制基本上有个总体的清晰的了解。
下面再将三宏的内容给大家看看:
DECLARE_DYNAMIC
#define DECLARE_DYNAMIC(class_name) /
protected: /
static CRuntimeClass* PASCAL _GetBaseClass(); /
public: /
static const AFX_DATA CRuntimeClass class##class_name; /
virtual CRuntimeClass* GetRuntimeClass() const; /
DECLARE_DYNCREATE
#define DECLARE_DYNCREATE(class_name) /
DECLARE_DYNAMIC(class_name) /
static CObject* PASCAL CreateObject();
DECLARE_SERIAL
#define DECLARE_SERIAL(class_name) /
_DECLARE_DYNCREATE(class_name) /
AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
_IMPLEMENT_RUNTIMECLASS
#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) /
AFX_DATADEF CRuntimeClass class_name::class##class_name = { /
#class_name, sizeof(class class_name), wSchema, pfnNew, /
RUNTIME_CLASS(base_class_name), NULL }; /
CRuntimeClass* class_name::GetRuntimeClass() const /
{ return RUNTIME_CLASS(class_name); } /
IMPLEMENT_DYNAMIC
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) /
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)
IMPLEMENT_DYNCREATE
#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)
IMPLEMENT_SERIAL
#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; } /
一个类要是能够Serialize,此类应加上一个default构造函数。因为若一个对象来自文件,MFC必须先动态创建它,而且在没有任何参数的情况下调用其构造函数,然后才能从文件中读取对象的数据。