MFC框架程序中的类(1)
MFC框架程序中的类(1)
App类
MFC类层次结构如下图所示:
1、CMyApp类是MFC类的四个主要类之一。它是应用程序运行的基础,其父类是MFC中的CWinApp。CMyApp类除了构造函数,另一个重要的成员函数是InitInstance。一个CMyApp类型的对象实例生成时,系统会调用InitInstance函数完成生产该新实例的一些初始化工作。
2、CWinApp为应用程序类,MFC 中的主应用程序类封装用于 Windows 操作系统的应用程序的初始化、运行和终止。基于框架生成的应用程序必须有且仅有一个从 CWinApp 派生的类(CMyApp)的对象(theApp)。在创建窗口之前先构造该对象。
CWinApp 是从 CWinThread 派生的,后者表示可能具有一个或多个线程的应用程序的主执行线程。在最新版本的 MFC 中,InitInstance、Run、ExitInstance 和 OnIdle 成员函数实际位于 CWinThread 类中。此处将这些函数作为 CWinApp 成员来探讨,因为探讨所关心的是对象作为应用程序对象而不是主线程的角色。
注意:应用程序类构成应用程序的主执行线程。使用 Win32 API 函数还可以创建辅助执行线程。这些线程可以使用 MFC 库。有关更多信息,请参见多线程编程。
与用于 Windows 操作系统的任何程序一样,框架应用程序也具有 WinMain 函数。但在框架应用程序中不必编写 WinMain。它由类库提供,并在应用程序启动时调用。WinMain 执行注册窗口类等标准服务。然后它调用应用程序对象的成员函数来初始化和运行应用程序。(可通过重写由 WinMain 调用的 CWinApp 成员函数来自定义 WinMain。)
为初始化应用程序,WinMain 调用应用程序对象的 InitApplication 和 InitInstance 成员函数。为运行应用程序的消息循环,WinMain 调用 Run 成员函数。在终止时,WinMain 调用应用程序对象的 ExitInstance 成员函数。下图显示了框架应用程序中的执行顺序。
基于MFC的应用程序有一个应用对象,它是CWinApp派生类的对象,该对象代表了应用进程的主线程。当线程执行完并退出线程时,由于进程中没有其他线程存在,进程自动结束。类CWinApp从CWinThread派生出来,CWinThread是用户接口线程的基本类。我们在编写用户接口线程时,需要从CWinThread派生我们自己的线程类,ClassWizard可以帮助我们完成这个工作。
先用ClassWizard派生一个新的类,设置基类为CwinThread。注意:类的DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏是必需的,因为创建线程时需要动态创建类的对象。根据需要可将初始化和结束代码分别放在类的InitInstance和ExitInstance函数中。如果需要创建窗口,则可在InitInstance函数中完成。然后创建线程并启动线程。可以用两种方法来创建用户接口线程,MFC提供了两个版本的AfxBeginThread函数,其中一个用于创建用户接口线程。第二种方法分为两步进行:首先,调用线程类的构造函数创建一个线程对象;其次,调用CWinThread::CreateThread函数来创建该线程。线程建立并启动后,在线程函数执行过程中一直有效。如果是线程对象,则在对象删除之前,先结束线程。CWinThread已经为我们完成了线程结束的工作。
3、CWinThread类是MFC用来封装线程的,包括UI线程和工作者线程。因此每个MFC程序至少使用一个CWinThread派生类。
Windows以消息驱动方式工作,每个WIN32应用程序都至少包含一个消息队列和一个消息泵。消息队列建立在操作系统提供内存保留区中,消息泵不断搜寻消息队列,将取得的消息分发给应用程序的各个部分进行处理,这个过程叫做消息循环。基本消息循环如下:
while(GetMessage(&msg,0,0,0)) //从队列中获取消息
{
TranslateMesssage(&msg); //转换消息参数
DispatchMessage(&msg); //分发消息
}
Windows以线程封装消息循环,封装消息循环的线程叫做用户界面线程,即UI线程。该线程可以创建并撤销窗口。此外,还有一种线程叫做工作者线程,它是辅助UI线程工作的,它没有消息循环,不能处理系统事件和窗口消息,也不能关联主窗口。主线程和辅线程虽然享有共同的虚拟地址空间,但各自占用独立的CPU时间片,参与系统资源的竞争。所以,可以使用辅线程完成经常性的、耗费机时的数据处理工作(例如网络通信),减轻UI线程的负担,确保UI线程及时响应用户的窗口操作。根据需要,一个应用程序中也可以创建多个UI线程。
常用成员变量:CWinThread::m_pMainWnd
该成员变量去存储你的线程主窗口对象。当和m_pMainWnd 相关的窗口被关闭后,MFC会自动终止你的线程。如果该线程是应用程序主线程,程序也将会被终止。如果该数据成员为NULL,应用程序CWinApp对象的主窗口将用来决定什么时候去终止线程。m_pMainWnd是一个CWnd*类型的public变量。很明显,你需要在重载InitInstance时为m_pMainWnd赋值。在工作线程中,m_pMainWnd自动继承其父线程的值。补充:只有CWinThread对象才有m_pMainWnd
下面介绍几个实用的CWinThread类成员函数:
(1)虚函数InitInstance
Windows允许同时运行一个应用程序的多个备份,又称为运行一个程序的多个实例。InitInstance就是“初始化实例”的意思,可见,它是在实例创建时首先被调用的。应用程序总要重载这个虚函数,进行系统设置,创建运行环境。例如,主窗口一定要在InitInstance()中创建,因为该函数退出后就进入该线程的消息循环。
(2)虚函数Run
该函数提供UI线程的消息循环,即反复地提取消息,分发消息,直到收到WM_QUIT退出循环,线程随即结束。在循环中,如果当前没有收到消息,则调用空闲消息处理程序OnIdle()。
virtual int CWinThread::Run()
{
ASSERT_VALID(this);
BOOL bIdle = TRUE; //是否要做空闲处理
LONG lIdleCount = 0; //用户记录空闲处理已经连接执行的次数
//acquire and dispatch messages until a WM_QUIT message is received.
for (;;) //消息循环
{
//如果空闲状态为真,且消息队列为空,则进行空闲处理
while(bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
{
//PeekMessage()用于检查消息队列是否为空,但并不处理消息
//调用空闲处理程序,如果返回零,说明空闲处理已全部完成
if (!OnIdle(lIdleCount++))
bIdle = FALSE;
}
do //空闲处理循环结束,进入消息泵循环
{
//调用消息泵,提取消息并分发消息
如果收到WM_QUIT消息,则退出消息循环
if (!PumpMessage())
return ExitInstance();
//根据刚处理的消息类型,判断是否应该在没有消息到来时立即进行空闲处理
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0; //在重新进行空闲处理前,清空空闲处理的执行次数
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE); //不可能执行的语句
}
/*------------- 消息泵函数PumpMessage() ----------------*/
//省略了调试信息的输出功能
BOOL CWinThread::PumpMessage()
{
ASSERT_VALID(this);
//取出消息队列中的第一个消息,直到取得消息,该函数才返回
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
{
//收到WM_QUIT消息
return FALSE;
}
//处理消息,但不处理WM_KICKIDLE
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur); //转换虚键消息到字符消息
::DispatchMessage(&m_msgCur); //分发消息
}
return TRUE;
}
阅读PumpMessage代码可知,消息泵并不处理WM_KICKIDLE消息,收到该消息后,直接返回。其实,WM_KICKIDLE消息被用来刺激空闲处理的执行,它作为一个空消息促使::GetMessage()返回。
虽然Run()是虚拟函数,但很少被重载。
(3)虚函数ExitInstance
与InitInstance()相反,该函数是在退出消息循环时执行,一般被框架调用,做最后的清理工作。但如果调用InitInstance()失败,ExitInstance()也会被调用。可以重载ExitInstance(),为线程做相关的清理工作。不要在除重载的Run()函数外的地方调用它。如果将CWinThread成员变量m_bAutoDelete设为TRUE,CWinThread::ExitInstance()会删除当前的CWinThread对象。所以,如果在堆栈中构造了UI线程对象,可以利用默认的ExitInstance()自动将它删除。