Step 1:CWinApp-取代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:AfxWinInit-AFX 内部初始化动作
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->InitApplication。pApp 指向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 10:CWinApp::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,结束程序。