小識MFC,一套設計優雅與不優雅並存的類庫—-小話MFC(2)

來源:互聯網
上載者:User

作者:陳曦
日期:2012-11-29 22:39
環境:[win7 32位作業系統 Intel i3 支援64位指令 VS2012]  

轉載請註明出處

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 _DEBUG  if (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應用程式訊息處理基本過程。

後面的調用關係就不具體說了。

作者:陳曦
日期:2012-11-29 22:39
環境:[win7 32位作業系統 Intel i3 支援64位指令 VS2012]  

轉載請註明出處

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.