我们通过写一个简单的MFC程序来认识这个机制
1.建一个win32简单应用程序,不要认为这样就不能写出MFC程序,因为是不是MFC程序取决于调没调MFC函数。
2. 删除入口函数,只留下#include "stdafx.h"3.将stdafx.h中的头文件 <windows.h> 更改为 <afxwin.h>。
4.Project-->Settings菜单项中设置使用MFC库,如下图所示
5.编写代码:写一个最简单的窗口程序,通过它来分析程序起动机制
// MFCCreate.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
class CMyFrameWnd: public CFrameWnd
{
};
class CMyWinApp:public CWinApp
{
public:
CMyWinApp();
virtual BOOL InitInstance();
};
CMyWinApp::CMyWinApp()
{
}
CMyWinApp theApp;//全局对象
BOOL CMyWinApp::InitInstance()
{
CMyFrameWnd *pFrame = new CMyFrameWnd;
pFrame->Create(NULL,"MFCBase");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
运行结果:生成一个窗口,那么是怎么运行起来的,我们来通过加断点跟进微软内部函数来分析
1. 在theApp这行设置断点,以调试方式进入到CWinApp的构造函数中,写出这段函数的伪代码进行分析(按f11进入函数中,f10执行下一步)
//aaa和bbb是微软定义好的两个全局变量,只不过找不到在哪定义着。
AFX_MODULE_STATE aaa;//全局变量(全称叫当前程序模块状态信息)
AFX_MODULE_THREAD_STATE bbb;//全局变量(当前程序线程状态信息)
CWinApp::CWinApp(...)
{
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
//获取全局变量 &aaa
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
//获取全局变量 &bbb,bbb的地址保存在了aaa的成员一个变量里
pThreadState->m_pCurrentWinThread = this;
//将 &theApp 保存到 全局变量bbb的一个成员中。(this是指 &theApp )
AfxGetThread()//f11进入AfxGetThread()函数
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
//获取全局变量 &bbb
CWinThread* pThread = pState->m_pCurrentWinThread;
//取出保存在 bbb 中的 &theApp
return pThread; //返回 &theApp
}
pModuleState->m_pCurrentWinApp = this;
//将 &theApp 保存到 全局变量aaa的一个成员中
AfxGetApp()
{
return AfxGetModuleState()->m_pCurrentWinApp;
//返回 &theApp
}
}
2. 断点加到CMyFrameWnd *pFrame = new CMyFrameWnd;这行,利用call stack(调用堆栈)查看函数之间的调用关系(双击可以定位到该函数中),view->debug window->call stack,自己跟到AfxWinMain函数内部,写出伪代码
WinMain(...)
{
AfxWinMain(...)
{
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
//以上两句代码获取 &theApp
AfxWinInit(...);//初始化MFC库
pApp->InitApplication()
//CWinApp类成员虚函数,初始化应用程序。
pThread->InitInstance()//创建、显示窗口
{
//回到自己的代码
}
pThread->Run()//函数内部this指针为 &theApp
{
CWinThread::Run()//函数内部this指针为 &theApp
{
while( 没有消息 )
{
OnIdle(..)//CWinApp类的成员虚函数(空闲处理)
}
PumpMessage()//CWinApp类的成员虚函数(提取/翻译/派发 消息)
{
if( ::GetMessage 抓到 WM_QUIT )
return FALSE;
}
if( PumpMessage函数 返回FALSE )
ExitInstance();//CWinApp类的成员虚函数(善后处理)
}
}
}
}
上边两段伪代码需要自己熟悉vc6.0的断点调试和调用堆栈,再有就是细心和耐心了,如果实在跟不下去也没关系,下面我会总结上边段伪代码究竟在干啥。
总结上边程序的执行流程,也就是程序启动机制的原理:
1 构造 theApp 对象,执行CWinApp::CWinApp
将theApp对象地址 保存到 当前程序模块状态信息中
将theApp对象地址 保存到 当前程序线程状态信息中
AfxGetThread / AfxGetApp - 返回theApp对象地址
2 进入入口函数,调用AfxWinMain
2.1 获取 theApp 对象地址
2.2 利用 theApp 调用应用程序类的成员虚函数 InitApplication(初始化应用程序)。
2.3 利用 theApp 调用应用程序类的成员虚函数InitInstance(注册窗口/创建并显示窗口)
2.4 利用 theApp 调用应用程序类的成员虚函数Run(消息循环)
1)如果没有消息利用 theApp 调用应用程序 类的成员虚函数OnIdle(空闲处理)
2)如果程序退出利用 theApp 调用应用程序类的成员虚函数ExitInstance(善后处理)
通俗的将就是我们自己实例化一个应用程序对象,被WinMain抓到后执行一系列函数
思考:
1. MFC 中的所有类都没定义对象,需要自己定义然后调用比如这里的theApp。
2. 事实上我们写的代码只是整个流程中的一小部分,只是重写了InitInstance函数
3.微软在几十年前些的代码就能拿到我现在自己定义的全局变量的地址,为什么???
答:是通过构造函数+this指针做到的,所以说构造函数的功能不止我们之前了解的那么简单,这种微软大牛的方法不得不让人佩服和学习啊。
4.从上边的结论可以看出 程序的流程是由CWinApp负责的,它的成员虚函数:
InitApplication ()
InitInstance ()
Run()
OnIdle()
ExitInstance()
成员变量 : m_pMainWnd - 是用来保存 自己new的 框架类对象地址
我们可以通过重写这些虚函数,实现不同的功能,下边将给出重写代码,当然只是用提示框做了个模拟:
给CMyWinApp类 添加成员变量 :
virtual BOOL InitApplication();//初始化应用程序
virtual int Run();//消息循环
virtual BOOL OnIdle(LONG lCount);//空闲处理
virtual BOOL ExitInstance(); //善后处理
实现:
BOOL CMyWinApp::ExitInstance()
{
AfxMessageBox("CMyWinApp::ExitInstance");
return CWinApp::ExitInstance();
}
BOOL CMyWinApp::OnIdle(LONG lCount)
{
// AfxMessageBox("CMyWinApp::OnIdle");
return CWinApp::OnIdle(lCount);
}
BOOL CMyWinApp::Run()
{
AfxMessageBox("CMyWinApp::Run 点掉就就消息循环了");
return CWinApp::Run();
}
BOOL CMyWinApp::InitApplication()
{
AfxMessageBox("CMyWinApp::InitApplication");
return CWinApp::InitApplication();
};