Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2760
  • 博文数量: 4
  • 博客积分: 200
  • 博客等级: 二等列兵
  • 技术积分: 35
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-05 20:51
文章分类
文章存档

2010年(4)

我的朋友
最近访客

分类: C/C++

2010-12-05 21:01:17

MFC技术内幕系列之(二)
引言:侯捷老师的"深入浅出MFC"一书的第8章中有“"Document/View"是MFC的基石。”一说,可以看出文档视图结构在MFC Framework中的地位是多么的重要。本文将以一个标准MFC应用程序向导作成的MDI程序为例,来和大家一起详细挖掘文档视图结构的内幕。

正文
                       ///////////////////////////////////////////////////////
                       /* 1.回顾"InitInstance函数" */
                       //////////////////////////////////////////////////////

  在我的《MFC应用程序“生死因果”内幕》一文中,当谈到CMyWinApp::InitInstance()时,我只是粗略的讲了介绍了一下各个函数的功能,而忽略了很多细节,这里让我们在回顾一下CMyWinApp::InitInstance()函数,并将里面与文档视图结构有关的代码深入探讨一下:

   BOOL CMyApp::InitInstance()//只列出了与文档视图结构相关的源代码
{
//...

//文档模板将用作文档、框架窗口和视图之间的连接
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE,
   RUNTIME_CLASS(CMyDoc),
   RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
   RUNTIME_CLASS(CMyView));
AddDocTemplate(pDocTemplate);
// 创建主 MDI 框架窗口
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
   return FALSE;
m_pMainWnd = pMainFrame;
// 仅当具有后缀时才调用 DragAcceptFiles
// 在 MDI 应用程序中,这应在设置 m_pMainWnd 之后立即发生
// 分析标准外壳命令、DDE、打开文件操作的命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 调度在命令行中指定的命令。如果
// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
   return FALSE;
// 主窗口已初始化,因此显示它并对其进行更新
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
return TRUE;
}

                       ///////////////////////////////////////////////////
                       /*     2.初始化文档模板       */
                       /////////////////////////////////////////////////

  分析以下代码:

CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_MyTYPE,
   RUNTIME_CLASS(CMyDoc),
   RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架
   RUNTIME_CLASS(CMyView));

  应用程序首先实例化一个CMultiDocTemplate对象,此过程也是对CMultiDocTemplate类数据成员的初始化过程。按调用次序我列出了以下源代码:

  注释1: CDocTemplate构造函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\doctempl.cpp

CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)//部分源代码
{
ASSERT_VALID_IDR(nIDResource);
ASSERT(pDocClass == NULL ||
   pDocClass->IsDerivedFrom(RUNTIME_CLASS(CDocument)));
ASSERT(pFrameClass == NULL ||
   pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd)));
ASSERT(pViewClass == NULL ||
   pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)));

m_nIDResource = nIDResource;
...//
        m_pDocClass = pDocClass;
m_pFrameClass = pFrameClass;
m_pViewClass = pViewClass;
m_pOleFrameClass = NULL;
m_pOleViewClass = NULL;
        ...//
}

  以上为CMultiDocTemplate类的基类CDocTemplate构造函数的部分源代码,该函数初始化了四个重要的成员m_nIDResource,m_pDocClass,m_pFrameClass和m_pViewClass。

  注释2: CMultiDocTemplate构造函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\docmulti.cpp

CMultiDocTemplate::CMultiDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass) : CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass)
{
ASSERT(m_docList.IsEmpty());

m_hMenuShared = NULL;
m_hAccelTable = NULL;
m_nUntitledCount = 0;   // start at 1

// load resources in constructor if not statically allocated
if (!CDocManager::bStaticInit)
   LoadTemplate();
}

  看完以上代码后,来回过头看一看InitInstance函数将什么参数值传给了CMultiDocTemplate的构造函数。
原来是一些RUNTIME_CLASS宏。以下是RUNTIME_CLASS宏的定义:

  注释3: 以下的宏定义在:..\Visual Studio.NET\vc7\atlmfc\include\afx.h中

   #define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name)
   #define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))

  这个地方是个难点,这将涉及到MFC的另一个重要技术---"执行期类型识别"。此项技术我将在MFC技术内幕系列之(三)---《MFC执行期类型识别与动态创建技术内幕》中详细讲解。回到眼前来,源代码中这样作是为了将CMyDoc,CChildFrame,CMyView各类中的static CRuntimeClass class##class_name地址赋予CMultiDocTemplate类的各CRuntimeClass*指针成员m_pDocClass,m_pFrameClass和m_pViewClass,这位以后的动态创建Document/Frame/View"三口组"打下了基础。

                       /////////////////////////////////////////////////
                       /*       3.文档模板列队       */
                       ///////////////////////////////////////////////

  文档模板初始化结束后,InitInstance函数调用了CWinApp::AddDocTemplate(pDocTemplate)函数,其主要目的是将以初始化后的那个文档模板加入到文档模板链表中,并由CDocManager类对象进行管理。以下操作就是为了完成此工作。

  注释1: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\appui2.cpp

void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)//将CMultiDocTemplate* pDocTemplate
{                                                    //传给pTemplate
if (m_pDocManager == NULL)
   m_pDocManager = new CDocManager;
m_pDocManager->AddDocTemplate(pTemplate);
}

  注释2: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\docmgr.cpp

CDocManager::CDocManager()
{
}//目前是一个空函数;

void CDocManager::AddDocTemplate(CDocTemplate* pTemplate)//部分源代码
{
if (pTemplate == NULL)
{
   ...//
}
else
{
   ASSERT_VALID(pTemplate);
   ASSERT(m_templateList.Find(pTemplate, NULL) == NULL);// must not be in list
   pTemplate->LoadTemplate();
   m_templateList.AddTail(pTemplate);//CPtrList m_templateList is a member                                                             //of CDocManager
}
}

                       ////////////////////////////////////////////////////////
                       /*    4.创建程序主框架窗口    */
                       //////////////////////////////////////////////////////

  应用程序实例化了一个CMainFrame类对象,并调用LoadFrame函数加载窗口资源创建主框架窗口。以下是创建主框架窗口的流程。

  创建窗口的主要代码是:pMainFrame->LoadFrame(IDR_MAINFRAME);LoadFrame函数是MFC包装了窗口创建过程的函数,在后面动态创建Child窗口时,它还将披挂上阵(但稍有不同)。下面是它的源代码,让我们仔细分析一下:

  注释1: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\winmdi.cpp
   
BOOL CMDIFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)          
{
if (!CFrameWnd::LoadFrame(nIDResource, dwDefaultStyle,
   pParentWnd, pContext))
   return FALSE;

// save menu to use when no active MDI child window is present
ASSERT(m_hWnd != NULL);
m_hMenuDefault = ::GetMenu(m_hWnd);
if (m_hMenuDefault == NULL)
   TRACE(traceAppMsg, 0, "Warning: CMDIFrameWnd without a default menu.\n");
return TRUE;
}

  CMDIFrameWnd::LoadFrame调用了其基类CFrameWnd的LoadFrame,并将参数原封不动的传给它。

  注释2: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\winfrm.cpp

BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,
CWnd* pParentWnd, CCreateContext* pContext)          //部分源代码
{
...//
CString strFullString;
if (strFullString.LoadString(nIDResource))
   AfxExtractSubString(m_strTitle, strFullString, 0);    // first sub-string

VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));

// attempt to create the window
LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);
CString strTitle = m_strTitle;
if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,
   pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext))
{
   return FALSE;   // will self destruct on failure normally
}

...//
if (pContext == NULL)   // send initial update
   SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);

return TRUE;
}

                       /////////////////////////////////////////////////////////////////
                       /* 4.1注册应用程序主框架窗口类 */
                       ///////////////////////////////////////////////////////////////

  在传统的Win32API编程中,创建窗口一般步骤是定义窗口类,注册窗口类,并调用::CreateWindow函数来创建。前面说过LoadFrame函数封装了MFC创建窗口的过程,那么也就是说LoadFrame函数将负责定义窗口类,注册窗口类等琐碎工作。下面我们就通过挖掘源代码来看看LoadFrame函数是如何完成这些工作的。

  LoadFrame首先调用AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG),

  注释1: 以下宏定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\afximpl.h

#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
   
   注释2: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\wincore.cpp
  
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)//部分源代码
{
// mask off all classes that are already registered
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
fToRegister &= ~pModuleState->m_fRegisteredClasses;
if (fToRegister == 0)
   return TRUE;

LONG fRegisteredClasses = 0;

// common initialization
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS));   // start with NULL defaults
wndcls.lpfnWndProc = DefWindowProc;
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_WND_REG)
{
   // Child windows - no brush, no icon, safest default class styles
   wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
   wndcls.lpszClassName = _afxWnd;
   if (AfxRegisterClass(&wndcls))
    fRegisteredClasses |= AFX_WND_REG;
}
...//
if (fToRegister & AFX_WNDMDIFRAME_REG)
{
   // MDI Frame window (also used for splitter window)
   wndcls.style = CS_DBLCLKS;
   wndcls.hbrBackground = NULL;
   if (_AfxRegisterWithIcon(&wndcls, _afxWndMDIFrame, AFX_IDI_STD_MDIFRAME))
    fRegisteredClasses |= AFX_WNDMDIFRAME_REG;
}
if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
{
   // SDI Frame or MDI Child windows or views - normal colors
   wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
   wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
   if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
    fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
}
        
       ...//
// must have registered at least as mamy classes as requested
return (fToRegister & fRegisteredClasses) == fToRegister;
   }

  MFC预定义了若干个“窗口类模板”,比如"AFX_WNDMDIFRAME_REG","AFX_WNDFRAMEORVIEW_REG"等,MFC在LoadFrame函数中调用AfxEndDeferRegisterClass函数为你的应用程序预注册了适当的窗口类。本例中预注册的窗口类为AFX_WNDFRAMEORVIEW_REG。(注意是预注册,如果你在后面更改了CREATESTRUCT结构的域成员,MFC还会根据你的更改重新为你的应用程序正式注册新的窗口类,稍候会有详细叙述)
   
  预注册完窗口类,MFC将判断你是否想更改窗口类的各参数。若你更改了,则MFC会重新注册新类;否则源预注册的窗口类就将成为正式的窗口类。下面我们来看看MFC的判断过程:此判断过程由GetIconWndClass开始
  LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle, nIDResource);

  注释3: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\winfrm.cpp
  
LPCTSTR CFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)//部分源代码
{
...//
HICON hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDResource));
if (hIcon != NULL)
{
   CREATESTRUCT cs;
   memset(&cs, 0, sizeof(CREATESTRUCT));
   cs.style = dwDefaultStyle;
   PreCreateWindow(cs);
    // will fill lpszClassName with default WNDCLASS name
    // ignore instance handle from PreCreateWindow.

   WNDCLASS wndcls;
   if (cs.lpszClass != NULL &&
    GetClassInfo(AfxGetInstanceHandle(), cs.lpszClass, &wndcls) &&
    wndcls.hIcon != hIcon)
   {
    // register a very similar WNDCLASS
    return AfxRegisterWndClass(wndcls.style,
     wndcls.hCursor, wndcls.hbrBackground, hIcon);
   }
}
return NULL;        // just use the default
}

  GetIconWndClass函数将调用CMainFrame::PreCreateWindow(CREATESTRUCT& cs)来看看应用程序是否修改了CREATESTRUCT结构的域成员。CMainFrame::PreCreateWindow调用CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs),后者的代码如下:

BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)//in winmdi.cpp
{
if (cs.lpszClass == NULL)
{
   VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG));
   cs.lpszClass = _afxWndMDIFrame;
}
return TRUE;
}
MFC将为应用程序注册AFX_WNDMDIFRAME_REG预定义窗口类,并设置cs.lpszClass = _afxWndMDIFrame。
在应用程序的代码中我更改了cs结构:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CMDIFrameWnd::PreCreateWindow(cs) )
   return FALSE;
// TODO: 在此处通过修改 CREATESTRUCT cs 来修改窗口类或
// 样式
cs.dwExStyle=~WS_EX_CLIENTEDGE;
return TRUE;
}   

  CMainFrame::PreCreateWindow返回后,GetIconWndClass函数调用GetClassInfo函数重新收集cs信息(此时的信息已是更改后的了),并调用AfxRegisterWndClass函数重新注册该窗口类(此窗口类为该应用程序的正式窗口类)。到此为止窗口类注册完毕,以后程序还会调用一次CMainFrame::PreCreateWindow,不过那此只是"过门不如"而已。

                       //////////////////////////////////////////////////////////
                       /*    4.2主框架窗口创建开始    */
                       ////////////////////////////////////////////////////////

  开始进入创建框架窗口的实质阶段。看LoadFrame函数做了什么?原来它调用了Create函数。

  注释1: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\winfrm.cpp

BOOL CFrameWnd::Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle,
CCreateContext* pContext)
{
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
   // load in a menu that will get destroyed when window gets destroyed
   HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
   if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL)
   {
    TRACE(traceAppMsg, 0, "Warning: failed to load menu for CFrameWnd.\n");
    PostNcDestroy();            // perhaps delete the C++ object
    return FALSE;
   }
}

m_strTitle = lpszWindowName;    // save title for later

if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle,
   rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
   pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
{
   TRACE(traceAppMsg, 0, "Warning: failed to create CFrameWnd.\n");
   if (hMenu != NULL)
    DestroyMenu(hMenu);
   return FALSE;
}

return TRUE;
}

  简单地说CFrameWnd::Create函数调用了基类的CWnd::CreateEx;
  注释2: 以下函数定义在:..\Visual Studio.NET\vc7\atlmfc\src\mfc\wincore.cpp

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)//部分源代码
{
// allow modification of several common create parameters
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;

if (!PreCreateWindow(cs))
{
   PostNcDestroy();
   return FALSE;
}

AfxHookWindowCreate(this);
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);
        ...//
       
        return TRUE;
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CMDIFrameWnd::PreCreateWindow(cs) )
   return FALSE;
// TODO: 在此处通过修改 CREATESTRUCT cs 来修改窗口类或
// 样式
        cs.dwExStyle=~WS_EX_CLIENTEDGE;
return TRUE;
}

BOOL CMDIFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
   VERIFY(AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG));
   cs.lpszClass = _afxWndMDIFrame;
}
return TRUE;
}

  CWnd::CreateEx调用了CMainFrame::PreCreateWindow,但此次AfxDeferRegisterClass将不会被调用。也就是我上面所说的“过门不入”。

  CWnd::CreateEx函数还调用了AfxHookWindowCreate(this);后者是干什么的呢?其实它与消息映射和命令传递有关,我将在MFC技术内幕系列之(四)--《MFC消息映射与消息传递内幕》一文中详解。
   
  CWnd::CreateEx调用Win32API ::CreateWindowEx函数(传统的Win32API程序员一定不陌生这个函数),
就这样主框架窗口创建结束。

来自: http://hi.baidu.com/njy1102/blog/item/943da731ddb2a71ceac4af88.html
阅读(324) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-12-07 10:14:03

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com