📄 13.3.2 mfc框架对serialize函数的调用过程.txt
字号:
13.3.2 MFC框架对Serialize函数的调用过程
另外,在Graphic程序运行后,当单击【文件_保存】和【文件_打开】菜单项时都会出现一个对话
框。但是在CGraphicDoc类中并没有看到相应的菜单命令响应函数,因此我们可以推断这些命令响应
函数一定也是在MFC框架内部实现的。我们可以跟踪【文件_打开】命令的消息响应函数,同时查看
MFC框架对CGraphicDoc类的Serialize函数的调用过程。
同样,在Microsoft提供的MFC源文件: APPDLG.CPP中,可以看到OnFileOpen也是CWinApp的一个成员
函数。井且按照上面查看CWinApp类的OnFileNew函数定义的方法一样,可以发现这个OnFileOpen函
数的声明前面有 afx_msg标识符,说明这个函数确实是菜单命令消息响应函数。该函数的实现代码
如例 13-13所示。
例 13-13
void CWinApp: :OnFileOpen()
{
ASSERT(m_pDocManager != NULL);
m_pDocManager->OnFileOpen();
可以看到, CWinApp类的 OnFileOpen函数接着调用了文档管理器的 OnFileOpen函数,该函数的定
义位于 MFC源文件: DOCMGR. CPP文件中,定义代码如例 13-14所示。
例3-14
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
可以看到,在 CDocManager类的 OnFileOpen函数中调用了一个函数: DoPrompt FileName,从其函
数名大概可以猜到这是一个显示"文件打开/保存"对话框的函数(读者先在此函数调用处设置一个断
点)。该函数的实现代码如例 13-15所示。
例 13-15
BOOL CDocManager : : DoPromptFileName (CString& fileName , UINT nIDSTitle, DWORD lFlags ,
BOOL bOpenFileDialog, CDocTemplate* pTemplate) { CFileDialog dlgFile(bOpenFileDialog);
CString title;
VERIFY(title . LoadString(nIDSTitle)) ;
dlgFile.m_ofn.Flags 1= lFlags ;
CString strFilter;
CString strDefault;
if (pTemplate != NULL)
{
ASSERT_ VALID(pTemplate) ;
_AfxAppendFilterSuffix(strFilter, dlgFile . m一ofn, pTemplate , &str Default) ;
else
11 do for all doc template
POSITION pos = m_templateList.GetHeadPosition();
BOOL bFirst = TRUE ;
while (pos != NULL)
{ CDocTemplate* pTemplate = (CDocTemplate*) m_templateList. GetNext
500 I如......
(pos) ; _Af xAppendFilterSuffix(strF工 lter, dlgFile.m_ofn, pTemplate, bFirst ?
&strDefault : NULL); bFirst = FALSE;
.
1/ append the "*.*" all files filter
CString allFilter;
VER工 FY(allFilter.LoadString(AFX_IDS_ALLFILTER));
strFilter += al工 Filter;
strFilter += (TCHAR)'\0'; // next string please
strFilter += _T("*.*");
strFilter += (TCHAR) , \0' ; / / last str工ng
dlgFile.m_ofn.nMaxCustFilter++;
dlgFile.m一ofn.lpstrFilter = strFilter; dlgFile.m_ofn.lpstrTitle = title;
dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH);
int nResult = dlgFile.DoModal();
fileName.ReleaseBuffer();
return nResult工 DOK;
可以看到,在 CDocManager类的 DoPromptFileName函数中构造了一个 CFileDialog对象: dlgFile.
由此我们可以知道原来 MFC框架也是利用 CFileDialog这个类来显示"文件打开"对话框的。
让我们回到上述如例 13-14所示 CDocManager类的 OnFileOpen函数,可以看到,接下来,该函数调
用 AfxGetApp函数获得应用程序 (CWinApp)类对象指针,并利用此指针调用 CWinApp对象的
OpenDocumentFile函数。我们在此行代码处也设置一个断点。而 CWinApp类的 OpenDocumentFile
定义位于 AppUl.cpp文件中,代码如例 13-16所示。
例 13-16
CDocument* CWinApp: :OpenDocumentFile(LPCTSTR lpszFileName)
{
ASSERT(m-pDocManager != NULL);
return m-pDocManager->OpenDocumentFile(lpszFileName);
可以看到. CWinApp类的 OpenDocumentFile函数实际上就是去调用 CDocManager类的
OpenDocumentFile函数。后者位于 DOCMGR.CPP文件中,代码如例 13-17所示。
CDocument* CDocManager::OpenDocumentFile(LPCTSTR lpszFileName) {
11 find the highest confidence
POSITION pos = m_templateList . GetHeadPos工 tion() ;
CDocTemplate ::Confidence bestMatch = CDocTemplate : : noAttempt ;
CDocTemplate* pBestTemplate = NULL ;
( CDocument* pOpenDocument =阳LL ;
TCHAR szPath[_MAX_ PATH] ;
ASSERT(lstrlen(lpszFileName) < _countof(szPath)) ;
TCHAR szTernp[_MAX_ PATH] ;
if (lpszFileNarne[O] , \ 11
I )
++lpszFileName ;
lstrcpyn(szTemp , lpszFileNarne, _MAX_PATH) ;
LPTSTR lpszLast = _tcsrchr(szTemp , \ " , ) ;
if (lpszLast ! = NULL)
*lpszLast = 0 ; AfxFullPath (szPath , szTemp) ; TCHAR szLinkName[_MAX_PATH] ; if
(AfxResolveShortcut(AfxGetMainWnd() , szPath, szL工nkNarne, MAX
PATH) ) lstrcpy(szPath, szLinkName) ;
while (pos ! = NULL)
CDocTemplate* pTemplate = (CDocTemplate*)m_ternplateList .GetNext (pos) ;
ASSERT_KINDOF(CDocTernplate , pTemplate) ;
CDocTemplate : : Confidence match;
ASSERT( p OpenDocument NULL) ;
( rnatch = pTernpl ate->MatchDocType (szPath, pOpenDocument);
if (match > bestMatch)
bestMatch = match;
pBestTernplate = pTernplate ;
}
if (match CDocTemplate : : yesAlreadyOpen) break; /1 stop here
if (pOpenDocument != NULL)
{ POSITION pos = pOpenDocument->GetFirstViewPosition() ; if (pos ! = NULL)
{
CView* pView =pOpenDocument->GetNextView(pos) ; 11 get first one
ASSERT_ VALID(pView) ;
CFrarneWnd* pFrarne = pView->GetParentFrarne() ;
if (pFrarne != NULL)
pFrame->ActivateFrame() ; else TRACEO ("Error: Can not find a frame for document to
activate.
\n" ) ;
CFrameWnd* pAppFrame;
if (pFrame != (pAppFrame = (CFrameWnd*)AfxGetApp()-> m-pMainWnd))
{
ASSERT_ KINDOF(CFrameWnd, pAppFrame); pAppFrame->ActivateFrame() ;
}
else
TRACEO ( "Error : Can not f工 nda飞riew for document to acti vate . \n ;
u )
( return pOpenDocument;
if (pBestTemplate NULL)
{
AfxMessageBox(AFX_IDP_FA ILED_T。一 OPEN_DOC) ; return NULL;
@D return pBestTemplate->OpenDocumentFile(szPath);
读者先在 CDocManager类的 OpenDocumentFile函数开始处设置一个断点。然后阅读此函数的实现代
码,可以看到,在此函数中定义了一个 CDocument类型的指针变量: pOpenDocument (.位置处的代
码),并在后面有一个函数调用: MatchDocType (.位置处的代码),该函数将 pOpenDocument指针变
量作为参数传递进去了。我们在此行代码设置一个断点。在该代码的最后利用文档模板的指针调用
OpenDocumentFile (.位置处的代码)。因为本程序是一个单文档类型的程序,所以这里实际上调用
的是单文档模板类 ( CSingleDocTemplate )的 OpenDocumentFile函数。该函数的代码可参见本章
前面的内容,可以看到在该函数中,对 IpszPathName变量进行判断的 else子句块下面调用了文档
对象的 OnOpenDocument函数(上面 CSingleDocTemplate类 OpenDocumentFile函数的 .位置处的代
码)。这个函数的定义位于 DocCore.cpp文件中,代码如例 13-18所示。
例 13-18
BOOL CDocument: :OnOpenDocument(LPCTSTR lpszPathName)
if (IsModified()) TRACEO ( "Warning : OnOpenDocument replaces an unsaved document . \ n
11 ) i
CFileException fe ; ( CFile* pFile = GetFile(lpszPathName ,
" ‘ I 503
第13
CFile::rnodeReadICFile::shareDenyWrite, &fe);
if (pFile NULL)
{
ReportSaveLoadExcept工O且(lpszPathNarne, &fe , FALSE , AFX_IDP_FAILED_TO一OPEN_DOC) ;
return FALSE;
DeleteContents() ;
SetModifiedFlag(); // dirty during de-serialize
( CArchive loadArchive(pFile , CArchive ::load CArchive::bNoFlushOn
Delete) ; loadArchive.rn-pDocurnent = this; loadArchive . rn_bForceFlat = FALSE; TRY {
CWaitCursor wait;
if (pFile->GetLength() != 0)
③ Serialize(loadArchive); // load rne
loadArchive . Close();
ReleaseFile(pFile , FALSE);
}
CATCH_ALL(e)
{
ReleaseF工le (pFile , TRUE);
DeleteContents() ; // rernove fa工led contents
TRY
ReportSaveLoadException(lpszPathNarne , e ,
FALSE , AFX_IDP_FAILED_TO_OPEN_DOC); } END TRY DELETE_EXCEPTION(e) ; return FALSE;
END CATCH ALL
SetModifiedFlag(FALSE); // start off with unrnod工fied
return TRUE;
这时己经进入到文档基类 (CDocument)中了。在CDocument类的OnOpenDocument函数中,根据得到的
文件名,构造一个 CFile对象 ce位置处的代码),然后利用此对象指针构造一个CArchive对象c(.
位置处的代码),随后有一个Serialize函数的调用 ce位
置处的代码〉。可以在此调用处设置一个断点。
调试运行 Graphic程序,当程序界面出现后,选择【文件飞打开】菜单项,程序进入 CWinApp类的
OnFileOpen函数,该函数将调用CDocManager类的OnFi1eOpen函数。继续运行,程序进入CDocManager
类的DoPromptFileName函数,该函数的作用就是弹出一个文件打开(或保存)对话框。继续运行,程
序就会弹出一个文件打开对话框,从中选择一个文件,例如 Graphic.txt文件并打开。然后程序调
用 CWinApp类的 OpenDocmentFile函数。继续运行,程序回到CDocManager类的OpenDocmentFile函
数。继续运行,程序进入 CSingleD∞Template类的OpenDocumentFile函数。在此函数中,调用文档
对象的 OnOpenDocument函数。因此,程序进入到CDocument类的OnOpenDocument函数,在该函数中,
首先构造CFile对象,接着构造CArchive对象,之后调用Serialize函数,这是一个虚函数,根据多
态性原则,它调用就是子类: CGraphicDoc类的Serialize函数。继续运行,就可以看到程序进入了
CGraphicDoc类的Serialize函数。因为此时是从文件加载数据,所以进入该函数中的else分支。继
续运行程序,即可从弹出的消息框中看到读取的数据。上述过程如图 13.14所示。
文件打开菜单命令
图 13.14 文件打开菜单命令响应过程
刚才我们看到一个现象,就是当保存数据后,在不关闭程序窗口的情况下,再次打开同一个文件时,
程序并不会进入Serialize函数中。读者可以再次调试运行Graphic程序,
先单击【文件_保存】菜单项,并选择一个文件,例如Graphic.txt文件保存数据。然后单击【文件
_打开】菜单项,这时程序会依次进入CWinApp类的OnFileOpen函数、 CDocManager类的OnFileOpen
函数,通过DoPrompFileName函数调用显示文件打开对话框。选择我们刚才所保存的文件:
Graphic.txt,程序进入到 CWinApp类的 OpenDocumentFile函数,然后是CDocManager类的
OpenDocumentFile函数。读者要注意了,这里非常关键!在此函数中,我们单步运行程序,一直运行
到MatchDocType函数调用处。当执行完这个函数调用后,可以发现pOpenDocument这个指针有值了,
也就是说,程序发现这个文件己经与先前的一个文档对象相关联了,即获得了先前的文档对象指针:
CGraphicDoc类型的指针(如图 13.15所示)。因为此时 pOpenDocument指针不为空了,那么
CDocManager类的 OpenDocumentFile函数将直接返回 pOpenDocument这个文档类指针(如例 13-17
所示 CDocManager类 OpenDocumentFile函数实现代码中@位置处的代码).这个函数的执行就结束
了,它并没有调用 CSingleDocTemplate类的 OpenDocumentFile函数,也就没有调用CDocument函数
中的Serialize函数,当然其子类: CGraphicDoc中的Serialize函数也就没有被调用。这就是上面在
保存数据之后再次打开同一个文件时,看不到读取的数据消息框的原因。
图 13.15 pOpenDocument指针已经指向CGraphicDoc类的对象
读者可以试验一下,如果在保存数据之后,再打开另一个文件,这时程序就会弹出消息框,显示读
取到的数据,说明这时调用了CGraphicDoc类的Serialize函数。这就说明文档对象与文件是相关联
的。一旦换了另一个文件,文档对象会将它的数据清空,然后将新文件中的数据与这个对象相关联。
注意在这个过程中,文档对象是同一个文档对象。因为对单文档类型的程序来说,每次只能写一份
文档,因此从效率上考虑,没有必要构造多个文档对象。但是对多文档程序来说,每打开一个文件
都会构造一个新的文档对象。一定要注意,对单文档来说,文档对象本身并不会销毁,它只是将数
据清空,然后再与一个新的
文件相关联。
上面的调试过程比较复杂,读者只要记住.(文件保存和【文件打开】莱单项的命令响应画数都是在
CWinApp类中提供的。CWinApp类有一个成员变量:m_pDocManager. 是指向CDocManager对象的指针,
也就是说, CWinApp负责管理文档管理器,而后者有
: m_templateList.
里文档模板,而后者又是用来管理文档类、框架类和视类的,始终让这三个对象三位一体,一起为
文档服务。所以在上述调试过程中,我们可以看到都是由 CWinApp对象转到 CDocManager对象,再
转到CSingleDocTemplate对象,如果牵扯到CDocument对象,就转到文档对象的函数中。读者只要掌
握了这一线索,不管函数调用如何跳来跳去,都会很清楚地知道它们之间的调用逻辑。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -