以单文档项目Test举例,我们的自动生成的代码中所包含以下这些类:
CAboutDlg;CMainFrame; CTestApp; CTestDoc ;CTestView; 他们分别如何与_tWinMain()函数关联呢?
先分析CTestApp类如何关联。
在自动生成的文件Test.cpp中 定义了一个也是唯一一个应用程序类CTestApp theApp
它在程序编译前系统就要给这个对象分配内存空间。(这些称之为预编译或者预处理)因此程序要调用它的构造函数,又因为CTestApp是从CWinAPP中派生出来的,因此要先调用CWinAPP的构造函数。实际上在CWinAPP的构造函数中完成了一些程序的初始化工作。MFC也利用了这种子类与父类的关系可以把对象最终与_tWinMain关联起来。如何关联呢?
在CWinAPP的构造函数中又这样四句话:
pCState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
起到一定作用。其中this是指向CTestApp对象TheApp。
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,__in LPTSTR lpCmdLine, int nCmdShow)
当这些构造函数都调用完成后就进入
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
__in LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
这个_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,__in LPTSTR lpCmdLine, int nCmdShow)函数是微软的MFC 原代码appmodul.cpp 中所写。查看这个函数名的定义可以看到:
#define _tWinMain wWinMain 因此它实际上就是API函数中的wWinMain。
AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
以下为函数代码:
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
__in LPTSTR lpCmdLine, int nCmdShow)
{
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)
{
TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd/n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
_tWinMain实际上调用了AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);这一函数。这一函数是微软定义在WinMain.cpp文件之中的。在这个函数中调用了CWinApp* pApp = AfxGetApp();
在这里有必要说一下,凡是以afx开头的程序叫做应用程序框架类函数。是全局函数。也就是说这种函数在任何类中都可以进行调用。
AfxGetApp();这个函数也是一个应用程序框架类的(全局)函数。也就是说这种函数在任何类中都可以进行调用。此处这个函数会获得一个指针。这个指针就是在CWinAPP的构造函数中通过pModuleState->m_pCurrentWinApp = this; 赋值的指针变量pModuleState->m_pCurrentWinApp,于是通过这个函数使pApp这个指针变量中存贮了一个指向CWinApp派生类CTestApp的对象TheApp的指针.
经接着pThread和pApp调用了3个函数,这3个函数完成了作为一个应用程序所需要的步骤。
包括:设计窗口,注册窗口,创建窗口,显示窗口,更新窗口,消息循环。 消息过程(消息处理函数)
通过语句if (pApp != NULL && !pApp->InitApplication())
goto InitFailure;
首先pApp调用了InitApplication()这个函数。这个函数主要是MFC内部管理所调用的一个函数。
然后pThread->InitInstance(),因为在CWinThread和 CWinApp中InitInstance()都是虚函数,所以最后就调用了CTestApp中的InitInstance(),(多态原理)注意理解这3个类之间的派生关系。
最后nReturnCode = pThread->Run();调用了Run()方法,在Run()中完成了消息循环。
分析以上应用程序所需要的步骤
1. 如何设计窗口?
在MFC中已经定义了一些缺省窗口设定。因此设计部分可以不做额外考虑。
2. 如何实现注册窗口?
利用了函数afxEndDeferRegister(),这个函数写在WinCore.cpp中。使用它注册了MFC中已经定义了一些缺省窗口设定。这个函数调用了afxRegisterClass(),它里面又调用了RegisterClass().到此已经是最底层的Win32API函数了. afxEndDeferRegister()由BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)调用。按照正常流程应该先是执行CMainFrame::PreCreateWindow(CREATESTRUCT& cs)再执行afxEndDeferRegister(),但是对于单文档程序由于牵涉到文档管理等一些工作注册直接由CTestApp::InitInstance()里面进行调用。
3. 创建窗口?
窗口已经注册,那么何时产生的呢?产生的时候需要设定窗口类的名字。注意到创建项目的时候自动生成的类中有一个CMainFrame,它最终派生于CWnd类。凡是派生于CWnd类的类都是窗口类。在这里CMainFrame是应用程序的框架窗口。在CMainFrame中有一个函数
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此經由修改CREATESTRUCT cs
// 達到修改視窗類別或樣式的目的
return TRUE;
}
它里面首先调用了基类中的CFrameWnd::PreCreateWindow(cs),这个函数在微软的afxWin.h中的定义由CFrameWnd类中由virtual BOOL PreCreateWindow(CREATESTRUCT& cs);这句话定义。再查看这个函数的定义的过程中发现似乎直接派生于CWnd类的所有类的定义都写在微软的afxWin.h中。在
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);里面涉及到一个变量CREATESTRUCT,它由
typedef CREATESTRUCTW CREATESTRUCT;定义为CREATESTRUCTW类型。CREATESTRUCTW又是一个什么结构呢?可以参看它的定义代码如下:
typedef struct tagCREATESTRUCTW {
LPVOID lpCreateParams;
HINSTANCE hInstance;
HMENU hMenu;
HWND hwndParent;
int cy;
int cx;
int y;
int x;
LONG style;
LPCWSTR lpszName;
LPCWSTR lpszClass;
DWORD dwExStyle;
} CREATESTRUCTW, *LPCREATESTRUCTW;
实际上这个结构体的参数和CreateWindow() 函数所需的参数一致。因为窗口由系统给我们生成,再此提供一个CREATESTRUCTW结构体的作用是我们可以对这个结构体进行设置,因为CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)这个函数用到它的引用,就可以对窗口特性进行一些修改。在看BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)这个函数的实际内容,经过查找发现它是写在WinFrm.cpp中。代码如下:
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
}
if (cs.style & FWS_ADDTOTITLE)
cs.style |= FWS_PREFIXTITLE;
cs.dwExStyle |= WS_EX_CLIENTEDGE;
return TRUE;
}
首先判断cs.lpszClass == NULL?在子类中如果没有对cs赋值,则成立。于是调用AfxDeferRegisterClass
去注册一个AFX_WNDFRAMEORVIEW_REG, AfxDeferRegisterClass实际上由宏定义等同于afxEndDeferRegister()。
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG))进行判断,
如果没有注册就注册,如果注册了就将cs.lpszClass = _afxWndFrameOrView;将应用程序所适合的已经注册的框架类的名字赋给cs结构体中的cs.lpszClass,标识类名。
之后调用写在WinCore.cpp文件中的
BOOL CWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext)
这一函数,开始创建窗口。
再之后调用
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
这一函数实现具体的窗口创建工作。
注意到在CWnd::CreateEx函数中又调用了PreCreateWindow,在这里对它进行调用的母的是在窗口创建前给我门一次机会对窗口属性进行修改。
4. 显示窗口,更新窗口。
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
在Test.cpp中由以上两句代码完成。
5. 消息循环。
由CWinThread::Run()来完成。(见第2页最后一句话),这个函数由AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow)函数中所定义的指向CWinThread类的指针nReturnCode = pThread->Run();调用了Run()方法,调用的时间是发生在窗口更新语句m_pMainWnd->UpdateWindow();之后。在Run()中完成了消息循环。
再来看CWinThread::Run()函数本身。它里面调用了PumpMessage(),
BOOL CWinThread::PumpMessage()
{
return AfxInternalPumpMessage();
}
实际上最底层的W32API函数中的GetMessage();TranslateMessage();DispatchMessage();函数是写再这个函数中的。至此完成消息循环。
6. 窗口过程
在消息循环中每取到一条消息,则路由给操作系统进行处理。操作系统的默认窗口过程是在窗口注册的
afxEndDeferRegister()中定义为默认窗口过程DefWndProc。实际上消息不是都交给DefWndProc处理,在这里MFC做了一种转换,使用了叫做消息映射的机制。由消息响应函数进行处理。
至此一个框架窗口完成。
CTestView类也是最终派生于CWnd,因此它的流程与CMainFrame类似。
以下再讨论CTestDoc类。派生关系是CObject->CCmdTarget->CDocument->CTestDoc.
CDocument类表示一个文档类。文档视图结构实际上就是要把文档数据和它的显示分离开来。关于CTestDoc类的详细讨论放到后面。先说一下框架代码各部分与CTestApp的关联关系。
其余的CMainFrame; CTestDoc ;CTestView是如何与CTestApp关联起来的呢?
在CTestApp的初时化函数BOOL CTestApp::InitInstance()中有这样一段代码。
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // 主SDI 框架視窗
RUNTIME_CLASS(CTestView));
它定义了一个单文档模版和一个模版指针,如果要深究CSingleDocTemplate的来历,查看定义,可以在afxWin.h文件中发现这样两句代码。
class AFX_NOVTABLE CDocTemplate : public CCmdTarget
class CSingleDocTemplate : public CDocTemplate
可见CSingleDocTemplate这个单文档模版类派生于CCmdTarget。通过这个模版和这个指针把CMainFrame; CTestDoc ;CTestView是如何与CTestApp关联起来。再利用AddDocTemplate(pDocTemplate);将单文档模版增加到文档模版中。