目录
MFC 程序启动
MFC 入口函数
程序执行流程总结
在Win32课程中WinMain由程序员自己实现,那么流程是程序员安排,但到了MFC中,由于MFC库实现WinMain,也就意味着MFC负责安排程序的流程。
MFC 程序启动
程序的启动,构造theApp对象,调用父类CWinApp的构造函数。
- 将theApp对象的地址保存到线程状态信息中
- 将theApp对象的地址保存到模块状态信息中
- 进入WinMain函数,调用AfxWinMain函数
在应用类下断点,并且打开函数调用堆栈
摁下F11,CMyWinApp(){} 构造函数调用,会先调用 CMyWinApp 类的父类 CWinApp 类构造函数,在调用爷爷类 CThread
爷爷类 CThread 只是做一些初始化的操作,不做关注,直接看 CWinApp 的构造函数
这两个都是类,都定义在全局:
- AFX_MODULE_STATE 当前程序模块状态信息
- AFX_MODULE_THREAD_STATE 当前程序线程状态信息
//获取当前程序模块状态信息
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
//获取当前程序线程状态信息
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
// 将 &theApp 保存到 当前程序线程状态信息 的一个成员中
pThreadState->m_pCurrentWinThread = this;
一个断言(assertion),用于在代码中进行调试和错误检测。在这行代码中,它检查AfxGetThread()
返回的线程指针是否等于当前的线程指针this
,如果不相等,则会触发断言失败,打印相关的错误信息并中断程序的执行。
ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);
AfxGetThread() 函数 返回的为&theApp
CWinThread* AFXAPI AfxGetThread()
{
// check for current thread in module thread state
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
CWinThread* pThread = pState->m_pCurrentWinThread;
return pThread;
}
这两行代码看不明白?
可以在前面补上this->,发现 this 是 CMyWinApp 的对象,这两个成员变量应该是继承父类来的
获取当前线程的伪句柄,获取当前线程的ID
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
所以 AfxGetApp() 返回&theApp
ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
总结:
- CWinApp,封装了应用程序、线程等信息
- CMyWinApp继承自CWinApp 负责程序的运行
- CMyWinApp theApp; 程序就开始执行,并且调用构造函数,依次为祖宗类到本类,只需要关注父类 CWinApp 构造函数即可
- CWinApp::CWinApp(LPCTSTR lpszAppName),在调用时会自动传递参数并且为空,主要逻辑未带如下
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
m_pszAppName = NULL; // 应用程序的名称 赋值为空
/* 下面是初始化当前的执行线程,也就是主线程了 */
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE(); // 获取当前程序模块状态信息
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread; // 当前程序线程状态信息,这个类也是当前程序模块信息类的成员类
pThreadState->m_pCurrentWinThread = this; // 把&theapp赋值给当前程序线程信息的成员编程
m_hThread = ::GetCurrentThread(); // 获取当前线程的伪句柄
m_nThreadID = ::GetCurrentThreadId(); // 获取当前线程的id
/* 初始化应用类状态 */
pModuleState->m_pCurrentWinApp = this; // 把&theapp赋值给当前程序线程信息的成员编程
/*剩下的就是初始化一些其他的成员变量*/
}
其他调用到的一些函数:
- AfxGetModuleState() :调用此函数获取一个指针,指向AFX_MODULE_STATE
- AfxGetThread():调用此函数获取一个指针,指向表示当前执行线程的 CWinThread 对象,&theapp
- GetCurrentThread():获取当前线程伪句柄
- AfxGetApp():调用此函数获取一个指针,指向AFX_MODULE_STATE,&theapp
综上所述,应用程序类的构造函数主要做一些初始化的操作
MFC 入口函数
进入入口函数WinMain
- 获取应用程序类对象theApp的地址
- 利用theApp地址调用InitApplication,初始化当前应用程序的数据
- 利用theApp地址调用InitInstance函数初始化程序,在函数中我们创建窗口并显示。
- 利用theApp地址调用CWinApp的Run函数进行消息循环
- 如果没有消息,利用theApp地址调用OnIdle虚函数实现空闲处理
- 程序退出利用theApp地址调用ExitInstance虚函数实现退出前的善后处理工作
给函数 InitInstance 下断点
查看调用堆栈
补充一点:应用程序句柄和窗口句柄的关系?
应用程序句柄(HINSTANCE)用于标识一个特定的应用程序实例,它通常在应用程序启动时由操作系统分配。应用程序句柄主要用于访问应用程序的资源,例如图标、位图、对话框模板等。
窗口句柄(HWND)用于标识一个窗口,它是在窗口被创建时由操作系统分配的。窗口句柄允许应用程序与窗口进行交互,比如显示、隐藏、关闭、重绘等操作。
在关系上,应用程序句柄和窗口句柄可以是相互独立的。一个应用程序实例可以拥有多个窗口,每个窗口都有自己的窗口句柄,但它们共享相同的应用程序句柄。因此,应用程序句柄通常用于全局资源的管理,而窗口句柄用于特定窗口的操作。
查看 wWinMain 函数,函数功能主要实现是交给 AfxWinMain 实现的
下面是 AfxWinMain的伪代码
这两代码都是获取&theApp
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
AfxWinInit 函数与初始化MFC有关
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
利用theApp对象调用应用程序类成员虚函数 初始化
if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
利用theApp对象调用应用程序类成员虚函数 创建并显示窗口
这就和我们在 CMyWinApp 中重写的虚函数对上了,后面的是未初始化成功的处理
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;
}
利用theApp对象调用应用程序类成员虚函数 消息循环
nReturnCode = pThread->Run();
接下来进入 Run() 函数看看
m_pMainWnd 是 CWinThread 类的一个成员变量,是指向线程的主窗口对象的指针
也就是说 if 语句块是处理错误的,return 处调用父类的Run函数
int CWinApp::Run()
{
if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
{
// Not launched /Embedding or /Automation, but has no main window!
TRACE(traceAppMsg, 0, "Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.\n");
AfxPostQuitMessage(0);
}
return CWinThread::Run();
}
接下来就是真的消息循环的位置
突然看到这里有点懵,AfxGetThreadState() 函数,全局函数,调用此函数可获取指向表示当前正在执行的线程的 CWinThread 对象的指针。必须从所需的线程内调用。
_AFX_THREAD_STATE* pState = AfxGetThreadState();
检查线程消息队列中是否存在已发布的消息,
如果没有消息,做空闲处理,利用theApp对象调用应用程序类成员虚函数 空闲处理
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
}
PumpMessage实际上调用的是 AfxInternalPumpMessage() 函数
程序结束前,利用theApp对象调用应用程序类成员虚函数 善后处理。
if (!PumpMessage())
return ExitInstance();
当窗口收到 WM_QUIT消息,就返回会FASLE,否则返回TRUE
if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
{
#ifdef _DEBUG
TRACE(traceAppMsg, 1, "CWinThread::PumpMessage - Received WM_QUIT.\n");
pState->m_nDisablePumpCount++; // application must die
#endif
// Note: prevents calling message loop things in 'ExitInstance'
// will never be decremented
return FALSE;
}
if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
{
::TranslateMessage(&(pState->m_msgCur));
::DispatchMessage(&(pState->m_msgCur));
}
return TRUE;
程序执行流程总结
程序启动后,会调用构造函数初始化应用程序类的对象,以及其父类(主要)
- 初始化 当前模块程序信息 以及 当前模块程序线程信息
- 其他成员变量的初始化
调用 AfxWinMain 函数,执行程序流程
- 初始化 MFC 以及 应用程序
- 调用 InitInstance 完成窗口的注册创建显示
- 调用爷爷类 CWinThread 的Run函数,进入消息循环