Q1:?CPoint繼承于POINT,這樣有什么好處?
A:?繼承的一個最基本的好處當然就是減少代碼量。CPoint和POINT內部數據一樣,只是一個提供了更多的方法來操作對象。
typedef struct tagPOINT
{LONG x;LONG y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;
class CPoint :public tagPOINT
這樣的方式,很自然,CPoint不用再單獨加入成員x,y,?而且函數參數可以很自然的實現從派生類到基類的轉換,是個不錯的設計。
Q2: CDocument, CView實現界面展示、變化以及用戶操作UI是MVC架構嗎?
A:?看起來有點像,實際并不是純正的MVC設計。CDocument的設計更多考慮了windows操作系統MDI文檔視圖,它希望提供一個MDI中每個視圖的模板原型。
CView主要實現顯示,不過對于用戶和UI的交互,MFC架構中并沒有提供框架為MVC的控制器來單獨考慮,它將控制器放入了CView, CDocument甚至CWinApp中。
這聽起來并不是一個很好的設計,但是實際上,視圖和操作視圖放在一起也并不是一個萬惡不赦的設計, 因為UI本來就改來改去,程序員還是可以接受這樣的方式。
Q3: CWnd類內部如此多的成員函數,這樣設計合理嗎?
A: CWnd主要處理一個窗口的顯示,包括窗口標題、最大化最小化、內部子控件獲取等。不過ms的設計,將此類內部加入了太多和CWnd關系不是很大的東西,導致了此類成員很多,弊端不用說了,此類不是一個優秀設計,它處理了太多不該去處理的東西,使得整個類庫設計清晰度降低;
不過,也有一定優點,很多在主框架或者view中可以直接調用它的成員函數,不用花心思再去想需要調用的函數出自哪個類。
Q4:?使用MFC向導創建的應用程序,里面的消息處理流程很復雜,如何很好地查看消息流?
A:?函數堆棧是查看它的很好方式。
如上,是一個使用MFC app wizzard創建的SDI應用程序basic_mfc.exe開始運行后的調用堆棧。
可以看出,應用程序開始運行后,會調用應用程序類的ProcessShellCommand解析命令行參數,此過程可能就進入了消息處理過程(比如,一個應用程序剛打開,默認的處理是打開一個新文檔),如下是ProcessShellCommand的部分代碼:
case CCommandLineInfo::FileNew:if (!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))OnFileNew();if (m_pMainWnd == NULL)bResult = FALSE;break;// If we've been asked to open a file, call OpenDocumentFile()case CCommandLineInfo::FileOpen:if (!OpenDocumentFile(rCmdInfo.m_strFileName))bResult = FALSE;break;// If the user wanted to print, hide our main window and// fire a message to ourselves to start the printingcase CCommandLineInfo::FilePrintTo:case CCommandLineInfo::FilePrint:m_nCmdShow = SW_HIDE;ASSERT(m_pCmdInfo == NULL);if(OpenDocumentFile(rCmdInfo.m_strFileName)){m_pCmdInfo = &rCmdInfo;ENSURE_VALID(m_pMainWnd);m_pMainWnd->SendMessage(WM_COMMAND, ID_FILE_PRINT_DIRECT);m_pCmdInfo = NULL;}bResult = FALSE;break;
從上面可以看出,FileNew就是從這里進去的。OnCmdMsg函數會調用全局函數_AfxDispatchCmdMsg,它的部分代碼如下:
case AfxSigCmd_v:// normal command or control notificationASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKEDASSERT(pExtra == NULL);(pTarget->*mmf.pfnCmd_v_v)();break;case AfxSigCmd_b:// normal command or control notificationASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKEDASSERT(pExtra == NULL);bResult = (pTarget->*mmf.pfnCmd_b_v)();break;case AfxSigCmd_RANGE:// normal command or control notification in a rangeASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKEDASSERT(pExtra == NULL);(pTarget->*mmf.pfnCmd_v_u)(nID);break;case AfxSigCmd_EX:// extended command (passed ID, returns bContinue)ASSERT(pExtra == NULL);bResult = (pTarget->*mmf.pfnCmd_b_u)(nID);break;case AfxSigNotify_v:{AFX_NOTIFY* pNotify = (AFX_NOTIFY*)pExtra;ENSURE(pNotify != NULL);ASSERT(pNotify->pResult != NULL);ASSERT(pNotify->pNMHDR != NULL);(pTarget->*mmf.pfnNotify_v_NMHDR_pl)(pNotify->pNMHDR, pNotify->pResult);}break;case AfxSigNotify_b:{AFX_NOTIFY* pNotify = (AFX_NOTIFY*)pExtra;ENSURE(pNotify != NULL);ASSERT(pNotify->pResult != NULL);ASSERT(pNotify->pNMHDR != NULL);bResult = (pTarget->*mmf.pfnNotify_b_NMHDR_pl)(pNotify->pNMHDR, pNotify->pResult);}break;
它其實是對不同的消息類型調用不同的默認回調函數,有的是空參數的,有的以一個整形為參數的,等等。最終的返回值表征是否已經處理,外部會根據這個返回值決定是否繼續處理下去。
如下是接下來的處理:
這里可以看到,MFC類庫內部完成了主要的消息傳遞過程,最終到達應用程序document類的OnNewDocument來完成最后的處理。
ok,當應用程序啟動后,手動點擊菜單的新建或者工具欄中的新建,調用堆棧如下:
注意,上面的調用堆棧并不完全,堆棧最底層的是在ntdll中線程啟動的代碼,這里不列出了。
不過可以看出,應用程序啟動后,對于菜單或者工具欄的操作將通過應用程序類的Run函數,它會將UI命令傳遞進去,讓適當的模塊處理,這和剛剛啟動時的調用堆棧不一致。
在這里,我們主要看看AfxInternalPumpMessage這個函數:
BOOL AFXAPI AfxInternalPumpMessage()
{_AFX_THREAD_STATE *pState = AfxGetThreadState();if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL)){
#ifdef _DEBUGTRACE(traceAppMsg, 1, "CWinThread::PumpMessage - Received WM_QUIT.\n");pState->m_nDisablePumpCount++; // application must die
#endif// Note: prevents calling message loop things in 'ExitInstance'// will never be decrementedreturn FALSE;}#ifdef _DEBUGif (pState->m_nDisablePumpCount != 0){TRACE(traceAppMsg, 0, "Error: CWinThread::PumpMessage called when not permitted.\n");ASSERT(FALSE);}
#endif#ifdef _DEBUG_AfxTraceMsg(_T("PumpMessage"), &(pState->m_msgCur));
#endif// process this messageif (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur))){::TranslateMessage(&(pState->m_msgCur));::DispatchMessage(&(pState->m_msgCur));}return TRUE;
}
可以看到,它其實主要就是GetMessage, TranslateMessage, DispatchMessage這3個函數,是windows應用程序消息處理基本過程。
后面的調用關系就不具體說了。
微風不燥,陽光正好,你就像風一樣經過這里,愿你停留的片刻溫暖舒心。
我是程序員小迷(致力于C、C++、Java、Kotlin、Android、Shell、JavaScript、TypeScript、Python等編程技術的技巧經驗分享),若作品對您有幫助,請關注、分享、點贊、收藏、在看、喜歡,您的支持是我們為您提供幫助的最大動力。
歡迎關注。助您在編程路上越走越好!