1. 无论是用win32 SDK编写的应用程序还是MFC应用程序,程序的入口都是WinMain;在MFC中程序的入口函数为:_tWinMain,这个函数被微软隐藏在源文件文件:AppModule.cpp中。
为了能找到MFC的运行流程的脉络,我们可以让程序处于调试模式,单步跟踪程序的运行状况。下面首先列举出将要跟踪的函数和类及其所在的源文件中。
函数————————————————>所在源文件
—————————————>
AfxWinMain—————————————>WinMain.cpp
CWinApp类—————————————>AppCode.cpp
CreateEx——————————————>WinCode.cpp(调用API函数CreateWindowEx,进行窗口创建,他是CreateWindow的扩展函数,同是API函数)
AfxEndDeferRegisterClass——————>WinCode.cpp(注册窗口类)
AfxDeferRegisterClass————————>AfxIMPL.h(因为这个函数是一个宏,他是在这个头文件中定义的,他实际上就是AfxEndDeferRegisterClass)
Create————————————————>WinFrm.cpp(调用CreateEx函数)
CFrameWnd::PreCreateWindow————>WinFrm.cpp(是虚函数。窗体创建前,在子类的PreCreateWindow中可以修改窗体的样式。在窗口创建前做一些准备工作。)
CFrameWnd::LoadFrame———————>WinFrm.cpp(调用Create函数,创建窗口)
这些文件的位置在:VC++环境安装位置\Microsoft Visual Studio\VC98\MFC\SRC,进入后可点击搜索——>“文件包含一个词或一个词组”,输入函数类容....
2. 首先创建SDI程序(单文档程序)作为测试程序。每一个MFC程序都有且仅有一个全局变量theApp,他表示当前应用程序模块。
整个MFC程序的执行顺序大致为:
首先是全局构造
CObject构造函数à CCmdTarget àCWinThreadàCWinAppà theApp构造函数
然后进入WinMain函数
WinMainàAfxWinMainàAfxWinInitàtheApp.InitApplicationàtheApp.InitInstance
接着执行线程过程。
theApp.Run()
最后清理
AfxWinTerm
我们首先进入文件AppModule.cpp,找到_tWinMain,函数定义为:
代码
1
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
2
LPTSTR lpCmdLine,
int nCmdShow)
3
{
4
//
call shared/exported WinMain
5
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
6
}
发现就一个函数:AfxWinMain,我们在进入这个函数。他的主要定义如下:
代码
1
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
2
LPTSTR lpCmdLine,
int nCmdShow)
3
{
4
ASSERT(hPrevInstance == NULL);
5
6
int nReturnCode = -
1;
7
CWinThread* pThread = AfxGetThread();
//
返回的就是指向theApp的指针,以为C***App继承自CWinApp,CWinApp继承自CWinThread
8
CWinApp* pApp = AfxGetApp();
//
返回的就是指向theApp的指针
9
10
//
AFX internal initialization
11
if (!
AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
12
goto InitFailure;
13
14
//
App global initializations (rare)
15
if (pApp != NULL && !
pApp
->
InitApplication())
16
goto InitFailure;
17
18
//
Perform specific initializations
19
if (!
pThread
->
InitInstance())
20
{
21
if (pThread->m_pMainWnd != NULL)
22
{
23
TRACE0(
"
Warning: Destroying non-NULL m_pMainWnd\n
");
24
pThread->m_pMainWnd->DestroyWindow();
25
}
26
nReturnCode = pThread->ExitInstance();
27
goto InitFailure;
28
}
29
nReturnCode
=
pThread
->
Run();
30
31
InitFailure:
32
#ifdef _DEBUG
33
//
Check for missing AfxLockTempMap calls
34
if (AfxGetModuleThreadState()->m_nTempMapLock !=
0)
35
{
36
TRACE1(
"
Warning: Temp map lock count non-zero (%ld).\n
",
37
AfxGetModuleThreadState()->m_nTempMapLock);
38
}
39
AfxLockTempMaps();
40
AfxUnlockTempMaps(-
1);
41
#endif
42
43
AfxWinTerm();
44
return nReturnCode;
45
}
在AfxWinMain中,AfxWinInit和pApp->InitApplication(),主要做的事情是:初始化应用程序内部的环境,完成MFC自己内部的一些设置等。这个函数和外部没多大干系。应用程序的很多工作都是在pThread->InitInstance()中完成,类比上节课的知识,他主要做的事情是:
a.设计窗口类:WNDCLASS
b.注册窗口类:RegisterClass
c.创建窗口类:CreateWindow
d.显示窗口类:ShowWindow
e.更新窗口类:UpdateWindow
然后又到nReturnCode = pThread->Run();这是一个消息循环,处理系统消息。
有上面可以看出,在AfxWinMain中的执行流程和Win32下的流程差不多(第一节课讲的内容)。
3. 我们首先看pThread->InitInstance(),这是由虚函数,而pThread指向的是theApp,所以其处他所调用的是子类C***App类中的函数,C***App的InitInstance函数我们可以修改他,先看看他的Code大体是什么样子的:
代码
BOOL CTestApp::InitInstance()
{
AfxEnableControlContainer()
#ifdef _AFXDLL
Enable3dControls();
//
Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();
//
Call this when linking to MFC statically
#endif
SetRegistryKey(_T(
"
Local AppWizard-Generated Applications
"));
LoadStdProfileSettings();
//
Load standard INI file options (including MRU)
CSingleDocTemplate* pDocTemplate;
pDocTemplate =
new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame),
//
main SDI frame window
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);
//
其处是将Doc类,CMainFrame,CView三个类用文档模板类关联到一起,在装载到代表当前应用程序的全局对象theApp中。
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
/*这个函数CWinApp::ProcessShellCommand主要完成的工作为:
a.设计窗口类:WNDCLAS
b.注册窗口类:RegisterClass
c.创建窗口类:CreateWindow
他不是一个虚函数,而且在CWinThread中没有这个成员函数,所以在其处调用的ProcessShellCommand一定是早已经在CwinApp中定义好的函数,他所应该完成的工作都已经定制好的了。
*/
// The one and only window has been initialized, so show and update it.
m_pMainWnd->ShowWindow(SW_SHOW);//显示窗口,调用API函数ShowWindow
m_pMainWnd->UpdateWindow();//更新窗口,调用API函数UpdateWindow
return TRUE;
}
说明:
对于上面代码中的函数ProcessShellCommand完成的功能,我个人分析是:
a.设计窗口类:WNDCLAS
b.注册窗口类:RegisterClass
c.创建窗口类:CreateWindow
他不是一个虚函数,而且在CWinThread中没有这个成员函数,所以在其处调用的ProcessShellCommand一定是早已经在CwinApp中定义好的函数,他所应该完成的工作都已经定制好的了。
这是我个人的观点,老师在视频中并没有说明。因为这三个工作肯定在C***App::InitInstance()中完成,通过跟踪,发现只可能在这里面完成的。
那么ProcessShellCommand是如何完成这个功能的呢? 首先可以肯定的调用线路是:CFrameWnd::LoadFrame()——>Cwnd::Create()——>Cwnd::CreateEx()——>::CreateWindowEx();其中“——>”表示调用关系。可见创建窗口主要是在CFrameWnd::LoadFrame()中完成的。
另外,我根据老师说明,从函数ProcessShellCommand进入进行跟踪,发现如下的一条线路通向CFrameWnd::LoadFrame(),在其列举出仅供参考,线路如下(其中“——>”表示调用关系):
ProcessShellCommand()——>OnCmd()——>AfxDispatchCmdMsg()——>pfn_Command()——>OFileNew()——>OpenDocumentFile()——>CreateNewFrame()——>LoadFrame()
在跟踪过程中,我发现系统多次调用PreCreateWindow、CreateEx、AfxEndDeferRegisterClass ,但是那好像都是都是注册工具栏、状态栏等的(实际上工具栏、状态栏等都是窗口),
没有发现注册主窗口,可能是跟丢了...
那现在看看CreateEx这个函数:
代码
1
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
2
LPCTSTR lpszWindowName, DWORD dwStyle,
3
int x,
int y,
int nWidth,
int nHeight,
4
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
5
{
6
//
allow modification of several common create parameters
7
CREATESTRUCT cs;
8
cs.dwExStyle = dwExStyle;
9
cs.lpszClass = lpszClassName;
10
cs.lpszName = lpszWindowName;
11
cs.style = dwStyle;
12
cs.x = x;
13
cs.y = y;
14
cs.cx = nWidth;
15
cs.cy = nHeight;
16
cs.hwndParent = hWndParent;
17
cs.hMenu = nIDorHMenu;
18
cs.hInstance = AfxGetInstanceHandle();
19
cs.lpCreateParams = lpParam;
20
21
if (!PreCreateWindow(cs))
22
{//此处调用的PreCreateWindow,事实上调用的是C***App的成员函数,他是虚函数,可以在C***App中被从写,这样可以在窗口被创建前,可以通过结构体CREATESTRUCT更改窗口风格等,
23 PostNcDestroy();
24
return FALSE;
25
}
26
27
AfxHookWindowCreate(
this);
28
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
//
开始创建窗口
29
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
30
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
31
32
if (!AfxUnhookWindowCreate())
33
PostNcDestroy();
//
cleanup if CreateWindowEx fails too soon
34
35
if (hWnd == NULL)
36
return FALSE;
37
ASSERT(hWnd == m_hWnd);
//
should have been set in send msg hook
38
return TRUE;
39
}
注意上面PreCreateWindow的功能。
我们在看看AfxEndDeferRegisterClass的代码:
代码
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
//
mask off all classes that are already registered
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
LONG fRegisteredClasses =
0;
//
common initialization
WNDCLASS wndcls;
memset(&wndcls,
0,
sizeof(WNDCLASS));
//
start with NULL defaults
wndcls.lpfnWndProc = DefWindowProc;
//
是窗口类的窗口过程为系统的默认窗口过程。在MFC中,对于外部用户,已不再使用win32中的消息循环了,用户正真接触到的是消息响应机制。
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
INITCOMMONCONTROLSEX init;
init.dwSize =
sizeof(init);
//
work to register classes as specified by fToRegister, populate fRegisteredClasses as we go
........
if (fToRegister & AFX_WNDCONTROLBAR_REG)
{
//
Control bar windows
wndcls.style =
0;
//
control bars don't handle double click
wndcls.lpszClassName = _afxWndControlBar;
wndcls.hbrBackground = (HBRUSH)(COLOR_BTNFACE +
1);
if (AfxRegisterClass(&wndcls))
//
注册窗口
fRegisteredClasses |= AFX_WNDCONTROLBAR_REG;
}
if (fToRegister & AFX_WNDCOMMCTL_TAB_REG)
{
init.dwICC = ICC_TAB_CLASSES;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_TAB_REG);
}
if (fToRegister & AFX_WNDCOMMCTL_PROGRESS_REG)
{
init.dwICC = ICC_PROGRESS_CLASS;
fRegisteredClasses |= _AfxInitCommonControls(&init, AFX_WNDCOMMCTL_PROGRESS_REG);
}
.......
}
接下来的类容就是一些零散的笔记了
1. 在Win32 SDK 或者MFC中,函数后面加"Ex"的一般表示为另一个函数的扩展函数,如CreateWindowEx为CreateWindow的扩展函数.
2. 在MFC中定义了一下用于应用程序框架类的全局函数,这些全局函数都是以AFX开头的,在程序中都可以调用.
3.一般我们看到的"一个窗口",实际上他可能有多个窗口组成,如单文档程序,里面的就有两个窗口:CMainFrame和CView.
4. AfxGetApp和AfxGetThread 返回的都是theApp对象.
5. 在MFC程序中当调用的API函数或者全局函数和MFC中的成员函数或者成员变量在名字上有冲突时,可以在API函数或者全局函数前面加上"::",表示他们是API函数或者全局函数;
6. 当一个窗口销毁时,CWnd对象不一定立即被销毁,因为销毁窗口(DestroyWindow())只是将窗口句柄置为NULL,而CWnd对象只是包含了一个窗口句柄,CWnd对象的成员句柄不知为空后,Cwnd对象可能还会存在。