MFC内部机制探秘

来源:互联网 发布:企业园区网络实施 stp 编辑:程序博客网 时间:2024/06/11 02:12

由于MFC应用程序涵盖了基于SDK的windows程序几乎所有的功能,所以使用MFC AppWizard创建的MFC程序将自动具有WIndows程序的基本功能,我们今天就来探寻一下MFC的框架机制。

首先大家先利用向导制动建立一个基于MFC的单文档应用程序。我取名为MFC_DISCOVER,方便大家自己验证自己的程序。

1.声明全局对象

利用应用程序对象theApp启动应用程序,theApp一般在应用程序类的实现文件中声明,而且为全局对象。在类视图点击CMFC_DISCOVERApp,再点击下面的构造成员函数,
这里写图片描述
然后在函数对应编辑框中会找到这样一句话:
这里写图片描述
声明该类的变量显然会调用该类的构造函数,构造函数就在声明该变量的上面,但是我们知道CMFC_DISCOVER类派生于CWinApp类,所以会先调用基类的构造函数,
这里写图片描述
点击上图中的CWinApp类右键,点击“转到定义”,可以查看到CWinApp类的构造函数
这里写图片描述

// Constructor    explicit CWinApp(LPCTSTR lpszAppName = NULL); 

然而我们发现该类依然有派生类CWinThread,继续按照上面做发现有这样的关系:

CObeject->CCmdTarget->CWinTread
我就不一一截图了,我们知道是在这个过程中进入WinMain函数的,那么WinMain在哪儿呢?
我们在项目管理搜索栏目里面输入WinMain,搜索如下:
这里写图片描述
在tchar.h文件中发现有这样一个定义,也就是说_tWinMain就是WinMain,现在我们需要找到_tWinMain函数。不要设断点,直接按F10键,我们就看到了_tWinMain函数了:
这里写图片描述
这个函数在MFC的源文件appmodul.cpp中,那这个main怎么被MFC调用的呢?你看那注释,是linkage to this module,也就是被链接器去调用的.准确说是被C-Runtime DLL,C运行时动态链接库调用的.


我们知道在MFC中能从代码里看到的入口点是定义一个全局的继承于CWinApp的类.这样定义下.在C++中全局变量是先于main被执行的,所以先初始化theApp后才接着调用main,WinMain里面又调用了AfxWinMain函数

2.AfxWinMain

要查看AFxWinMain函数的源码,就需要一步你的安装目录下啦,找到安装目录->vc98->MFC->Src(MFC的部分源代码) 搜索 WinMain,然后打开,就会有下面的代码(不要改动这个代码

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,    _In_ LPTSTR lpCmdLine, int nCmdShow){    ASSERT(hPrevInstance == NULL);    int nReturnCode = -1;    CWinThread* pThread = AfxGetThread();    CWinApp* pApp = AfxGetApp();    // AFX internal initialization    if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))        goto InitFailure;    // App global initializations (rare)    if (pApp != NULL && !pApp->InitApplication())        goto InitFailure;    // Perform specific initializations    if (!pThread->InitInstance())//调用******InitIncetance函数**********    {        if (pThread->m_pMainWnd != NULL)        {            TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");            pThread->m_pMainWnd->DestroyWindow();        }        nReturnCode = pThread->ExitInstance();        goto InitFailure;    }    nReturnCode = pThread->Run();//******进入消息循环*********InitFailure:#ifdef _DEBUG    // Check for missing AfxLockTempMap calls    if (AfxGetModuleThreadState()->m_nTempMapLock != 0)    {        TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",            AfxGetModuleThreadState()->m_nTempMapLock);    }    AfxLockTempMaps();    AfxUnlockTempMaps(-1);#endif    AfxWinTerm();    return nReturnCode;}

在AfxWInMain函数中,通过InitInstance函数完成吧了对窗口的创建步骤:设计窗口类,注册窗口类,创建窗口,显示窗口,更新窗口,消息循环,以及窗口函数等。
**根据多态性原理,AfxWinMain函数实际上是调用CMFC_DISCOVERApp的InitInstance函数来完成应用程序的一些初始化工作,包括窗口类的注册,创建,显示和更新等。
InitInstance函数代码如下:

BOOL CMFC_DISCOVERApp::InitInstance(){    // 如果一个运行在 Windows XP 上的应用程序清单指定要    // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,    //则需要 InitCommonControlsEx()。否则,将无法创建窗口。    INITCOMMONCONTROLSEX InitCtrls;    InitCtrls.dwSize = sizeof(InitCtrls);    // 将它设置为包括所有要在应用程序中使用的    // 公共控件类。    InitCtrls.dwICC = ICC_WIN95_CLASSES;    InitCommonControlsEx(&InitCtrls);    CWinApp::InitInstance();    // 初始化 OLE 库    if (!AfxOleInit())    {        AfxMessageBox(IDP_OLE_INIT_FAILED);        return FALSE;    }    AfxEnableControlContainer();    EnableTaskbarInteraction(FALSE);    // 使用 RichEdit 控件需要  AfxInitRichEdit2()     // AfxInitRichEdit2();    // 标准初始化    // 如果未使用这些功能并希望减小    // 最终可执行文件的大小,则应移除下列    // 不需要的特定初始化例程    // 更改用于存储设置的注册表项    // TODO: 应适当修改该字符串,    // 例如修改为公司或组织名    SetRegistryKey(_T("应用程序向导生成的本地应用程序"));    LoadStdProfileSettings(4);  // 加载标准 INI 文件选项(包括 MRU)    // 注册应用程序的文档模板。文档模板    // 将用作文档、框架窗口和视图之间的连接    CSingleDocTemplate* pDocTemplate;    pDocTemplate = new CSingleDocTemplate(        IDR_MAINFRAME,        RUNTIME_CLASS(CMFC_DISCOVERDoc),        RUNTIME_CLASS(CMainFrame),       // 主 SDI 框架窗口        RUNTIME_CLASS(CMFC_DISCOVERView));    if (!pDocTemplate)        return FALSE;    AddDocTemplate(pDocTemplate);    // 分析标准 shell 命令、DDE、打开文件操作的命令行    CCommandLineInfo cmdInfo;    ParseCommandLine(cmdInfo);    // 调度在命令行中指定的命令。如果    // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。    if (!ProcessShellCommand(cmdInfo))        return FALSE;    // 唯一的一个窗口已初始化,因此显示它并对其进行更新    m_pMainWnd->ShowWindow(SW_SHOW);    m_pMainWnd->UpdateWindow();    return TRUE;}

不需要用完全看完,注意到在上面的代码中有这句:

if (!ProcessShellCommand(cmdInfo))        return FALSE;

程序会通过上面的代码调用CMainFram::LoadFrame函数进行注册和创建窗口。

3.LoadFrame

该函数具体代码如下:

BOOL CFrameWnd::LoadFrame(UNIT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext){/*主窗口的菜单、图标、加速键、及标题都以nIDResource标识。除创建窗口外,还要做许多工作,如设置帮助上下文ID、装入加速键、初始化子窗口。所以在文档/视图框架程序中,总是使用LoadFrame()创建主窗口。*/ASSERT_VALID_IDR(nIDResource);ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);m_nIDHelp = nIDResource; // ID for help context (+HID_BASE_RESOURCE)CString strFullString;if (strFullString.LoadString(nIDResource))AfxEXtractSubString(m_strTitle, strFullString, 0); //取得窗口标题VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));//**装入图标,注册窗口类**LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);LPCTSTR lpszTitle = m_strTitle;//调用CFrameWnd::Create()if (!Create(lpszClass, lpszTitle, dwDefaultStyle, rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))//**创建窗口**return FALSE;//存储菜单句柄ASSERT(m_hWnd != NULL);m_hMenuDefault = ::GetMenu(m_hWnd);//装入加速键LoadAccelTable(MAKEINTRESOURCE(nIDResource));if (pContext == NULL) //初始化子窗口SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);return TRUE;}

在上面的代码中,首先通过AfxEndDeferRegisterClass函数实现窗口注册,再调用Create函数实现窗口创建。该函数定义如下:

BOOL CFrameWnd::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, LPCTSTR lpszMenuName, DWORD dwExStyle, CCreateContext* pContext){/*可见,参数列表与CWnd::Create()稍有不同。因为目的是创建主窗口,所以第6个参数要求菜单资源名*/HMENU hMenu = NULL;if (lpszMenuName != NULL){//搜索包含该菜单资源的实例(当前进程或者按进行装入的DLL)HINSTANCE hInst = AfxFindResourceHandl(lpszMenuName, RT_MENU);//装入菜单资源if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL){TRACE0(“Warning: failed to load menu for CFrameWnd.\n”);PostNcDestroy(); //perhaps delete to C++ objectreturn FALSE;}}m_strTitle = lpszWindowName; //存储窗口标题,以备后用(如刷新显示)// 调用CWnd::CreateEx()if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right – rect.left, rect,bottom – rect.top, pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext)){if (hMenu != NULL)DestroyMenu(hMenu); //如果创建失败,释放菜单资源return FALSE;}return TRUE;}

其中又调用了CreateEx函数来完成实际的创建工作,

4.CreateEx

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam){ // allow modification of several common create parameters CREATESTRUCT cs; cs.dwExStyle = dwExStyle; cs.lpszClass = lpszClassName; cs.lpszName = lpszWindowName; cs.style = dwStyle; cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight; cs.hwndParent = hWndParent; cs.hMenu = nIDorHMenu; cs.hInstance = AfxGetInstanceHandle(); cs.lpCreateParams = lpParam; if (!PreCreateWindow(cs)) {  PostNcDestroy();  return FALSE; } AfxHookWindowCreate(this);//核心过程***********挂钩开始 HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,   cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,   cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);#ifdef _DEBUG if (hWnd == NULL) {  TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X/n",   GetLastError()); }#endif if (!AfxUnhookWindowCreate())//挂钩结束  PostNcDestroy();        // cleanup if CreateWindowEx fails too soon if (hWnd == NULL)  return FALSE; ASSERT(hWnd == m_hWnd); // should have been set in send msg hook return TRUE;}

我们知道创建窗口、显示窗口是需要响应WM_PAINT消息的,也就是调用窗口函数,从上面的代码,我们看不出调用了DefWindowProc函数的代码,它是隐藏在AfxHookWindowCreate函数之中,该函数源码如下:

void AFXAPI AfxHookWindowCreate(CWnd* pWnd){_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();if (pThreadState->m_pWndInit == pWnd)   return;if (pThreadState->m_hHookOldCbtFilter == NULL){   pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,//设置CBT钩子    _AfxCbtFilterHook/*回调函数*/, NULL, ::GetCurrentThreadId());   if (pThreadState->m_hHookOldCbtFilter == NULL)    AfxThrowMemoryException();}ASSERT(pThreadState->m_hHookOldCbtFilter != NULL);ASSERT(pWnd != NULL);ASSERT(pWnd->m_hWnd == NULL);   // only do onceASSERT(pThreadState->m_pWndInit == NULL);   // hook not already in progresspThreadState->m_pWndInit = pWnd;}

在CreateEX源码中我们看到AfxHookWindowCreate函数是先于CreatWindowEx函数被调用的,CreateWindowEx是创建真正的窗口对象函数,AfxHookWindowCreate调用了SetWindowHookEx函数设置了钩子,这样有满足设置的消息时,系统就发送给设置函数。这样每次创建窗口时,该函数就将窗口函数修改为AfxWinProc函数,这是消息循环的起点。
这里我们发现创建了一个CBT钩子,回调函数为_AfxCbtFilterHook,这个函数有点长,我们来看一下(看有注释的地方即可)

LRESULT CALLBACK_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam){_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();if (code != HCBT_CREATEWND)//专门用来钩创建窗口的钩子{   // wait for HCBT_CREATEWND just pass others on...   return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,    wParam, lParam);}ASSERT(lParam != NULL);LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs;ASSERT(lpcs != NULL);CWnd* pWndInit = pThreadState->m_pWndInit;BOOL bContextIsDLL = afxContextIsDLL;if (pWndInit != NULL || (!(lpcs->style & WS_CHILD) && !bContextIsDLL)){   // Note: special check to avoid subclassing the IME window   if (_afxDBCS)   {    // check for cheap CS_IME style first...    if (GetClassLong((HWND)wParam, GCL_STYLE) & CS_IME)     goto lCallNextHook;    // get class name of the window that is being created    LPCTSTR pszClassName;    TCHAR szClassName[_countof("ime")+1];    if (DWORD_PTR(lpcs->lpszClass) > 0xffff)    {     pszClassName = lpcs->lpszClass;    }    else    {     szClassName[0] = '\0';     GlobalGetAtomName((ATOM)lpcs->lpszClass, szClassName, _countof(szClassName));     pszClassName = szClassName;    }    // a little more expensive to test this way, but necessary...    if (::AfxInvariantStrICmp(pszClassName, _T("ime")) == 0)     goto lCallNextHook;   }   ASSERT(wParam != NULL); // should be non-NULL HWND   HWND hWnd = (HWND)wParam;   WNDPROC oldWndProc;//用于保存原window回调函数   if (pWndInit != NULL)   {    AFX_MANAGE_STATE(pWndInit->m_pModuleState);    // the window should not be in the permanent map at this time    ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL);    // connect the HWND to pWndInit...    pWndInit->Attach(hWnd);    // allow other subclassing to occur first    pWndInit->PreSubclassWindow();    WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr();    ASSERT(pOldWndProc != NULL);    // subclass the window with standard AfxWndProc    WNDPROC afxWndProc = AfxGetAfxWndProc();    oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,     (DWORD_PTR)afxWndProc);//设置新的回调函数afxWndProc    ASSERT(oldWndProc != NULL);    if (oldWndProc != afxWndProc)     *pOldWndProc = oldWndProc;    pThreadState->m_pWndInit = NULL;   }   else   {    ASSERT(!bContextIsDLL);   // should never get here    static ATOM s_atomMenu = 0;    bool bSubclass = true;       if (s_atomMenu == 0)    {     WNDCLASSEX wc;     memset(&wc, 0, sizeof(WNDCLASSEX));     wc.cbSize = sizeof(WNDCLASSEX);     s_atomMenu = (ATOM)::AfxCtxGetClassInfoEx(NULL, _T("#32768"), &wc);    }    // Do not subclass menus.    if (s_atomMenu != 0)    {     ATOM atomWnd = (ATOM)::GetClassLongPtr(hWnd, GCW_ATOM);     if (atomWnd == s_atomMenu)       bSubclass = false;    }    else    {        TCHAR szClassName[256];     if (::GetClassName(hWnd, szClassName, 256))     {      szClassName[255] = NULL;      if (_tcscmp(szClassName, _T("#32768")) == 0)       bSubclass = false;     }    }       if (bSubclass)    {     // subclass the window with the proc which does gray backgrounds     oldWndProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC);     if (oldWndProc != NULL && GetProp(hWnd, _afxOldWndProc) == NULL)     {      SetProp(hWnd, _afxOldWndProc, oldWndProc);      if ((WNDPROC)GetProp(hWnd, _afxOldWndProc) == oldWndProc)      {       GlobalAddAtom(_afxOldWndProc);       SetWindowLongPtr(hWnd, GWLP_WNDPROC, (DWORD_PTR)_AfxActivationWndProc);       ASSERT(oldWndProc != NULL);      }     }    }   }}lCallNextHook:LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,   wParam, lParam);#ifndef _AFXDLLif (bContextIsDLL){   ::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter);   pThreadState->m_hHookOldCbtFilter = NULL;}#endifreturn lResult;}

接下来就可以准备进入消息循环了


5.消息循环

大家回到前面的AfxWinMain函数中可以看到有这么一行代码:

nReturnCode = pThread->Run();//******进入消息循环*********

Run函数的源码如下:

virtual int CWinThread::Run(){ASSERT_VALID(this);//是否要做空闲处理BOOL bIdle = TRUE;//用户记录空闲处理已经连接执行的次数LONG lIdleCount = 0;//acquire and dispatch messages until a WM_QUIT message is received.//消息循环for (;;){//如果空闲状态为真,且消息队列为空,则进行空闲处理while(bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE)){//PeekMessage()用于检查消息队列是否为空,但并不处理消息//调用空闲处理程序,如果返回零,说明空闲处理已全部完成    if (!OnIdle(lIdleCount++))        bIdle = FALSE;}//空闲处理循环结束,进入消息泵循环    do    {        //调用消息泵,提取消息并分发消息        //如果收到WM_QUIT消息,则退出消息循环        if (!PumpMessage())            return ExitInstance();        //根据刚处理的消息类型,判断是否应该在没有消息到来时立即进行空闲处理        if (IsIdleMessage(&m_msgCur))        {             bIdle = TRUE;       //在重新进行空闲处理前,清空空闲处理的执行次数            lIdleCount = 0;        }    }while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));}ASSERT(FALSE); //不可能执行的语句}

可以看到进入消息循环后,由PumpMessage函数处理消息,是处理消息的关键,源码如下:

//省略了调试信息的输出功能BOOL CWinThread::PumpMessage(){ASSERT_VALID(this);//取出消息队列中的第一个消息,直到取得消息,该函数才返回if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)){//收到WM_QUIT消息return FALSE;}//处理消息,但不处理WM_KICKIDLEif (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)){//转换虚键消息到字符消息::TranslateMessage(&m_msgCur);//分发消息::DispatchMessage(&m_msgCur);}return TRUE;}阅读PumpMessage代码可知,消息泵并不处理WM_KICKIDLE消息,收到该消息后,直接返回。其实,WM_KICKIDLE消息被用来刺激空闲处理的执行,它作为一个空消息促使::GetMessage()返回。虽然Run()是虚拟函数,但很少被重载。

CWinThread类是MFC用来封装线程的,包括UI线程和工作者线程。因此每个MFC程序至少使用一个CWinThread派生类。
可见Run函数和PumpMessage函数实现了Win32程序中的WinProc函数的功能。

至此MFC的机制就基本讲完了,如果觉得没说清楚的可以去看《深入浅出MFC》前几章就好,那本书讲得很细。本文中的所有代码,都可以在百度上搜到,源码只需要看有注释的关键几行。不得不说Windows将这个机制隐藏的很深,对于初学者学习,真的不是很友好。它这么做必然有它的道理,我想这与它的系统有个很深的关系,MFC也不是一下子就搞出来的,微软也走过错路,有兴趣的读者可以取百度一下这个历史。

参考链接:
(http://www.cnblogs.com/lidabo/archive/2012/07/04/2576832.html)
http://www.cnblogs.com/chinazhangjie/archive/2011/09/19/2181954.html
http://blog.csdn.net/weiwenhp/article/details/8455471
http://www.cnblogs.com/liuweilinlin/archive/2012/08/16/2643272.html
http://blog.csdn.net/misskissc/article/details/8555105

1 0
原创粉丝点击