2013年1月17日更新-2014年12月3日-2014年12月9日-本章主要是讲MFC的程序和WIN 32SDK程序的相同之处,其实都是包括设计窗口类,注册窗口类,创建窗口,显示和更新窗口,消息循环,以及窗口过程函数。MFC的程序过程在孙鑫的P87也有总结,下次直接看总结就好了,不用再看视频了。
在MFC中,类的命名都以字母“
C
”开头。CAboutDlg类派生于CDialog类,CTestView类派生于CView类,CMainFrame类派生于CFrameWnd类,而CFrameWnd是由CWnd派生的,CView类也是由CWnd类派生的,
CWnd类封装了于窗口相关的操作
。每一个类都有一个基类。
F9键设置断点,F5键调试运行当前程序
。
MFC的入口函数在哪?
winmain函数是所有函数的入口函数,C:\Program Files\Microsoft Visual Studio\VC98\MFC\SRC SRC下面有MFC的源代码,APPMODUL.CPP文件下面有一个WinMain函数,可以找到下面的WinMain函数
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
设置断点后,发现程序在编译连接时,确实要经过_tWinMain,
WinMain函数前面有一个_t, 可以go to definition看到#define _tWinMain WinMain 说明 _tWinMain 是一个宏,就是WinMain.
#define命令将一个指定的标示符(即宏名)来代表一个字符串
。
那么MFC的这些类(CAboutDlg,CTestView,CMainFrame,CTestApp,CTestDoc)是如何和WinMain函数关联的?
无论是全局变量还是全局对象,程序在运行时,在加载main函数之前,就已经为全局变量或者全局对象分配了内存空间。对于一个全局对象来说,此时就会调用该对象的构造函数,构造该对象,并进行初始化操作。
首先需要了解MFC程序的流程顺序,
程序首先到CTestApp theApp(定义类的对象,类名 对象名)这里定义全局对象theApp,然后需要运行派生类CTestApp的构造函数,
the App全局对象表示了应用程序本身,每个MFC程序有且仅有一个从应用程序类(CWinApp)派生的类,每个MFC程序有且仅有一个该派生类实例化的对象,就是the App
但是在运行子类的构造函数前首先需要运行基类CWinApp的构造函数CWinApp()
CWinApp:CWinApp(LPCTSTR lpszAppName) // 知识点:(1)在类外定义构造函数;(2)构造函数的名字和类名相同
{
---------
pModuleState->m_pCurrentWinApp==this;这里this对象代表的是子类CTestApp的对象(the App) pModuleState所指向的对象中的m_pCurrentWinApp成员。
----------
}
当程序调用的CWinApp的构造函数,并执行了CTestApp的构造函数,并且产生了theApp对象后,接下来就是进入WinMain函数,这里WinMain函数是通过AfxWinMain实现的,前缀Afx--代表应用程序框架函数。
int AFXAFI AfxWinMain()
{
CWinThread* pThread=AfxGetThread();
// MSDN中查找,AfxGetThread()是指向当前正在执行的线程的指针,知识点,定义pThread为指向CWinThread类对象的指针变量
CWinApp* pApp=AfxGetApp();// 获取指向当前应用程序的指针,也就是CWinApp的派生类CTestApp的指针,也就是theApp指针,就是上面的this指针
pApp和pThread所指向的实际上是一样,就是派生类CTestApp的指针。
if (pApp!=NULL && !pApp->InitApplication())
//如果pApp为非空,并且pApp所指向的的对象(the)的成员函数InitApplication()为非真时,则到goto InitFailure InitApplication完成MFC内部管理方面的工作。
goto InitFailure
if(!pThread->InitInstance()) //
接着调用pThread所指向对象(The App)的成员函数InitInstance()
nReturnCode = pThread->Run();
}
通过AfxGetThread()这个函数中的代码
if (pThread==NULL)
pThread=AfxGetApp();
Return pThread; 所以AfxGetThread()返回的就是AfxGetApp()的结果
AfxGetApp()这个函数的代码如下
_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
{return afxCurrentWinApp;}
而afxCurrentWinApp的定义如下
#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp //这里m_pCurrentWinApp==this指针,
结论就是AfxGetApp得到的是CWinApp中保存的this指针,这个this指针实际上指向the App这个全局对象。也就是说pThread和pApp都是指向CTestApp类的全局对象the App指针。
InitInstance()后就是注册窗口类,系统已经预设定了一些窗口类,只要选择所需要的窗口类,然后注册就可以了。窗口类的注册是由AfxEndDeferRegisterClass函数来完成的。单文档程序有两个窗口,其中一个是框架窗口,另一个View类窗口。然后PreCreateWindow.然后就是创建窗口,在MFC中创建窗口是由CWin类的CreatEx函数实现的。
m_pMainWnd->ShowWindow(SH_SHOW); 显示应用程序框架窗口 m_pMainWnd 指针类型 指向框架窗口的指针,也就是指向CMainFrame对象的指针
m_pMainWnd->UpdateWindow(); 更新应用程序框架窗口
最后就是消息循环
int CWinThread::Run()
{
if(!PumpMessage())
}
BOOL CWinThread::PumpMessage()
{
if(!::GetMessage(&m_msgCur,NULL,NULL,NULL)
{
}
if()
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
}
wndcls.lpfnWndProc = DefWindowProc; 这行代码的作用是窗口过程函数。
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
这段函数把文档类CTestDoc和视类CMainFrame和CTestView组合在一起。
CAboutDlg类也是一个窗口类,该类是一个无关紧要的类。
窗口类,窗口类对象和窗口的关系
窗口对象和窗口的联系是,窗口对象中保存了和窗口相联系的窗口句柄。当窗口消失的时候窗口对象的生命周期并没有结束。
单文档应用程序都会有CxxxxApp类(是从CWinApp派生来的,表示是一个应用程序类),CxxxxDoc类(文档类),CxxxxView类,CMainFrame框架类,CMainFrame类名字是不会变的,程序运行的时候先运行CxxxxApp theApp定义theApp这个全局变量(对于全局变量来说,在运行winmain函数钱就已经给全局变量赋值,分配内存空间,在执行winmain函数之前就已经分配内存空间,用theApp这个对象来表示这个应用程序),由于定义了全局变量theApp所以要调用CxxxxApp()这个构造函数来给theApp赋值,然后再运行到_tWinMain函数。在APPCORE.CPP文件中可以找到CWinAPP函数,如下:
CWinApp::CWinApp(LPCTSTR lpszAppName) ,可以看到他的构造函数带了一个形参,但是CxxxxApp::CxxxxApp()里面是没有参数的,如果参数有一个缺省值,这样就不用传递形参。pThreadState->m_pCurrentWinThread = this 这里面的this指针指向theApp这个对象。 然后调用InitInstance(),然后注册窗口类,来完成设计窗口类,先创建CMainFrame是框架窗口,然后创建CxxxxView是显示窗口
::函数,表示这个一个WIN 32 平台SDK的函数。如果函数名不同那么也可以不写::,如果函数名和MFC中的函数相同那么需要加上::。
下
class CWnd
{
public:
BOOL CreateEx(DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // registered class name
LPCTSTR lpWindowName, // window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // menu handle or child identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam); // window-creation data
BOOL ShowWindow(int nCmdShow);
BOOL UpdateWindow();
public:
HWND m_hWnd;
};
BOOL CWnd::CreateEx(DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // registered class name
LPCTSTR lpWindowName, // window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // menu handle or child identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam) // window-creation data
{
m_hWnd=::CreateWindowEx(dwExStyle,lpClassName,dwStyle,x,y,
nWidth,nHeight,hWndParent,hMenu,hInstance,
lpParam);
if(m_hWnd!=NULL)
return TRUE;
else
return FALSE;
}
BOOL CWnd::ShowWindow(int nCmdShow)
{
return ::ShowWindow(m_hWnd,nCmdShow); //调用平台SDK的函数
}
BOOL CWnd::UpdateWindow()
{
return ::UpdateWindow(m_hWnd);
}
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
)
{
WNDCLASS wndcls;
wndcls.cbClsExtra=0;
wndcls.cbWndExtra=0;
......
RegisterClass(&wndcls);
CWnd wnd;
wnd.CreateEx(...);
wnd.ShowWindow(SW_SHOWNORMAL);
wnd.UpdateWindow();
HWND hwnd;
hwnd=CreateWindowEx();
::ShowWindow(hwnd,SW_SHOWNORMAL);
::UpdateWindow(hwnd);
......
}当窗口对象析构的时候,应为操作系统要回收窗口对象的资源,所以窗口也销毁了,但是当窗口销毁的时候,窗口对象并没有销毁。也就是说当窗口销毁的时候,窗口类内部的成员函数是可以被窗口对象所调用的。
孙鑫最后关于CButton类的讲解,在MSDN查看一个类的时候,同时需要查看它的成员函数,里面有构造函数,但是我们要使用这个对象,创建一个按钮的时候,还需要调用它的初始化函数Initialization Create,Create函数的作用是把CButton对象和所创建的windows按钮相联系。
在CMainFrame里面有一个OnCreate函数,用在响应创建框架比如,函数里面有创建工具栏,状态栏。涉及到一个知识点就是在成员函数内部创建的对象,局部的对象,当函数执行完后,操作系统就会回收资源,对象就会被销毁。在类里面增加成员变量的方法
可以看到,标题栏和菜单栏都在非客户区,而工具栏是在客户区的。
在VC++中添加某个消息的处理函数的方法
在单文档对话框中,用GetParent()函数可以获得父窗口的指针,单文档对话框的父窗口就是CMainFrame类对象创建的那个框架窗口。
此外还有一点是关于CWnd::ShowWindow函数和平台SDK中的ShowWindow函数的区别,可以看到CWnd类成员的ShowWindow函数并不需要传递hWnd窗口句柄,应为hWnd是CWnd的成员变量,
CWnd::ShowWindow已经默认传递了这个窗口句柄了。
CWnd::ShowWindow
BOOLShowWindow(int
nCmdShow
);
Return Value
Nonzero if the window was previously visible; 0 if the
CWnd
was previously hidden.
ShowWindow
The
ShowWindow
function sets the specified window's show state.
BOOL ShowWindow( HWND
hWnd
,
// handle to window
int
nCmdShow
// show state
);