建立Single document MFC应用程序,向导会为我们自动生成代码,其中有五个重要的类需要注意:
这五个类中后三个名字中的Test是项目名称,会随着项目名字变化而变化。
这里涉及到的基类的继承关系可以从下图看出:
最终继承自窗口类(CWnd)的类都和窗口实现有关,CAboutDlg负责帮助对话框,CMainFrame负责主框架窗口,CTestView负责视窗口(在主框架窗口之上)。
在一个MFC项目中仅有一个类派生自应用程序类(CWinApp),这个类的名字由C+项目名+App构成。
我们知道在Win32应用程序中通过一个实例句柄(hInstance)来唯一标识应用程序本身,MFC中则有所不同,它通过一个**应用程序类的全局对象(theApp)**来标识应用程序本身。
MFC程序运行的第一件事就是创建CTestApp类的实例(theApp):
//Test.CPP
//创建应用程序实例对象
CTestApp theApp;
//CTestApp构造函数
CTestApp::CTestApp()
{
}
创建theApp的同时会调用CTestApp的构造函数,根据C++继承相关的概念,在调用CTestApp的构造函数之前还会先调用基类CWinApp的构造函数,基类构造函数部分代码如下:
//APPCORE.CPP
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
if (lpszAppName != NULL)
m_pszAppName = _tcsdup(lpszAppName);
else
m_pszAppName = NULL;
// initialize CWinThread state
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;//this指向theApp对象
ASSERT(AfxGetThread() == this);
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
ASSERT(afxCurrentWinApp == NULL);
pModuleState->m_pCurrentWinApp = this;//this指向theApp对象
ASSERT(AfxGetApp() == this);
......
基类CWinApp构造函数具有一个lpszAppName参数,因此在派生类中应该显式调用。然而由于该构造函数中声明了默认参数,所以实际上并没有显示调用。应用程序类的构造函数完成了应用程序的各种初始化工作。
当theApp全局对象成功创建以后就要进入WinMain的环节了,该部分代码如下:
//APPMODUL.CPP
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)//入口函数
{
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
这部分代码和Win32中的WinMain并没有什么太大的区别,_tWinMain前面第一篇文章已经介绍过是一个兼容UNICODE和ASCII两种情况的宏。在WinMain函数中调用了AfxWinMain函数,MFC中以Afx开头的函数是应用程序框架函数,他们都是全局函数,可以在任意一个类中调用,此处AfxWinMain函数负责创建,注册窗口类,创建,显示,更新窗口等等工作。
AfxWinMain中分别调用AfxGetThread函数和AfxGetApp函数取得了两个指针pThread和pApp,然后又通过这两个指向theApp的指针调用了三个成员函数完成了一个Windows应用程序的全部必要步骤,部分代码如下:
//WINMAIN.CPP
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();//指向theApp的指针
CWinApp* pApp = AfxGetApp();//指向theApp的指针
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
if (pApp != NULL && !pApp->InitApplication())//重要函数1,完成MFC内部管理方面的工作
goto InitFailure;
if (!pThread->InitInstance())//重要函数2,该函数是一个虚函数,因此这里调用的是子类的函数
{
if (pThread->m_pMainWnd != NULL)
{
TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();//重要函数3,完成消息循环
通过pThread调用了虚函数InitInstance,因此实际调用的是子类版本,该函数中完成了窗口类的创建和注册,窗口的创建,显示,更新。跟踪该函数的代码,在其中会发现下面两行:
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
很显然,这两行会进行窗口的显示和更新,在本文的后面还会再次提到。
注册窗口类是在一个叫做CMainFrame::PreCreateWindow的函数中完成的,该函数在窗口产生之前被调用,其代码大概如下:
//MAINFRM.CPP
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )//进一步调用了其基类的PreCreateWindow函数
return FALSE;
return TRUE;
}
然后进入被调用的函数CFrameWnd::PreCreateWindow,其代码如下:
//MAINFRM.CPP
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));//这里验证了窗口类是否注册,如果没有则注册
cs.lpszClass = _afxWndFrameOrView;
}
if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4)
cs.style |= FWS_PREFIXTITLE;
if (afxData.bWin4)
cs.dwExStyle |= WS_EX_CLIENTEDGE;
return TRUE;
}
上面这个函数调用AfxDeferRegisterClass来注册窗口类,实际上它是一个到AfxEndDeferRegisterClass函数的宏,AfxEndDeferRegisterClass的实现如下:
//WINCORE.CPP
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
fToRegister &= ~pModuleState->m_fRegisteredClasses;
if (fToRegister == 0)
return TRUE;
LONG fRegisteredClasses = 0;
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS));
wndcls.lpfnWndProc = DefWindowProc;//给窗口类设置窗口过程函数
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
INITCOMMONCONTROLSEX init;
init.dwSize = sizeof(init);
if (fToRegister & AFX_WND_REG)
{
// Child windows - no brush, no icon, safest default class styles
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpszClassName = _afxWnd;
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WND_REG;
}
if (fToRegister & AFX_WNDOLECONTROL_REG)
{
// OLE Control windows - use parent DC for speed
wndcls.style |= CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpszClassName = _afxWndOleControl;
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WNDOLECONTROL_REG;
}
if (fToRegister & AFX_WNDCONTROLBAR_REG)
{
// Control bar windows
wndcls.style = 0; // control bars don't handle double click
wndcls.lpszClassName = _afxWndControlBar;
wndcls.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
if (AfxRegisterClass(&wndcls))
fRegisteredClasses |= AFX_WNDCONTROLBAR_REG;
}
if (fToRegister & AFX_WNDMDIFRAME_REG)
{
// MDI Frame window (also used for splitter window)
wndcls.style = CS_DBLCLKS;
wndcls.hbrBackground = NULL;
if (_AfxRegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
}
if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
{
// SDI Frame or MDI Child windows or views - normal colors
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
}
if (fToRegister & AFX_WNDCOMMCTLS_REG)
{
// this flag is compatible with the old InitCommonControls() API
init.dwICC = ICC_WIN95_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WIN95CTLS_MASK);
fToRegister &= ~AFX_WIN95CTLS_MASK;
}
if (fToRegister & AFX_WNDCOMMCTL_UPDOWN_REG)
{
init.dwICC = ICC_UPDOWN_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_UPDOWN_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_TREEVIEW_REG)
{
init.dwICC = ICC_TREEVIEW_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TREEVIEW_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_TAB_REG)
{
init.dwICC = ICC_TAB_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TAB_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_PROGRESS_REG)
{
init.dwICC = ICC_PROGRESS_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_PROGRESS_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_LISTVIEW_REG)
{
init.dwICC = ICC_LISTVIEW_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_LISTVIEW_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_HOTKEY_REG)
{
init.dwICC = ICC_HOTKEY_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_HOTKEY_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_BAR_REG)
{
init.dwICC = ICC_BAR_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_BAR_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_ANIMATE_REG)
{
init.dwICC = ICC_ANIMATE_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_ANIMATE_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_INTERNET_REG)
{
init.dwICC = ICC_INTERNET_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_INTERNET_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_COOL_REG)
{
init.dwICC = ICC_COOL_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_COOL_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_USEREX_REG)
{
init.dwICC = ICC_USEREX_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_USEREX_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_DATE_REG)
{
init.dwICC = ICC_DATE_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_DATE_REG);
}
// save new state of registered controls
pModuleState->m_fRegisteredClasses |= fRegisteredClasses;
// special case for all common controls registered, turn on AFX_WNDCOMMCTLS_REG
if ((pModuleState->m_fRegisteredClasses & AFX_WIN95CTLS_MASK) == AFX_WIN95CTLS_MASK)
{
pModuleState->m_fRegisteredClasses |= AFX_WNDCOMMCTLS_REG;
fRegisteredClasses |= AFX_WNDCOMMCTLS_REG;
}
// must have registered at least as mamy classes as requested
return (fToRegister & fRegisteredClasses) == fToRegister;
}
首先,该函数接受了一个参数fToRegister,这个参数是由很多标志位通过或运算组合起来的一个整数,他代表着想要注册的全部类。代码的第一部分将已经注册的类的标志位从fToRegister中删除,然后通过大量的分支结构来判断某个预定义类是否在带注册类列表中,如果在就调用函数AfxRegisterClass将其注册,代码看起来很长,但大部分都是同一功能的简单重复。
AfxRegisterClass中包含下面的代码:
//WINCORE.CPP
if (!::RegisterClass(lpWndClass))//注册窗口类
{
TRACE1("Can't register window class named %s\n",
lpWndClass->lpszClassName);
return FALSE;
}
里面的RegisterClass函数我们很熟悉,这个函数就是在Win32中我们用来注册窗口类的函数。
注册号窗口类之后就该创建窗口了,这个功能最终是由CWnd::CreateEx来完成的,而这个函数则由Cwnd的派生类CFrameWnd中的方法CFrameWnd::Create来调用
//WINCORE.CPP
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;
}
该函数中调用了Windows API函数**::CreateWindowEx**来创建窗口,实际上在调用这个函数之前,还有一次调用了PreCreateWindow函数,目的是为了让用户可以在创建窗口前有机会修改窗口的外观样式。CreateEx函数在应用程序执行过程中会执行很多次,因为一个单文档MFC应用程序中包含很多个窗口,每个窗口的创建都是依靠这个函数完成的。
然后就是显示和更新窗口,这部分内容在CTestApp::InitInstance中完成,上面已经介绍过。
前面在看AfxWinMain函数实现的时候提到过里面通过两个指针调用了三个函数完成了应用程序的全部初始化创建过程,里面说的第三个函数就是CWinThread::Run,他的定义如下:
//THRDCORE.CPP
int CWinThread::Run()
{
ASSERT_VALID(this);
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
for (;;)//进入死循环
{
while (bIdle &&
!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))//从消息队列取消息
{
if (!OnIdle(lIdleCount++))
bIdle = FALSE;
}
//在接受到WM_QUIT时候退出
do
{
if (!PumpMessage())//PumpMessage中调用了很熟悉的GetMessage()
return ExitInstance();
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE);
}
上面这部分就是消息循环的代码,他主体就是一个for循环,该循环内部调用PumpMessage,这个函数内部先通过GetMessage取消息,然后TranslateMessage和DispatchMessage来翻译和派送消息,这个过程和Windows API的实现过程并没有区别。
在前面AfxEndDeferRegisterClass函数设计窗口类时,我们看到了窗口类绑定到了默认窗口过程,所以这里不再粘贴代码,指得注意的是看起来MFC完全通过默认窗口过程响应消息,但实际上使用的是消息映射机制,这个后面再说。
为了让窗口中显示一个按钮,就应该响应响应窗口的WM_CREATE事件,在MFC中响应该消息的方法就是编写窗口类的onCreate()函数,以创建按钮为例,代码如下:
private:
CButton btn;//给主框架窗口类添加一个成员变量用于保存按钮
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
......//这里略去很多自动生成的代码
//下面是用户自己编写的代码
btn.Create("按钮", WS_CHILD|BS_DEFPUSHBUTTON, CRect(0,0,100,100),this,123);
btn.ShowWindow(SW_SHOWNORMAL);
return 0;
}
注意按钮类的对象不能在消息响应函数中创建,这样会导致函数结束之后按钮对象析构。
注意下C++窗口类的对象和窗口本身并不是一个东西,他们之间的关系是窗口类对象中定义的窗口句柄变量保存了与这个C++窗口类相关的窗口的句柄。当窗口销毁时,窗口类对象是否销毁取决于该对象的生存期是否结束。窗口类对象销毁时,其内部的局部变量也会被回收,所以窗口也会跟着销毁(被析构函数析构)。
总体来说MFC是早已过时的技术,这里只是因为好奇随便看看,应该不会一直更新。