MFC生命周期详述

MFC 生命周期

 

Step 1CWinApp-取代WinMain 的地位


class CWinApp : public CWinThread
{
// Attributes
// Startup args (do not change)
HINSTANCE m_hInstance;
HINSTANCE m_hPrevInstance;
LPTSTR m_lpCmdLine;
int m_nCmdShow;
// Running args (can be changed in InitInstance)
LPCTSTR m_pszAppName; // human readable name
LPCTSTR m_pszRegistryKey; // used for registry entries
public: // set in constructor to override default
LPCTSTR m_pszExeName; // executable name (no spaces)
LPCTSTR m_pszHelpFilePath; // default based on module path
LPCTSTR m_pszProfileName; // default based on app name
public:
// hooks for your initialization code
virtual BOOL InitApplication();
// overrides for implementation
virtual BOOL InitInstance();
virtual int ExitInstance();
virtual int Run();
virtual BOOL OnIdle(LONG lCount);
...
};


几乎可以说CWinApp 用来取代WinMain 在SDK 程序中的地位。这并不是说MFC 程序

没有WinMain,而是说传统上SDK 程序的WinMain 所完成的工作现在由CWinApp 的三个函数完成:

l virtual BOOL InitApplication();

l virtual BOOL InitInstance();

l virtual int Run();

而CWinAPP的父类CWinThread成员变量m_pMainWnd记录主窗口的handle。

 

Step 2 :CFrameWnd-取代WndProc 的地位


CFrameWnd 主要用来掌握一个窗口,几乎你可以说它是用来取代SDK 程序中的窗口函

式的地位。传统的SDK 窗口函数写法是:

long FAR PASCAL WndProc(HWND hWnd, UNIT msg, WORD wParam, LONG lParam)
{
switch(msg) {
case WM_COMMAND :
switch(wParam) {
  case IDM_ABOUT :
      OnAbout(hWnd, wParam, lParam);
break;
}
break;
case WM_PAINT :
  OnPaint(hWnd, wParam, lParam);
break;
default :
DefWindowProc(hWnd, msg, wParam, lParam);
}
}
MFC 程序有新的作法,我们在Hello 程序中也为CMyFrameWnd 准备了两个消息处理例程,声明如下:
class CMyFrameWnd : public CFrameWnd
{
public:
CMyFrameWnd();
afx_msg void OnPaint();
afx_msg void OnAbout();
DECLARE_MESSAGE_MAP()
};


 

Step 3:Application object

我们的Application子类定义如下:

CMyWinApp theApp; // application object
BOOL CMyWinApp::InitInstance()
{
m_pMainWnd = new CMyFrameWnd();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
CMyFrameWnd::CMyFrameWnd()
{
Create(NULL, "Hello MFC", ...,
"MainMenu");
}
void CMyFrameWnd::OnPaint() { ... }
void CMyFrameWnd::OnAbout() { ... }
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
ON_COMMAND(IDM_ABOUT, OnAbout)
ON_WM_PAINT()
END_MESSAGE_MAP()
theApp就是Hello 程序的application object,每一个MFC 应用程序都有一个,而且也只有这么一个。当你执行Hello,这个全域对象产生,于是构造式执行起来。我们并没有定义CMyWinApp 构造式;至于其父类别CWinApp 的构造式内容摘要如下:
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
m_pszAppName = lpszAppName;
// initialize CWinThread state
AFX_MODULE_THREAD_STATE* pThreadState = AfxGetModuleThreadState();
pThreadState->m_pCurrentWinThread = this;
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
// initialize CWinApp state
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
pModuleState->m_pCurrentWinApp = this;
// in non-running state until WinMain
m_hInstance = NULL;
m_pszHelpFilePath = NULL;
m_pszProfileName = NULL;
m_pszRegistryKey = NULL;
m_pszExeName = NULL;
m_lpCmdLine = NULL;
m_pCmdInfo = NULL;
...
}


 

Step 4:WinMain

MFC的源代码WinMain.cpp中初始化当前的App对象:

int AFXAPI AfxWinMain (...)

{

CWinApp* pApp = AfxGetApp();

AfxWinInit(...);

pApp->InitApplication();

pApp->InitInstance();

nReturnCode = pApp->Run();

AfxWinTerm();

}

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

_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()

{ return afxCurrentWinApp; }

而afxCurrentWinApp 又定义于AFXWIN.H 中:

#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp

 

Step 5:AfxWinInitAFX 内部初始化动作

AfxWinInit 是继CWinApp 构造式之后的第一个动作。以下是它的动作摘要:

BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPTSTR lpCmdLine, int nCmdShow)

{

ASSERT(hPrevInstance == NULL);

// set resource handles

AFX_MODULE_STATE* pState = AfxGetModuleState();

pState->m_hCurrentInstanceHandle = hInstance;

pState->m_hCurrentResourceHandle = hInstance;

// fill in the initial state for the application

CWinApp* pApp = AfxGetApp();

if (pApp != NULL)

{

// Windows specific initialization (not done if no CWinApp)

pApp->m_hInstance = hInstance;

pApp->m_hPrevInstance = hPrevInstance;

pApp->m_lpCmdLine = lpCmdLine;

pApp->m_nCmdShow = nCmdShow;

pApp->SetCurrentHandles();

}

// initialize thread specific data (for main thread)

if (!afxContextIsDLL)

AfxInitThread();

return TRUE;

}

 

Step 6:CWinApp::InitApplication 基本上不需要改写

 

AfxWinInit 之后的动作是pApp->InitApplicationpApp 指向CMyWinApp对象(也就是本例的theApp),所以,当程序调用:

pApp->InitApplication();

相当于调用:

CMyWinApp::InitApplication();

CMyWinApp 继承自CWinApp,而InitApplication 又是CWinApp 的一个虚拟函数;我们并没有改写它(大部份情况下不需改写它),所以上述动作相当于调用:

CWinApp::InitApplication();

此函数之源代码出现在APPCORE.CPP 中:

BOOL CWinApp::InitApplication()

{

if (CDocManager::pStaticDocManager != NULL)

{

if (m_pDocManager == NULL)

m_pDocManager = CDocManager::pStaticDocManager;

CDocManager::pStaticDocManager = NULL;

}

if (m_pDocManager != NULL)

m_pDocManager->AddDocTemplate(NULL);

else

CDocManager::bStaticInit = FALSE;

return TRUE;

}

这些动作都是MFC 为了内部管理而做的。

Step 7:CMyWinApp::InitInstance 需要自己改写

继InitApplication 之后,AfxWinMain 调用pApp->InitInstance。稍早我说过了,pApp 指向

CMyWinApp 对象(也就是本例的theApp),所以,当程序调用:

pApp->InitInstance();

相当于调用

CMyWinApp::InitInstance();

CMyWinApp 继承自CWinApp,而InitInstance 又是CWinApp 的一个虚拟函数。由于我们改写了它,所以上述动作的的确确就是调用我们自己(CMyWinApp)的这个InitInstance 函数。我们将在该处展开我们的主窗口生命。

Step 8:CFrameWnd::Create 产生主窗口(并先注册窗口类别)

CMyWinApp::InitInstance 一开始new 了一个CMyFrameWnd 对象,准备用作主框窗口的C++ 对象。new 会引发构造式:

CMyFrameWnd::CMyFrameWnd

{

Create(NULL, "Hello MFC", WS_OVERLAPPEDWINDOW, rectDefault, NULL,

"MainMenu");

}

其中Create 是CFrameWnd 的成员函数,它将产生一个窗口,Create函数声明如下:

BOOL Create( 

LPCTSTR lpszClassName,

LPCTSTR lpszWindowName,

DWORD dwStyle = WS_OVERLAPPEDWINDOW,

const RECT& rect = rectDefault,

CWnd* pParentWnd = NULL,

LPCTSTR lpszMenuName = NULL,

DWORD dwExStyle = 0,

CCreateContext* pContext = NULL );

l 第一个参数lpszClassName 指定WNDCLASS 窗口类别。

l 第二个参数lpszWindowName 指定窗口标题,本例指定"Hello MFC"。

l 第三个参数dwStyle 指定窗口风格,预设是WS_OVERLAPPEDWINDOW,也正是最常用的一种,它被定义为(在WINDOWS.H 之中):

#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION |

WS_SYSMENU | WS_THICKFRAME |

WS_MINIMIZEBOX | WS_MAXIMIZEBOX)

l 第四个参数rect 指定窗口的位置与大小。默认值rectDefault 是CFrameWnd的一个static 成员变量,告诉Windows 以预设方式指定窗口位置与大小,就好象在SDK 程序中以CW_USEDEFAULT 指定给CreateWindow 函数一样。如果你很有主见,希望窗口在特定位置有特定大小,可以这么做:

Create(NULL,

"Hello MFC",

WS_OVERLAPPEDWINDOW,

CRect(40, 60, 240, 460), // 起始位置 (40,60),寬 200,高 400

NULL,

"MainMenu");

l 第五个参数pParentWnd 指定父窗口。对于一个top-level 窗口而言,此值应为NULL,表示没有父窗口(其实是有的,父窗口就是desktop 窗口)。

l 第六个参数lpszMenuName 指定菜单,"MainMenu"是RC之中指定好的菜单。

Create函数具体的流程如下所示:

BOOL CFrameWnd::Create(LPCTSTR lpszClassName,

LPCTSTR lpszWindowName,

DWORD dwStyle,

const RECT& rect,

CWnd* pParentWnd,

LPCTSTR lpszMenuName,

DWORD dwExStyle,

CCreateContext* pContext)

{

HMENU hMenu = NULL;

if (lpszMenuName != NULL)

{

// load in a menu that will get destroyed when window gets destroyed

HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);

hMenu = ::LoadMenu(hInst, lpszMenuName);

}

m_strTitle = lpszWindowName; // save title for later

CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,

rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,

pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext);

return TRUE;

}

函数中调用CreateEx。注意,CWnd 有成员函数CreateEx,但其衍生类别CFrameWnd 并无,所以这里虽然调用的是CFrameWnd::CreateEx,其实乃是从父类别继承下来的CWnd ::CreateEx。而CreateEx函数中主要执行以下流程:

...

PreCreateWindow(cs);

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

...

函数中调用的PreCreateWindow 是虚拟函数,CWnd 和CFrameWnd 之中都有定义。由于this 指针所指对象的缘故,这里应该调用的是CFrameWnd::PreCreateWindow::

// CFrameWnd second phase creation

BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)

{

if (cs.lpszClass == NULL)

{

AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);

cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background

}

...

}

 

Step 9:窗口显示与更新

 

CMyFrameWnd::CMyFrameWnd 结束后, 窗口已经诞生出来; 程序流程又回到

CMyWinApp::InitInstance , 于是调用ShowWindow 函数令窗口显示出来, 并调用

UpdateWindow 函数令Hello 程序送出WM_PAINT 消息。

MFC 程序也像SDK 程序一样,有一个GetMessage/DispatchMesage 循环,每个窗口也都有一个窗口函数,并以某种方式进行消息的判断与处理。

 

Step 10CWinApp::Run - 程序生命的活水源头

 

Hello 程序进行到这里,窗口类别注册好了,窗口诞生并显示出来了,UpdateWindow 被

调用,使得消息队列中出现了一个WM_PAINT 消息,等待被处理。现在,执行的脚步到

达pApp->Run。pApp 指向CMyWinApp 对象(也就是本例的theApp),所以,当程序调用:

pApp->Run();

相当于调用:

CMyWinApp::Run();

要知道,CMyWinApp 继承自CWinApp,而Run 又是CWinApp 的一个虚拟函数。我们并没有改写它(大部份情况下不需改写它),所以上述动作相当于调用:

CWinApp::Run();
int CWinApp::Run()
{
if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
{
// Not launched /Embedding or /Automation, but has no main window!
TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting
         application.\n");
AfxPostQuitMessage(0);
}
return CWinThread::Run();
}
int CWinThread::Run()
{
// 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(&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))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE); // not reachable
}
BOOL CWinThread::PumpMessage()
{
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
{
return FALSE;
}
// process this message
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
 


Step 11把消息与处理函数串接在一起:Message Map 机制

 

基本上Message Map 机制是为了提供更方便的程序接口(例如宏或表格),让程序员很方便就可以建立起消息与处理例程的对应关系。MFC 提供给应用程序使用的「很方便的接口」是两组宏。以Hello 的主窗口为例,第一个动作是在HELLO.H 的CMyFrameWnd 加上DECLARE_MESSAGE_MAP:

class CMyFrameWnd : public CFrameWnd

{

public:

CMyFrameWnd();

afx_msg void OnPaint();

afx_msg void OnAbout();

DECLARE_MESSAGE_MAP()

};

第二个动作是在HELLO.CPP 的任何位置(当然不能在函数之内)使用宏如下:

BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)

ON_WM_PAINT()

ON_COMMAND(IDM_ABOUT, OnAbout)

END_MESSAGE_MAP()

这么一来就把消息WM_PAINT 导到OnPaint 函数, 把WM_COMMAND(IDM_ABOUT)导到OnAbout 函数去了。但是,单凭一个ON_WM_PAINT 宏,没有任何参数,如何使WM_PAINT 流到OnPaint 函数呢?

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

以下三种:

■ 标准Windows 消息(WM_xxx)的对映规则:

 

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

ON_COMMAND(<id>,<memberFxn>)

例如:

1.ON_COMMAND(IDM_ABOUT, OnAbout)

2.ON_COMMAND(IDM_FILENEW, OnFileNew)

3.ON_COMMAND(IDM_FILEOPEN, OnFileOpen)

4.ON_COMMAND(IDM_FILESAVE, OnFileSave)

■ 「Notification 消息」(由控制组件产生,例如BN_xxx)的对映机制的宏分为好几种(因为控制组件本就分为好几种),以下各举一例做代表:

 

各个消息处理函数均应以afx_msg void 为函数型式。

综述

① 程序的诞生:

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

■ Afx WinMain 执行AfxWinInit,后者又调用AfxInitThread,把消息队列尽量加大到96。

■ Afx WinMain 执行InitApplication。这是CWinApp 的虚拟函数,但我们通常不改写它。

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

■ CMyWinApp::InitInstance 'new' 了一个CMyFrameWnd 对象。

■ CMyFrameWnd 构造式调用Create,产生主窗口。我们在Create 参数中指定的窗口类别是NULL, 于是MFC 根据窗口种类, 自行为我们注册一个名为"AfxFrameOrView42d" 的窗口类别。

■ 回到InitInstance 中继续执行ShowWindow,显示窗口。

■ 执行UpdateWindow,于是发出WM_PAINT。

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

② 程序开始运作:

■ 程序获得WM_PAINT 消息(藉由CWinApp::Run 中的::GetMessage 循环)。

■ WM_PAINT 经由::DispatchMessage 送到窗口函数CWnd::DefWindowProc 中。

■ CWnd::DefWindowProc 将消息绕行过消息映射表格(Message Map)。

■ 绕行过程中发现有吻合项目,于是调用项目中对应的函数。此函数是应用程序

利用BEGIN_MESSAGE_MAP 和END_MESSAGE_MAP 之间的宏设立起来的。

■ 标准消息的处理例程亦有标准命名,例如WM_PAINT 必然由OnPaint 处理。

③ 以下是程序的死亡:

■ 使用者选按【File/Close】,于是发出WM_CLOSE。

■ CMyFrameWnd 并没有设置WM_CLOSE 处理例程,于是交给预设之处理例程。

■ 预设函数对于WM_CLOSE 的处理方式是调用::DestroyWindow, 并因而发出WM_DESTROY。

■ 预设之WM_DESTROY 处理方式是调用::PostQuitMessage,因此发出WM_QUIT。

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

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

■ 最后回到AfxWinMain,执行AfxWinTerm,结束程序。

你可能感兴趣的:(生命周期,mfc)