二. MFC框架程序分析--Windows编程课程学习笔记

原文地址: https://blog.csdn.net/ling_xiao007/article/details/51720764

2.1 MAF APPWizard

MAF APPWizard是一个辅助生成代码的向导工具,可以帮助自动生成基于MFC框架的源代码。创建一个MFC的单文档界面(SDI)应用程序,命名为Windows编程。

2.2 基于MFC的程序框架剖析

查看一下类视图(ClassView)的标签页,可以看到五个非常重要的类。

类名以C开头,打开折叠项,发现他们都是由CObject派生。新奇的查阅一下MSDN中的层次结构图。CWnd类是非常重要的一个类。https://msdn.microsoft.com/zh-cn/library/ws8s10w4.aspx

I.WinMain函数

0.WinMain函数

Win32应用程序中,有1)WinMain函数;2)设计窗口类,注册窗口类,产生窗口,显示窗口,更新窗口;3)消息循环清晰地程序脉络。在MFC中虽然在工程中找不到这样的脉络,但是MS在MFC底层框架类中封装了这些步骤。

Edit->Find inFiles 在MFC安装目录下可以找到这些脉络。

1.png

在appmodul.cpp中找到了WinMain类似的函数,_tWinMian,调用AfxWinMain。在_tWinMian处F9下断点,程序停留在_tWinMian处。说明该_tWinMain(HINSTANCEhInstance, HINSTANCEhPrevInstance, In LPTSTR lpCmdLine, int nCmdShow)就是Win32中的WinMain。
2.png

3.png

1.theApp全局对象

找到了WinMain,那么它是如何将MFC程序的各个类组织到一起的?也就是说,MFC程序中的类是如何与WinMain函数关联起来的?


4.png

可以双击CWindows编程App类,跳转到定义的头文件中。CWindows编程App派生于CWinAppEx类(应用程序类),在CWindows编程App构造函数处设置断点,发现比先进入构造函数,在进入WinMain。

// 唯一的一个 CWindows编程App 对象
CWindows编程App theApp;

这里,回顾一下C++四种不同的对象的生与死问题:

①对于一般局部对象(栈Stack中产生):当对象产生时,构造函数被执行;当函数结束时(以至于对象将毁灭),析构函数被执行。

②对于new操作局部对象(堆Heap中产生):当对象产生时(执行new操作),构造函数被执行;当delete对象语句被执行时,析构函数被执行。

③对于全局对象:程序一开始,其构造函数就先被执行(比入口函数main、WinMain更早);程序结束前,析构函数被执行。

④对于局部静态(static)对象:只会有一个实例产生,而且在固定的内存上(既不是stack也不是heap),执行到第一次声明处(也就是在MyFunc第一次调用)时,构造函数被调用;当程序结束时(对象因此遭致毁灭),析构函数被执行,但比全局对象的析构函数先一步执行。

TheAPP对象的构造函数在调用之前,会调用其父类CWinApp的构造函数,从而将自己创建的类与Microsoft提供的基类关联起来。CWinApp的构造函数完成了程序运行时的一些初始化工作。

   //initialize CWinApp state
    ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
    pModuleState->m_pCurrentWinApp = this;
    ASSERT(AfxGetApp() == this);

this是theApp。同时CWinApp有一个形参lpszAppName。(CWinApp::CWinApp(LPCTSTRlpszAppName))但CWindows编程App构造函数没有形参,那子类将显式的调用基类带参数的构造函数。然而没有这么做的原因是CWinApp构造函数中设置了NULL。

class CWinApp : public CWinThread
{
    DECLARE_DYNAMIC(CWinApp)
public:
 
// Constructor
    explicit CWinApp(LPCTSTR lpszAppName = NULL);     // app name defaults to EXE name

2.AfxWinMain函数

程序调用CWinApp类的构造函数,以及CWindows编程App的构造函数,生成theApp对象后进入WinMain函数。WinMain实际调用AfxWinMain。在文件中找到AfxWinMain定义代码

/////////////////////////////////////////////////////////////////////////////
//Standard WinMain implementation
//  Can be replaced as long as 'AfxWinInit' iscalled first
 
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))
        gotoInitFailure;
 
    //App global initializations (rare)
    if (pApp != NULL && !pApp->InitApplication())
        gotoInitFailure;
 
    //Perform specific initializations
    if (!pThread->InitInstance())
    {
        if(pThread->m_pMainWnd != NULL)
        {
            TRACE(traceAppMsg, 0, "Warning:Destroying non-NULL m_pMainWnd\n");
            pThread->m_pMainWnd->DestroyWindow();
        }
        nReturnCode =pThread->ExitInstance();
        gotoInitFailure;
    }
    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;
}

调用AfxGetThread得到一个CWinTread类型指针。AfxGetApp得到一个CWinApp指针。AfxGetThread其实调用了AfxGetApp,所以pThread和pApp一致。

_AFXWIN_INLINECWinApp* AFXAPI AfxGetApp()
    { return afxCurrentWinApp; }
#define afxCurrentWinApp   AfxGetModuleState()->m_pCurrentWinApp

由CWinApp构造函数返回的是CWinApp中保存的this,所以知道pThread、pApp返回的是theApp全局对象。

3.InitInstance函数

virtual BOOL InitInstance();

虚函数调用的是子类CWindows编程的InitInstance

BOOL CWindows编程App::InitInstance()
{
    // 如果一个运行在 Windows XP 上的应用程序清单指定要
    // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
    //则需要 InitCommonControlsEx()。  否则,将无法创建窗口。
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    // 将它设置为包括所有要在应用程序中使用的
    // 公共控件类。
    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);
 
    CWinAppEx::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(CWindows编程Doc),
        RUNTIME_CLASS(CMainFrame),       // 主SDI 框架窗口
        RUNTIME_CLASS(CWindows编程View));
    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;
}

II.MFC框架窗口

1. 设计和注册窗口

MFC已经预设计了一些标准窗口类,只需要AfxEndDeferRegisterClass注册就好。AfxEndDeferRegisterClass在WINCORE.cpp中。AfxEndDeferRegisterClass代码可以知道首先判断窗口类型,赋予类名(wndcls.lpszClassName),调用AfxRegisterClass。AfxRegisterClass首先获得窗口类信息,未注册则调用RegisterClass注册。

在CMainFrame类PreCreateWindow函数设断点,发现程序在调用theApp和WinMain后到达,所以MFC在WinMain后注册窗口类的。

2.创建窗口

在设计和注册窗口类后是创建窗口,由CWnd类的CreateEx函数完成。声明在AfxWin.h中

        virtual BOOL CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
        LPCTSTRlpszWindowName, DWORD dwStyle,
        const RECT&rect,
        CWnd*pParentWnd, UINT nID,
        LPVOIDlpParam = NULL);

代码在wincore.cpp中,调用了CreateWindowEx函数;在MFC底层代码中CFrameWnd类调用了上述CreateEx,而前者又有LoadFrame调用。CFrameWnd派生于CWnd类,CWnd类的不是虚函数,所以CFrameWnd继承而没有重写。

3.显示窗口和更新

    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();

III.消息循环

AfxWinMain中的pThread->Run();CWinThread类的Run函数的定义。

// mainrunning routine until thread exits
int CWinThread::Run()
{
    ASSERT_VALID(this);
    _AFX_THREAD_STATE* pState = AfxGetThreadState();
 
    //for tracking the idle time state
    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;
 
    //acquire and dispatch messages until a WM_QUIT message is received.
    for (;;)
    {
        // phase1: check to see if we can do idle work
        while(bIdle &&
            !::PeekMessage(&(pState->m_msgCur),NULL, NULL, NULL, PM_NOREMOVE))
        {
            // call OnIdle while in bIdle state
            if(!OnIdle(lIdleCount++))
                bIdle = FALSE; // assume "no idle" state
        }
 
        // phase2: pump messages while available
        do
        {
            // pump message, but quit on WM_QUIT
            if(!PumpMessage())
                returnExitInstance();
 
            // reset "no idle" state after pumping"normal" message
            //if (IsIdleMessage(&m_msgCur))
            if(IsIdleMessage(&(pState->m_msgCur)))
            {
                bIdle = TRUE;
                lIdleCount = 0;
            }
 
        } while (::PeekMessage(&(pState->m_msgCur),NULL, NULL, NULL, PM_NOREMOVE));
    }
}

主要结构是一个for循环,在收到WM_QUIT消息退出,在循环中调用了一个PumpMessage。

IV.窗口过程函数

    wndcls.lpfnWndProc = DefWindowProc;

该行代码作用是设置窗口过程函数,默认的窗口过程:DefWindowProc。并不是将所有消息交给DefWindowProc处理,而是采取消息映射的机制。

至此,了解了MFC程序的整个运行机制。

你可能感兴趣的:(二. MFC框架程序分析--Windows编程课程学习笔记)