电子常识
摘要:本文章主要以MFC程序的执行流程、执行顺序等执行过程的剖析做出的结论,下面一起来看看原文的具体介绍。
微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。
1)我们知道在WIN32API程序当中,程序的入口为WinMain函数,在这个函数当中我们完成注册窗口类,创建窗口,进入消息循环,最后由操作系统根据发送到程序窗口的消息调用程序的窗口函数。而在MFC程序当中我们不在能找到类似WinMain这样的程序入口,取而代之的是一系列派生类的声明和定义以及一个冲CWinApp类派生而来的类的全局对象。CWinApp类被称之为应用程序对象,在一个MFC程序当中只允许有一个应用程序对象。由于CWinApp的派生对象是全局的,因此这个对象的构造函数会在所有的其他代码运行之前被调用,而由于CWinApp类当中包含了HWND、HINSTANCE等句柄的存在,其构造函数就执行了对这些成员数据的初始化操作,这里的所谓初始化仅仅是把所有的句柄对象赋值为NULL。
2)在调用完CWinApp的构造函数以后由连接器向程序内自动链接的AfxWinMain函数将被调用,而这个函数可以被看作MFC程序的入口函数。在这个函数当中调用全局AfxGetApp()函数获得应用程序对象,这时将调用AfxInit全局函数,这个函数的功能是使用操作系统传递给AfxWinMain函数的参数初始化应用程序对象当中的相关句柄数据成员。
3)之后AfxWinMain函数调用CWinApp::InitApplication成员函数,这个成员函数用来初始化应用程序对象当中的关于文档部分的内容。
4)随后调用CWinApp::InitInstance成员函数,在这个成员函数当中,使用new操作在堆上声明一个框架窗口对象,由此导致框架窗口对象的构造函数被调用,在框架窗口构造函数当中调用Create函数来创建窗口,而调用的Create函数一般将WNDCLASS参数设置成NULL,这样就由MFC内部调用PreCreateWindow函数,在这个函数当中由MFC注册几个默认的WNDCLASS供框架窗口的Create使用。这时程序控制权交还给CWinApp::InitInstance成员函数内部,由这个函数调用CWnd::ShowWindow显示窗口并且调用CWnd::UpdateWindow向窗口发送WM_PAINT消息。调用完CWinApp::InitInstance成员函数后由AfxWinMain函数调用CWinApp::Run成员函数,并由这个函数来创建和处理消息循环,并且在没有消息的时候处理OnIdle空闲处理。至此整个程序的创建过程完成。
5)在程序的运行过程当中,由操作系统源源不断的发送消息给应用程序,并且由CWinApp::Run当中的消息循环处理并且分发给相关的窗口对象的DefWindowProc成员函数,并由这个成员函数查询窗口对象的消息映射表,如果查到对应项,则由登记在消息映射表当中的类成员函数处理,否则则按照Message Route当中的顺序象父层类发送。
6)在消息运行结束,用户按下关闭按钮后,操作系统向程序发送WM_CLOSE消息,默认状况下程序调用DestoryWindow并且发送WM_DESTORY消息,应用程序接受到这个消息以后的默认操作是调用PostQuitMessage函数,由这个函数发送WM_QUIT消息。当程序对象接受到WM_QUIT消息后消息循环结束,由AfxWinMain函数调用AfxTerm函数清理程序使用过的资源并且结束整个程序。
MFC只是对WIN32的API进行了封装,所以MFC的本质还是WIN32程序。有了这层封装,我们看不到WIN32的WinMain函数,也就不清楚MFC程序的启动过程。虽然我们没有看到WinMain函数,但不代表没有WinMain函数,这个函数位于*\VC\atlmfc\src\mfc目录的appmodul .cpp文件中有一个_tWinMain函数, _tWinMain函数调用了WinMain.CPP文件中的AfxWinMain函数。
tWinMain函数实现:
extern “C” int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
__in LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}1234567
AfxWinMain函数实现:
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
__in LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
// App global initializations (rare)
if (pApp != NULL && !pApp-》InitApplication())
goto InitFailure;
// Perform specific initializations
if (!pThread-》InitInstance())
{
if (pThread-》m_pMainWnd != NULL)
{
TRACE(traceAppMsg, 0, “Warning: Destroying non-NULL m_pMainWnd\n”);
pThread-》m_pMainWnd-》DestroyWindow();
}
nReturnCode = pThread-》ExitInstance();
goto InitFailure;
}
nReturnCode = pThread-》Run();
InitFailure:
#ifdef _DEBUG
// Check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()-》m_nTempMapLock != 0)
{
TRACE(traceAppMsg, 0, “Warning: Temp map lock count non-zero (%ld)。\n”,
AfxGetModuleThreadState()-》m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm();
return nReturnCode;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445
_tWinMain和WinMain的声明是一致的,但是_tWinMain不是最先执行的,因为整个程序一开始是初始化全局变量,这里的全局变量有WinApp类型的theApp,初始化theApp就是执行CWinApp的构造函数。在这个AfxWinMain函数里面调用了pThread-》InitInstance()函数,InitInstance函数是CWinApp类(CWinApp继承于CWinThread)的虚函数,所以这里就调用派生类的InitInstance函数,用户一般在这个函数里面创建对话框,单文档,多文档界面。执行完InitInstance函数后,就会执行CWinApp类的run函数(*\VC\atlmfc\src\mfc\thrdcore.cpp),这个函数一个死循环,不断地从的消息队列中读取消息。代码如下:
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));
}
}123456789101112131415161718192021222324252627282930313233343536373839
小结
MFC程序的执行顺序和Win32的执行顺序完全一样:初始化全局变量(theApp),执行WinMain函数(AfxWinMain),创建及注册窗口(在InitInstance函数中创建对话框,单文档,多文档窗口),消息循环(Run函数)
对于理解MFC程序执行流程,我觉得理解new 、虚拟函数调用、构造和析构函数调用(http://blog.csdn.net/misskissc/article/details/8549254)次序、类指標结论几个地方对此很有帮助。刚好在学习MFC程序的过程中也正好去学习了这几个方面(或者说是先去查询式的学习了这几个方面之后觉得对理解MFC程序执行流程有帮助)。
new和malloc函数都可以为变量申请一段某种类型的动态存储空间,new在申请动态空间外还会引发申请空间对象构造函数的运行。当然,有new就需要在需要的地方有delete,这是一种必备的习惯。
使用派生类对象访问类的成员函数时,若此函数为非虚拟函数时,访问的函数都是基类成员函数的地址;若访问的成员函数为虚拟函数(前面加了virtual关键字)时,若在派生类中没有申明(修改)则调用的是基类(或者某派生类(当此派生类改变了此虚拟函数时))中的函数地址,若在派生类修改了虚拟函数,则调用的就是派生类的成员函数。
构造函数和析构函数的调用次序从一定程度上反应出MFC程序执行流程。建构式(运行应用程序类的对象的构造函数)是早于程序运行入口点的,即应用程序类对象的构造函数的运行要比AfxWinMain函数的运行要早。当然,析构函数是在相应情况下(delete发生、局部运行完毕、程序运行结束时)发生。而且构造函数的运行遵循一定的方式(构造函数析构函数调用次序),析构函数的运行与构造函数的运行相反。
如果以一个基础类别之指标指向衍生类别之物件,那么该指标只能呼叫基础类所定义的函式。如果一个衍生类指标指向一个基础类别物件,必须先做明确的转型动作(对基础类之物件取地址&对派生类指标赋值)。这种做法很危险。如果基础类别和衍生类别都定义了相同名之成员函式,那么透过物件指标呼叫成员函式时,到底呼叫到哪一个函式,必须视该指标原始性别而定,而不是视指标实际所指之物件的性别而定。
撇开所有的头文件中对类声明、派生的代码,我们到应用程序类源代码文件(CMy*.cpp)定义应用程序类对象的地方,见图1
图1.《深入浅出 MFC》一书中的MFC程序执行流程分析图
MFC程序执行流程跟标号增大的方向一致:
当定义应用程序类对象时,在程序进入AfxWinMain()函数前,应用程序类对象的构造函数先运行,构造应用程序类对象。
进入程序运行的入口点AfxWinMain()函数中,AfxGetApp()函数返回指向当前应用程序的单一的CWinApp对象的指针。根据类指標相关结论可知,除了被修改的虚拟函式外,用指针pApp访问的函数都是访问的CWinApp中的成员函式。所以用pApp-》InitApplication() [及Run] 调用的函式为CWindApp::InitApplication() [及CWindApp::Run()] ;因为InitIntance()函数被改变,因此而pApp-》InitIntance()调用的却是派生类中(假设为CMyWindApp)的函式(CMyWindApp::InitIntance()),而且InitIntance()函数内的内容会被自动执行,一般这里的代码为跟生成窗口有关。
这里是关于初始化的函数,作为初学者,这里可以先跳过。容日后究。
这就是在调用派生类CMyWindApp的成员函数,虽然这个调用的语句我们看不到。这里涉及虚拟函数妙用的内容哦,派生类中被修改的虚拟函数将会被调用。
(5-8语句)这是在对虚拟函数InitIntance()进行改写,在改写的过程中很可能依据函数的调用方式(虚拟函数,非虚拟函数的调用方式)再调用其它的函数。其实函数的调用占MFC应用程序执行流程的很大一部分,对于函数的调用,这里添加了一些规则,如此就构成了整个的运行。
(9语句)pApp-》Run()就是检测是否有消息发生的主消息循环事件函数,当有任何规定的事件发生它都会检测到,并通过一定方式调用到相应的函数,具体内容可以后深究。
这个过程只是在定义应用程序类对象时MFC内部机制的简单剖析,其实在接收到用户信息、调用用响应的事件函数(DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC巨集)等这些过程都是很有研究价值的,对咱初学者来说就先了解一个大概,最开始就要把这些原理弄得很清楚还是很费功夫的!如此,就找到了MFC程序入口及消息循环的基本原理了,至于MFC程序如何响应事件及另外的一些重要性质就让我们日后挖掘吧。
全部0条评论
快来发表一下你的评论吧 !