MFC程序的出生和结束
来源:互联网 发布:淘宝客店铺设置 编辑:程序博客网 时间:2024/03/28 17:55
在MFC程序设计的学习过程中最令人感到难受,甚至于有时会动摇学习者信心的就是一种对于程序的一切细节都没有控制权的感觉,而这种感觉的出现会使大家认为自己离开了书本上的例子就无法设计编制程序。事实上这是在MFC学习过程中经常出现的一种正常现象。产生这种情况的原因就是MFC最大的优点——封装。
众所周知,MFC将Windows程序设计中的一些重复、冗缀的部分进行了封装,使得在所有程序中都必然会出现的变化不大的部分不再由程序员手动输入,而是使用程序自动生成,程序员则可以集中精力于程序的核心部分。但是这种封装实在非常的严密以至于对于初学者来说已经完全看不到程序的入口处了,由此必然会导致对程序没有一个整体的感觉,对习惯了面向过程而不是面向对象的程序员来说更是如此。为了解决这种情况,本文将从MFC Framework的内部将程序的整体运行流程拆出,展示给读者来看,并希望这样做能有利于大家在MFC程序设计上更加得心应手。
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass)) //注册窗口类
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, //窗口类名称
TEXT ("The Hello Program"), //窗口标题
WS_OVERLAPPEDWINDOW, //窗口类型
CW_USEDEFAULT, //初始X坐标
CW_USEDEFAULT, //初始Y坐标
CW_USEDEFAULT, //窗口宽度
CW_USEDEFAULT, //窗口高度
NULL, //父窗口句柄
NULL, //窗口菜单句柄
hInstance, //程序实例句柄
NULL) ; //创建参数
ShowWindow (hwnd, iCmdShow) ; //显示窗口
UpdateWindow (hwnd) ; //刷新窗口
while (GetMessage (&msg, NULL, 0, 0)) //消息循环
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
打开任一个MFC工程,使用Class View都可以看到一个Globals组,该组中用来存储程序所用到的全局变量和全局函数。当我们新建一个MFC工程后该组中都会被自动添加一个 theApp对象,虽然很少有人会对它进行操作,但对于一个MFC程序来说它拥有相当于WinMain的地位,确切的说它对程序起引发的作用。下面我们就从对theApp的探讨来开始我们的挖掘工作。
(P.S:由于篇幅所限我无法向大家提供完整的例程,但各位可以使用VC++ 6.0的AppWizard生成一个单窗口MFC程序,由于程序的初始化代码对所有程序都具有同一性,所以我们可以使用各自生成的代码进行讨论)
theApp是一个CWinApp类的对象,CWinApp是CWinThread的子类,CWinThread又是CCmdTarget的子类,CCmdTarget则是直接派生自CObject这个几乎所有MFC类的老祖宗的。
(P.S:这里列出CWinApp的所有父类对于当前所谈到的程序初始化工作并没有太大的用处,读者现在也不必对此深究,但对于后面的涉及消息映射和命令传递的章节来说却是极为重要的,因此现在提前将他们列出来以便将来谈到时不会让大家感到突兀。)仔细观察SDK版的程序源码,我们可以看到一个Windows程序从WinMain函数开始,经过注册窗口类、创建窗口、显示和刷新窗口才使得该程序的窗口界面为用户可见,而用户对此界面所作的任何操作都会被Windows作为消息传递给程序的窗口函数,并由窗口函数对消息进行分类处理,这些工作都是被 WinMain函数独自包办的。但在MFC程序中WinMain函数的地位被CWinApp类取代了,它所负责的全部初始化工作和对消息解释及分派都有 CWinApp类的内部函数来完成,但是WinMain仍然存在,并且扮演着驾驭CWinApp的角色。CWinApp中几个最重要的函数如下:
virtual BOOL InitApplication();
virtual BOOL InitInstance();virtual int Run(); 那么在SDK程序设计中至关重要的主窗口句柄(就是那个hwnd,几乎程序所有有关窗口的操作都必须用到该句柄,它为Windows定位所要输出的信息的目的窗口)跑到哪里去了呢?它被存储在CWinThread中名为m_MainWnd的成员变量中,而CWinThread是CWinApp的父类。 再来说MFC对SDK程序中的窗口函数所进行的变化。原来的WndProc窗口函数与WinMain一样被变形后由单独生成的类进行了替代,替代它的是名为CFrameWnd的类,该类一般因程序不同而被继承为不同的模样,比较有代表性的一般形态如下:class CMyFrameWnd : public CFrameWnd{public: CMyFrameWND(); afx_msg void OnPaint(); afx_msg boid OnAbout(); DECLARE_MESSAGE_MAP();}; 虽然每一条消息都有很明显与之对应的函数进行处理,但却令人无法看出MFC是如何实现对于一条消息调用相应的函数的,要知道已经不再存在一个使用 switch...case 格式的窗口函数负责函数的调用了。事实上MFC在此使用了消息映射机制,类定义的最后一行“DECLARE_MESSAGE_MAP();”就是一个属于消息映射过程的宏,在以后的章节中我们将详细介绍这种消息映射机制,不过在此还是希望大家认识一下其中使用的一些宏,这是你的每一个MFC程序中都必然存在的代码:BEGIN_MESSAGE_MAP(CNyFrameWnd,CFrameWnd) ON_WM_PAINT() ON_COMMAND(IDM_ABOUT,OnAbout)EBD_MESSAGE_MAP()(P.S: 上述全局变量的初始化工作由一些名为startup的代码来完成,它们是由编译器提供并链接到程序中的。在编译时由编译器维护一个存有程序中所使用的全部静态对象的链表,程序开始执行时由startup码段负责遍历此链表并根据表内数据指针调用所指定的构造函数及其参数,完成后才将控制权转交给Main或 WinMain函数。) 第4种是产生一个局部静态对象,例如:void MyFunc(){ static CFoo foo; //在函数范围(scope)之内的一个静态对象 ... //other segments} 类似静态局部变量,其存活期从程序第一次运行到其声明处开始直到程序退出,但比全局对象要早一步被释放。
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
// initialize CWinApp state
ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
// in non-running state until WinMain
m_hInstance = NULL;
m_pszHelpFilePath = NULL;
m_pszProfileName = NULL;
m_pszRegistryKey = NULL;
m_pszExeName = NULL;
m_pRecentFileList = NULL;
m_pDocManager = NULL;
m_atomApp = m_atomSystemTopic = NULL;
m_lpCmdLine = NULL;
m_pCmdInfo = NULL;
{
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)
{
TRACE0("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)
{
TRACE1("Warning: Temp map lock count non-zero (%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm();
return nReturnCode;
4.WinMain详解
首先是AfxWinInit,它隐藏在APPINIT.CPP中,代码如下:
BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
// handle critical errors and avoid Windows message boxes
SetErrorMode(SetErrorMode(0) |
SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
// set resource handles
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
pModuleState->m_hCurrentInstanceHandle = hInstance;
pModuleState->m_hCurrentResourceHandle = hInstance;
// fill in the initial state for the application
CWinApp* pApp = AfxGetApp();
if (pApp != NULL)
{
// Windows specific initialization (not done if no CWinApp)
pApp->m_hInstance = hInstance;
pApp->m_hPrevInstance = hPrevInstance;
pApp->m_lpCmdLine = lpCmdLine;
pApp->m_nCmdShow = nCmdShow;
pApp->SetCurrentHandles();
}
// initialize thread specific data (for main thread)
if (!afxContextIsDLL)
AfxInitThread();
return TRUE;
}
其中用到的AfxInitThread函数位于THRDCORE.CPP中,具体代码如下:
void AFXAPI AfxInitThread()
{
if (!afxContextIsDLL)
{
// set message filter proc
_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
ASSERT(pThreadState->m_hHookOldMsgFilter == NULL);
pThreadState->m_hHookOldMsgFilter = ::SetWindowsHookEx
(WH_MSGFILTER,_AfxMsgFilterHook, NULL,
::GetCurrentThreadId());
#ifndef _AFX_NO_CTL3D_SUPPORT
// intialize CTL3D for this thread
_AFX_CTL3D_STATE* pCtl3dState = _afxCtl3dState;
if (pCtl3dState->m_pfnAutoSubclass != NULL)
(*pCtl3dState->m_pfnAutoSubclass)(AfxGetInstanceHandle());
// allocate thread local _AFX_CTL3D_THREAD just for automatic
//termination
_AFX_CTL3D_THREAD* pTemp = _afxCtl3dThread;
pTemp; // avoid unused warning
#endif
}
}
之后是theApp的InitApplication函数,由于程序并没有改写该函数,一次相当于调用CWinApp::InitApplication,其代码位于APPCORE.CPP中,如下所列:
BOOL CWinApp::InitApplication()
{
if (CDocManager::pStaticDocManager != NULL)
{
if (m_pDocManager == NULL)
m_pDocManager = CDocManager::pStaticDocManager;
CDocManager::pStaticDocManager = NULL;
}
if (m_pDocManager != NULL)
m_pDocManager->AddDocTemplate(NULL);
else
CDocManager::bStaticInit = FALSE;
return TRUE;
}
这是MFC为了内部管理所作的工作,我们不必理会。
接下来是pApp->InitInstance,CWinApp类中此函数是虚函数,由于我们的程序改写了该函数,所以现在等于调用我们自己的InitInstance,我们的程序也将从这里开始自己主窗口的生命。我生成的一个简单程序的这一段代码如下:
BOOL CTestApp::InitInstance()
{
AfxEnableControlContainer();
#ifdef _AFXDLL
Enable3dControls();
#else
Enable3dControlsStatic();
#endif
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings();
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (!ProcessShellCommand(cmdInfo))
return FALSE;
m_pMainWnd->ShowWindow(SW_SHOW); //这两行与SDK程序
m_pMainWnd->UpdateWindow(); //极为相似
return TRUE;
}
标为红色的一句调用了CMainFrame的构造函数,而在此构造函数中又有堆Create函数的调用,该函数用于创建窗口,共有8个参数。声明如下:
BOOL Create(LPCTSTR lpszClassName,LPCTSTR lpszWindowName,
DWORD dwStyle=WS_OVERLAPPEDWINDOW,
const RECT& rect=rectDefault,
CWnd* pParentWnd=NULL,
LPCTSTR lpszMenuName=NULL,
DWORD dwExStyle=0,
CCreateContext* pContext=NULL);
所使用的窗口类名称可以作为其第一个参数传入,也可以使用NULL,表示以MFC内建窗口类产生一个标准的外框窗口;第二个参数是窗口标题栏中显示的文字;第三个参数为窗口风格;第四个参数指定窗口位置和大小;第五个参数指定父窗口,没有父窗口则使用NULL;第六个参数指定菜单,使用在RC资源文件中定义的菜单名填写;第七个参数为扩展风格,找支持Windows 3.1以后的版本;第八个参数为一个之乡CCreateContext结构的指针,MFC使用它在具备Document/View结构的应用程序中初始化外框窗口。
SDK程序设计中要求的对窗口类的注册工作在MFC中也是必需的,并且它是由Create函数在创建窗口之前触发的操作,但其中间过程较为复杂,如果要解释清楚需要向本文加入大量源代码,限于篇幅本文将不介绍此部分,有兴趣的读者可以阅读侯俊杰先生的《深入浅出MFC》P283-P289,有相当详尽的描述和分析。
窗口的显示和更新较为简单,主要就是后边标红的两行,因此现在我们要把重心放在CWinApp::RUN函数上,其代码位于APPCORE.CPP中,列出如下:
int CWinApp::Run()
{
if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
{
// Not launched /Embedding or /Automation, but has no main window!
TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.\n");
AfxPostQuitMessage(0);
}
return CWinThread::Run();
}
可以看到,CWinApp::Run事实上指向CWinThread::Run,它位于THRDCORE.CPP中,代码如下:
int CWinThread::Run()
{
ASSERT_VALID(this);
// 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(&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))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
}
ASSERT(FALSE); // not reachable
}
PumpMessage代码如下:
BOOL CWinThread::PumpMessage()
{
ASSERT_VALID(this);
if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
{
return FALSE;
}
// process this message
if (m_msgCur.message != WM_KICKIDLE
&& !PreTranslateMessage(&m_msgCur))
{
::TranslateMessage(&m_msgCur);
::DispatchMessage(&m_msgCur);
}
return TRUE;
}
这俨然就是原来的WndProc的一个翻版,很明显正是有着一部分代码来负责整个程序的消息获取、解释、判断和分派的,我们可以通过对它的适当改写来使得程序的消息流动按我们的意愿进行,另外要使程序能处理空闲时间(Idle Time)也要从改写该函数开始。而在对消息进行了分派之后就是由早已架设好的由前面曾提到的那些宏所实现的消息映射来驱动整个程序了,由于我计划将消息映射方在后面的章节再来介绍,因此我们对于程序初生的剖析到此也就告一段落了,下面我们来看一看程序的死亡,也就是退出过程。
第四节、退出
MFC程序的死亡相对于初生来说要简单的多,主要是以下几步:
1.使用者通过点击File/Close或程序窗口由上角的叉号发出WM_CLOSE消息。
2.程序没有设置WM_CLOSE处理程序,交给默认处理程序。
3.默认处理函数对于WM_CLOSE的处理方式为调用::DestoryWindow,并因而发出WM_DESTORY消息。
4.默认的WM_DESTORY处理方式为调用::PostQuitMessage,发出WM_QUIT。
5.CWinApp::Run收到WM_QUIT后结束内部消息循环,并调用ExinInstance函数,它是CWinApp的一个虚拟函数,可以由用户重载。
6.最后回到AfxWinMain,执行AfxWinTerm,结束程序。
以上就是一个MFC程序的初始化和最后退出的全过程,对于阅读本文的读者我深表感谢,同时若文中有什么地方叙述有错误或者读者仍然存有疑问,请去FrontFree论坛提问,我将尽快回答。在后面的章节中我们将继续探索MFC内部机制和实现方法,希望各位继续支持。
选自:http://blog.chinaunix.net/uid-21169302-id-446230.html
- MFC程序的出生和结束
- 程序的开始和结束
- 计算出生的星期程序
- 结束MFC程序,讽刺……
- MFC中按下enter键程序结束问题
- 结束程序的方法
- MFC程序和Win32程序的关系
- MFC程序和Win32程序的关系
- MFC程序和Win32程序的关系
- MFC程序和Win32程序的关系
- MFC程序和Win32程序的关系
- MFC程序和Win32程序的关系
- MFC程序和Win32程序的关系
- MFC程序和Win32程序的关系
- MFC程序和Win32程序的关系
- 计算出生的天数和星座
- 关于android中activity的返回和结束整个程序
- 程序11--兔子出生
- 快速排序算法之我见(附上C代码)
- Android编程之仿微信显示更多文字的View
- 【OpenStack】metadata在OpenStack中的使用(二)
- 简单易懂的Dancing links讲解(1)
- 【Effective Java】Ch4_Class:Item13_最小化类及其成员的可访问性
- MFC程序的出生和结束
- 安装IIS并将web程序发布到IIS上
- 一个关于执行力的故事
- Socket编程 HTTP请求头中的特殊字符转义
- slab伙伴系统
- extern 和extern “C"的问题
- 拦截音量键调节音量
- Linux群集LVS简介
- EXT JS4 mvc模式及实例