VC++ - MFC框架解析

AppWizard只是一个源代码生成工具,是计算机辅助程序设计工具,此工具只是对我们熟悉的win32 API进行了深度封装而已。无论如何,我们设计的应用程序都必须是这种执行过程:

设计窗口类->注册窗口类->创建窗口->显示窗口->更新窗口->进入消息循环     (标准模式


这里我们想做的就是解析MFC究竟是如何在Win32 API基础上执行的。而对于文档类程序对话框程序又具有不一样的方式:


一、基于单文档过程


总体过程为:

----------------------------------------------------------------------

theApp->WinMain->InitInstance->ShowWindow(SW_SHOW)->UpdateWindow()->CWinThread::Run()

----------------------------------------------------------------------

其中:theApp我们应该很熟悉了,每个程序都需要一个全局对象来表示程序本省!而WinMain是所有Win32 应用程序的入口地址(Win32控制台程序入口为main)。

如果按照上“标准模式”来分解,此过程中已经具有了显示窗口、更新窗口及消息循环,那么窗口类的注册及窗口创建就当然在InitInstance完成了,我们打开一个单文档程序,打开CXXXApp类中的成员函数InitInstance:

BOOL CDdApp::InitInstance()
{
AfxEnableControlContainer();

// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif

// Change the registry key under which our settings are stored.
// TODO: You should modify this string to be something appropriate
// such as the name of your company or organization.
SetRegistryKey(_T("Local AppWizard-Generated Applications"));

LoadStdProfileSettings(); // Load standard INI file options (including MRU)

// Register the application's document templates. Document templates
// serve as the connection between documents, frame windows and views.

CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CDdDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CDdView));
AddDocTemplate(pDocTemplate);

// 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;

// The one and only window has been initialized, so show and update it.
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();

return TRUE;
}

在此函数中我们注意到了以下几个特殊的函数:

ProcessShellCommand、ShowWindow、UpdateWindow。

后面两个函数很熟悉,但对于ProcessShellCommand函数就是注册并创建窗口的关键所在。


那么到底基于单文档程序是怎么运行起来的呢?也就是详细的执行过程。如下所示(下面的过程是由笔者亲自使用VC6经过断点分部运行测试出来的,绝对可靠):

----------------------------------------------------------------------

theApp->WinMain->InitInstance->ProcessShellCommand->

CFrameWnd::LoadFrame->AfxEndDeferRegisterClass->

PreCreateWindow(子类)->CWnd::PreCreateWindow(基类)->

AfxDeferRegisterClass(也就是AfxEndDeferRegisterClass)->

CFrameWnd::Create->CreateEx->

PreCreateWindow(子类)->CWnd::PreCreateWindow(基类)->

CreateWindowEx->

ShowWindow(SW_SHOW)->UpdateWindow()->

CWinThread::Run()

----------------------------------------------------------------------

窗口的注册及创建都在ProcessShellCommand函数中完成,在ProcessShellCommand函数中调用了LoadFrame函数来实现窗口的注册及创建。

从这个过程中我们注意到几点:

1、在窗口创建前后都调用了PreCreateWindow函数,但是前后调用此函数的用意却不相同,第一次调用此函数其实主要是调用其内部封装的AfxEndDeferRegisterClass函数是不是有疑问,AfxEndDeferRegisterClass函数之前已经调用过了啊,为什么这里还要调用一次呢?其实这里调用AfxEndDeferRegisterClass只是判断即将使用的类是否已注册,如果没注册就尝试注册此类,这样做只是保证的程序的严密性。第二次调用PreCreateWindow是在调用CreateWindowEx真正创建窗口之前,这里调用此函数的用意就简单了,就是为了方便用户在真正创建窗口前还能对窗口风格、属性等做一次修改。

PreCreateWindow函数定义如下:

BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
// make sure the default window class is registered
VERIFY(AfxDeferRegisterClass(AFX_WND_REG));

// no WNDCLASS provided - use child window default
ASSERT(cs.style & WS_CHILD);
cs.lpszClass = _afxWnd;
}
return TRUE;
}

这是基类的PreCreateWindow函数定义,在子类的PreCreateWindow函数中会调用此函数。所以在创建窗口之前的那次调用 PreCreateWindow主要是基类的 PreCreateWindow函数以判断窗口类是否已注册。而创建之后调用主要是调用子类的 PreCreateWindow函数来对窗口属性进行一次修改!

注意PreCreateWindow主要针对文档类程序,在对话框程序中根本不会执行这个函数,不信你自己新建个对话框程序,然后添加这个虚函数,然后在此函数处设置断点调试运行看下,根本就不会运行这个函数。


2、还有一点就是,窗口的额创建其实是通过CFrameWnd::Create->CreateEx->PreCreateWindow(子类)->CWnd::PreCreateWindow(基类)->CreateWindowEx过程完成度额,所以窗口创建最终是通过CreateWindowEx函数实现的!



二、基于对话框过程


基于对话框的过程相对单文档较为简单,总体框架和单文档一样

----------------------------------------------------------------------

theApp->WinMain->InitInstance->ShowWindow(SW_SHOW)->UpdateWindow()->CWinThread::Run()

----------------------------------------------------------------------

不同地方主要体现在InitInstance函数中,这也让我们明白了,为什么AppWizard在创建单文档或对话框程序时都直接重写了InitInstance函数。打开对话框的InitInstance函数:

BOOL CDlgApp::InitInstance()
{
AfxEnableControlContainer();

// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif

CDlgDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();

if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}

// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}

可以看出窗口的注册创建及显示都被封装在了 DoModal 函数中。这只是形式上的不同罢了,我们跟踪MFC底层源代码可以发现基于对话框的执行流程为(没有深入的去跟踪):

----------------------------------------------------------------------

theApp->WinMain->InitInstance->DoModal->

AfxEndDeferRegisterClass->

CreateEx->

CreateWindowEx->

ShowWindow(SW_SHOW)->UpdateWindow()->

CWinThread::RunModalLoop()

----------------------------------------------------------------------

可以看出此对话框过程与单文档过程主要有以下几个区别:

1、对话框不会执行PreCreateWindow。

2、消息循环方式不同:对话框为RunModalLoop函数,而单文档为CWinThread::Run函数。



以下为详细介绍(基于单文档)

--------------------------------------------------------------
1、
寻找WinMain入口
在安装目录下找到MFC文件夹下的SRC文件夹,SRC下是MFC源代码。
WinMainAPPMODUL.CPP中实现:

_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
  // call shared/exported WinMain
  return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

注意:( #define _tWinMain WinMain


2、对于全局对象或全局变量来说,在程序运行即WinMain函数加载的时候,已经为全局对象或全局变量分配了内存和赋初值
所以,CTestApp theApp; -> CTestApp ::CTestApp(){} -> _tWinMain(){}
 明:每一个MFC程序,有且只有一个从WinApp类派生的类(应用程序类),也只有一个从应用程序类所实例化的对象,表示应用程序本身。在WIN32 序当中,表示应用程序是通过WinMain入口函数来表示的(通过一个应用程序的一个实例号这一个标识来表示的)。在基于MFC应用程序中,是通过产生一 个应用程序对象,用它来唯一的表示了应用程序。


3、通过构造应用程序对象过程中调用基类CWinApp的构造函,在CWinApp的构造函数中对程序包括运行时一些初始化工作完成了。
CWinApp类在APPCORE.CPP中实现:
CWinApp::CWinApp(LPCTSTR lpszAppName){…}  //带参数,而CTestApp构造函数没有显式向父类传参,难道CWinApp()有默认参数?见下:
CWinApp类定义中, CWinApp(LPCTSTR lpszAppName = NULL); 
注意:CWinApp()函数中:
pThreadState->m_pCurrentWinThread = this;
pModuleState->m_pCurrentWinApp = this
this指向的是派生类CTestApp对象,即theApp


4_tWinMain函数中通过调用AfxWinMain()函数来完成 它要完成的功能。(Afx*前缀代表这是应用程序框架函数,是一些全局函数,应用程序框架是一套辅助生成应用程序的框架模型,把一些类做一些有机的集成, 我们可根据这些类函数来设计自己的应用程序)。
AfxWinMain()函数在WINMAIN.CPP中:
AfxWinMain()函数中:
1CWinApp* pApp = AfxGetApp();
说明pApp存储的是指向WinApp派生类对象(theApp)的指针。

2)调用pApp->InitApplication()MFC类的一些内部初始化管理。

3)调用pThread->InitInstance()
说明pThread也指向theApp,由于基类中virtual BOOL InitInstance()定义为虚函数,所以调用pThread->InitInstance()的时候,调用的是派生类CTestAppInitInstance()函数。

4nReturnCode = pThread->Run();
说明pThread->Run()完成了消息循环。


5、注册窗口类AfxEndDeferRegisterClass();
  AfxEndDeferRegisterClass()WINCORE.CPP中实现:
  BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister){…}
 说明:

  设计窗口类:在MFC中事先设计好了几种缺省的窗口类,根据不同的应用程序的选择,调用AfxEndDeferRegisterClass()函数注册所选择的窗口类。
  调 试:

  CWinApp::CWinApp();->CTestApp theApp;->CTestApp ::CTestApp()->CWinApp::CWinApp()->CTestApp::CTestApp()-> _tWinMain(){}//进入程序
  ->AfxWinMain();->pApp->InitApplication()->pThread->InitInstance()  //父类InitInstance虚函数;
  ->CTestApp::InitInstance()  //子类实现函数;
  ->AfxEndDeferRegisterClass(LONG fToRegister)  //注册所选择的窗口类(出于文档管理,注册提前,正常的应在PreCreateWindow中进行注册)//之后进入创建窗口阶段(以下再不做调试)


6CMainFramePreCreateWindow()   //主要是注册窗口类,以及在创建窗口之前让用户有机会对style进行修改

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
      return FALSE;

    //在创建窗口之前让用户有机会对style进行修改,即修改cs


    return TRUE;
}


说明

CFrameWnd::PreCreateWindow()函数所在文件:WINFRM.CPP

BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{


if (cs.lpszClass == NULL)
{
  VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
  //判断AFX_WNDFRAMEORVIEW_REG型号窗口类是否注册,如果没有注册则注册
  cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
  //把注册后的窗口类名赋给cs.lpszClass
}

if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4)
  cs.style |= FWS_PREFIXTITLE;

if (afxData.bWin4)
  cs.dwExStyle |= WS_EX_CLIENTEDGE;

return TRUE;

}

其中
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
  //PreCreateWindow()是个虚函数,如果子类有则调用子类的。
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
const TCHAR _afxWndFrameOrView[] = AFX_WNDFRAMEORVIEW;
  //WINCORE.CPP文件中,定义为全局数组。
//#define AFX_WNDFRAMEORVIEW AFX_WNDCLASS(”FrameOrView”)


7、创建窗口
这里只讲解了框架窗口的创建,它的Create()函数在WINFRM.CPP中:

CFrameWnd::Create(…){
    
    CreateEx(…);//从父类继承来的,调用CWnd::CreateEx().
    
}


CWnd::CreateEx()函数在WINCORE.CPP中:

BOOL CWnd::CreateEx(…){
    
    if (!PreCreateWindow(cs))//虚函数,如果子类有调用子类的。
    {
      PostNcDestroy();
      return FALSE;
    }
    
    HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
    cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
    cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
    
}


  CreateWindowEx() 函数参数与 CREATESTRUCT 结构体参数有一   一对应关系 ,使我们在创建窗口之前可以通过  PreCreateWindow(cs) 修改 cs 结构体成员来修改所要的窗口外观。


8、显示和更新窗口

CTestApp类,TestApp.cpp
m_pMainWnd->ShowWindow(SW_SHOW); 
  //显示窗口,m_pMainWnd指向框架窗口
m_pMainWnd->UpdateWindow(); 
        //更新窗口
说明:
class CTestApp : public CWinApp{…}
class CWinApp : public CWinThread{…}
class CWinThread : public CCmdTarget
{
  
  public:
  CWnd* m_pMainWnd;
  
}


9、消息循环

int AFXAPI AfxWinMain()
{ 
  // Perform specific initializations
  if (!pThread->InitInstance()){…}
  //完成窗口初始化工作,完成窗口的注册,完成窗口的创建,显示和更新。
  nReturnCode = pThread->Run();
  //继承基类Run()方法,调用CWinThread::Run()来完成消息循环

}


CWinThread::Run()函数在THRDCORE.CPP

int CWinThread::Run()
  
  // phase2: pump messages while available
  do//消息循环
  {
    // pump message, but quit on WM_QUIT
    if (!PumpMessage())//取消息并处理
      return ExitInstance();
  
  } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
  
}

说明
BOOL PeekMessage(,,,,)
函数说明
The PeekMessage function checks a thread message queue for a message and places the message (if any) in the specified structure.
If a message is available, the return value is nonzero.
If no messages are available, the return value is zero.

/

BOOL CWinThread::PumpMessage()
{


if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))//
取消息
{…}

// process this message
if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
{
  ::TranslateMessage(&m_msgCur);  //进行消息(如键盘消息)转换
  ::DispatchMessage(&m_msgCur);  //将消息路由给操作系统,由相应的消息响应函数来处理
}
return TRUE;


}


10、文档与视结构
  可以认为CView类窗口是CMainFrame类窗口的子窗口。
  CDocument类是文档类。
  DOC-VIEW结构将数据本身与它的显示分离开。
  文档类:数据的存储,加载
  视类:数据的显示,修改

11、文档类,视类,框架类的有机结合


CTestAppCTestApp::InitInstance()函数中通过文档模板将文档类,视类,框架类组织在一起。
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CTestDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CTestView));
AddDocTemplate(pDocTemplate);//
增加到文档模板


12、窗口类,窗口对象,窗口
  窗口是屏幕上的一块儿矩形区域,是一种计算机资源;窗口类是封装了对窗口的一系列操作的类,比如注册窗口,创建窗口,显示窗口,销毁窗口等等;窗口对象是窗口类的一个实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值