Q1: CPoint继承于POINT,这样有什么好处?
A: 继承的一个最基本的好处当然就是减少代码量。CPoint和POINT内部数据一样,只是一个提供了更多的方法来操作对象。
cpp
typedef struct tagPOINT
{
LONG x;
LONG y;
} POINT, *PPOINT, NEAR *NPPOINT, FAR *LPPOINT;
cpp
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的部分代码:
cpp
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 printing
case 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,它的部分代码如下:
cpp
case AfxSigCmd_v:
// normal command or control notification
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(pExtra == NULL);
(pTarget->*mmf.pfnCmd_v_v)();
break;
case AfxSigCmd_b:
// normal command or control notification
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(pExtra == NULL);
bResult = (pTarget->*mmf.pfnCmd_b_v)();
break;
case AfxSigCmd_RANGE:
// normal command or control notification in a range
ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED
ASSERT(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这个函数:
cpp
BOOL AFXAPI AfxInternalPumpMessage()
{
_AFX_THREAD_STATE *pState = AfxGetThreadState();
if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
{
#ifdef _DEBUG
TRACE(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 decremented
return 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 message
if (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等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。
欢迎关注。助您在编程路上越走越好!