《深入浅出MFC》– MFC程序的生死因果

1.首先MFC程序需要哪些函数库?

Windows C Runtime函数库

LIBC.LIB       C Runtime函数库的静态链接版本

MSVCRT.LIB     C Runtime函数库的动态链接版本

MSVCRTD.LIB      ‘D’表示使用于Debug方式

DLL Import函数库:GDI32.LIB、USER32.LIB、KERNEL32.LIB…

MFC函数库:MFC42.LIB、MFC42D.LIB、MFCS42.LIB、MFCS42D.LIB、MFCN42D.LIB、MFCD42D.LIB、MFCO42.LIB…

 

2.MFC程序需要哪些头文件?

①STDAFX.H:这个文件用来作为Precompiled header file,其内只是载入其他的MFC头文件。

②AFXWIN.H:每一个Windows MFC程序都必须载入它,因为它以及它所载入的文件声明了所有的MFC类。此文件内涵AFX.H,后者又载入AFXVER_.H,后者又载入AFXV_W32.H,后者又载入WINDOWS.H。

③AFXEXT.H   使用工具栏、状态栏的程序必须载入这个文件。

④AFXDLGS.H    凡使用通用对话框(Common dialog)的MFC程序需要载入此文件。它内部包含COMMDLG.H

⑤FXCMN.H     凡使用windows 9x新增通用行控件(common control)的MFC程序需要载入此文件。

⑥AFXCOLL.H    凡使用MFC提供的容器都需要载入此文件。

⑦AFXDLLX.H    凡使用MFC extension DLLs需要载入此文件。

⑧AFXRES.H      MFC程序的RC文件必须载入此文件。此文件中对于标准资源的ID都有默认值。它们定义于此文件中。

 

3.afx_msg和CALLBACK的意义:

#define  afx_msg     //故意安排的一个位置,也许以后版本会用到

#define  CALLBACK   __stdcall      //一种函数的调用习惯

 

4.MFC把有着固定行为的WinMain内部操作封装在CWinApp中,把有着相当固定行为的WndProc内部操作封装在CFrameWnd中。也就是CWinApp代表程序主体,CFrameWnd代表一个主框窗口(Frame Window)。

几乎可以说CWinApp用来取代WinMain在SDK程序中地位。这并不是说MFC程序没有WinMain,而是传统上SDK程序的WinMain所完成的工作现在由CWinApp的三个函数完成:

virtual bool InitApplication();

virtual bool InitInstance();

virtual int Run();

从MFC 4.x开始,m_pMainWnd已经被移往CWinThread中了。

 

5.MFC内建了一个所谓的Message Map机制,会把消息自动送到“与消息对应的特定函数”中去,消息与处理函数之间的对应关系由程序猿指定。

BEGIN_MESSAGE_MAP(CMyFrameWnd,  CFrameWnd)

    ON_WM_PAINT()

    ON_COMMAND(IDM_ABOUT,  OnAbout)

END_MESSAGE_MAP()

 

6.引爆器—Application Object

image

 

上图的theApp就是Hello程序的application object,每一个MFC应用程序都有一个,而且也只有这么一个。当你执行Hello时,这个全局对象产生,于是构造函数执行起来。我们并没有定义CMyWinApp构造函数,至于其父类CWinApp的构造函数内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CWinApp:: CWinApp(LPCTSTR lpszAppName)
{
   m_pszAppName = lpszAppName;
   AFX_MODULE_THREAD_STATE * pThreadState = AfxGetMoudleThreadState();
   pThreadState->m_pCurrentWinThread = this;
  m_hThread = ::GetCurrentThread();
   m_nThreadID = ::GetCurrentThreadId();
   AFX_MOUDLE_STATE * pModueState = AfxGetModuleState();
   pModuleState->m_pCurrentWinApp = this;

   m_hInstance = NULL;
   m_pszHelpFilePath = NULL;
   m_pszProfileName = NULL;
   m_pszRegistryKey = NULL;
   m_pszExeName = NULL;
   m_lpCmdLine = NULL;
   m_pCmdInfo = NULL;
   …
}

从源代码中可以看出cwinapp的构造函数主要收集了一些关于应用程序主线程的信息及初始化一些相关应用程序的信息。值得注意的是cwinapp类的一些主要的数据成员如:m_hinstance,m_lpcmdline,m_pcmdinfo及m_atomapp等都初始化为null,这些成员在后面将被重新赋值。

 

7.隐晦不明的WinMain

theApp配置完成后,WinMain登场。我们并未撰写WinMain程序代码,这是MFC早已准备好并由链接器直接加到应用程序代码中的,其程序代码如下。_tWinMain函数的 “-t”是为了支持Unicode而准备的一个宏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
extern "C" int WINAPI
_tWinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow)
{
    Return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow )
{
    ASSERT( hPrevInstance == NULL );
    int nReturnCode = -1;
    CwinApp *pApp = AfxGetApp();
    if(!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
         goto  InitFailure;
    ASSERT_VALID(pApp);
    if(!pApp->InitApplication())
         goto  InitFailure;
    ASSERT_VALID(pApp);
    if(!pApp->InitInstance())
    {
         if(pApp->m_pMainWnd != NULL)
         {
               TRACE0("Warning:Destroying non-NULL m_pMainWnd\n");
               pApp->m_pMainWnd->DestroyWindow();
         }
         nReturnCode = pApp->ExitInstance();
         goto  InitFailure;
    }
    ASSERT_VALID(pApp);
    nReturnCode = pApp->Run();
    ASSERT_VALID(pApp);
InitFailure:
    AfxWinTerm();
    Return nReturnCode;
}

其中AfxGetApp是一个全局函数,定义域AFXWIN1.INL中:

_AFXWIN_INLINE CWinApp * AFXAPI AfxGetApp()
{ return afxCurrentWinApp ;}

而afxCurrentWinApp 又定义于AFXWIN.H中:

#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp

AfxGetApp其实就是取得CMyWinApp对象指针。

所以上面相当于

CMyWinApp::InitApplication();    ->   CWinApp::InitApplication();   

CMyWinApp::InitInstance();

CMyWinApp::Run();     ->     CWinApp::Run();

 

8.AfxWinInit:AFX内部初始化操作

前面说过:”CWinApp类的一些主要的数据成员在后面将被重新赋值。”,AfxWinInit函数就是这些数据成员被赋值的地方,它重新初始化这些在整个程中都扮演重要角色的成员,并且调用AfxInitThread()为主线程作了一些初始化工作,这些都为以后mfc框架的正常运作铺平了道路。

 

9.CWinApp::InitApplication

上面的pApp->InitApplication()即为CMyWinApp::InitApplication也就是CWinApp::InitApplication。

 

10.CMyWinApp::InitInstance

接着调用pApp->InitInstance.pApp指向CMyWinApp对象,由于我们改写了它,所以即为调用CMyWinApp->InitInstance.

 

11.CFrameWnd::Create产生主窗口

image

MFC构造了CMyFrameWnd,在CMyFrameWnd的构造函数内调用了Create函数,它产生一个窗口,它需要八个参数,其中六个都有了默认值。只有前两个需要指定。

第一个参数lpszClassName,用以指定WNDCLASS类,使用NULL表示使用MFC内建的窗口类产生一个标准的窗口,但是此时我们并没与发现窗口注册的操作。因为在Create函数内会调用注册窗口类的函数。这稍候会做介绍。

第二个参数:lpszWindowName,指定窗口标题。

第三个参数指定窗口风格。

第四个参数指定窗口的位置与大小。默认值rectDefault是CFrameWnd的一个static成员变量。告诉windows以默认方式指定窗口位置和大小。 同时也可以手动指定,如CRect(40,20,240,460);

第五个参数用以指定父窗口。对于一个顶层窗口来说它应该为NULL,表示它没有父窗口。但是其实它是有的,它的父窗口是desktop窗口。

第六个参数指定菜单,它是在RC文件中定义的。

第八个参数pContext指向CCreateContext结构指针,MFC利用它在文档视图结构初始化外框窗口。 不具备文档视图结构的程序此值为NULL.

前面提到,CFrameWnd::Create在产生窗口之前,会引发窗口类的注册操作。

Create是CFrameWnd的成员函数,在Create函数中又调用了CreateEx,因为派生类CFrameWnd没有改变CWind的虚函数CreateEx,所以其实调用的是CWnd::CreateEx.

然后我们看CreateEx源码会发现调用了PreCreateWindow();这是个虚函数,在CWnd和CFrameWnd都有定义,由于this指针的缘故,这里调用的是CFrameWnd::PreCreateWindow。

继续跟入可以发现AfxDeferRegisterClass()的调用。这是一个定义于AFXIMAPL.H的宏。

#define AfxDeferRegisterCalss(fClass)\

     ((afxRegisteredClasses&fClass)?true:AfxEndDeferRegisterClass(fClass);

这个宏表示如果afxRegisterClasses的值显示系统已经注册了fClass,窗口类就啥也不做。否则就调用AfxEndDeferRegisterClass注册该窗口类。

afxRegisterClasses是一个旗标变量,用来记录已经注册了哪些窗口类。

#define afxRegisterClasses AfxGetModuleState->m_fRegisteredClasses();

我们看AfxEndDeferRegisterClass的操作的话,它调用两个函数完成实际的窗口类注册操作,一个是RegisterWithIcon,一个是AfxRegisterClass,最终都是RegisterClass。

注意,不同的类PreCreateWindow成员函数都是在窗口产生之前一刻被调用,准备用来注册窗口类。如果我们制定的窗口类是NULL,那么就使用系统默认类。从CWnd及其各个派生类的PreCreateWindow成员函数可以看出,整个Framework针对不同功能的窗口使用不同的窗口类。

 

12.窗口显示与更新

CMyFrameWnd::CMyFrameWnd结束后,窗口已经诞生出来,程序流程又回到CMyFrameWnd::InitInstance,于是调用ShowWindow函数令窗口显示出来,并调用UpdateWindow函数令Hello程序送出WM_PAINT消息。

 

13.CWinApp::Run — 程序生命的活水源头

pApp指向CMyWinApp对象,也就是本例的theApp,所以,当程序调用:pApp->Run();相当于调用:CMyWinApp::Run();虚函数,没有改写,所以调用CWinApp::Run().

image

int CWinApp::Run()

{

   if(m_pMainWnd==NULL&&AfxOleGetUserCtrl())

    {

      AfxPostQUitMessage(0);

    }

   return CWinThread::Run();

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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())
            return exitinstance();

       // 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));
     }
}          

Bool CWinThread::PumpMessage()
{
   If(!::GetMessage(&m_msgCur,NULL,NULL,NULL)
   {
      Return false;
   }
   If(m_msg.message!=WM_KICKIDLE&&
                  !PreTranslateMessage(&m_msgCur)
     {
        ::TranslateMessage(&m_msgCur);
        ::DispatchMessage(&m_msgCur);
     }
}

窗口函数实际上也是由MFC提供的,DefWindowProc函数,但事实上消息并不是被送往该处,而是一个名为AfxWndProc的全局函数。

 

14.把消息与处理函数连接在一起:Message Map机制

image

MFC把消息主要分为三大类,Message Map机制中对于消息与函数间的对应关系也明定以下三种:

①标准Windos消息(WM_xxx)的对应规则

image

②命令消息(WM_COMMAND)的一般性对应规则是:

ON_COMMAND(IDM_ABOUT,OnAbout);

ON_COMMAND(ID_FILEOPEN,OnFileOpen);

③Notification消息(由空间产生,例如BN_xxx)的对应机制的宏分为好几种。

image

15.来龙去脉总整理

①程序的诞生:

1.Application object 产生, 内存于是获得配置,初值亦设立了。

2.AfxWinMain执行AfxWinInit,后者又调有AfxInitThread,把消息队列尽量加大到96

3.AfxWinMain执行InitApplication。这是CWinApp的虚拟函数,我们通常不改写它

4.AfxWinMain执行InitInstance。这是CWinApp的虚拟函数,我们必须改写它

5.CMyWinApp::InitInstance “new”了一个CMyFrameWnd对象

6.CmyFrameWnd构造函数调用Create,产生主窗口。我们在Create参数中指定的窗口类是NULL,于是MFC根据窗口种类,自行为我们注册一个名为“AfxFrameOrView42d”的窗口类。

7.回到InitInstance中继续执行ShowWindow,显示窗口

8.执行UpdateWindow,于是发出WM_PAINT

9.回到AfxWinMain,执行Run,进入消息循环。

②程序开始运行:

1.程序获得WM-PAINT消息(由CWinApp::Run中的::GetMessage循环)

2.WM-PAINT经由::DispatchMessage送到窗口函数CWnd::DefWindowProc中。

3.CWnd::DefWindowProc将消息传递到消息映射表格

4.传递过程中发现有相符项目,于是调用项目中对应的函数。此函数是利用BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间的宏设立起来的。

5.标准消息的处理程序亦有标准命名,例如WM_PAINT必由OnPaint处理

③程序的死亡:

1.使用者单击File/Close,于是发出WM-CLOSE

2.CMyFrameWnd并没有设置WM-CLOSE处理程序,于是交给默认的处理程序

3.默认函数对于WM-CLOSE的处理方式是调用::DestroyWindow,并因而发出WM_DESTROY

4.默认的WM-DESTROY处理方式是调用::PostQuitMessage,因此发出WM_QUIT

5.CWinApp::Run收到WM_QUIT后会结束内部之消息循环,然后调用ExitInstance,这是CWinApp的一个虚拟函数;

6.如果CMyWinApp改写了ExitInstance,那么CWinApp::Run所调用的就是CMyWinApp::ExitInstance,否则就是CWinApp:::ExitInstance

7.最后回到AfxWinMain,执行AfxWinTerm,结束程序.

 

16.如果你的MFC程序也想处理idle time,只要改写CWinApp派生类的OnIdle函数即可。

 

转载请标注来源:http://www.alonemonkey.com

By:AloneMonkey

本文链接:http://www.alonemonkey.com/mfc-five.html