MFC学习步步高04

来源:互联网 发布:淘宝客助手二合一 编辑:程序博客网 时间:2024/04/27 18:00

  本人同意他人对我的文章引用,但请在引用时注明出处,谢谢.作者:蒋志强 

前几次的学习中,我们知道了MFC程序结构的大致流程,知道MFC本质上和传统的SDK API编程是完全一样的.这次我们要了解MFC结构中很重要的两个技术: 运行时类型识别(RTTI)和动态对象创建.
 
RTTI是RunTime Type Indentificaion的英文缩写,也是运行时类型识别的意思.所谓运行时类型识别就是说,程序在运行的过程中具备识别内存中的对象是哪个类的实例的能力.
 
比如说,我们定义了一个类Class A1,并在程序中生成了这个类的一个对象A1 a1.那么,我们如果希望有这个a1对象时,可以在程序运行中知道a1是A1类的对象,这种能力就是运行时类型识别(RTTI).
 
而动态对象创建是指程序在运行时,动态产生某个类的对象的能力.在C++中,我们可以在程序中方便的使用关键字new来动态对象创建.但是,这要求我们知道我们要new的对象是哪种类型的.也就是说我们知道需要new一个CView还是需要new一个CWnd.如果我们只知道要new一个对象,而不知道要new哪种类型的对象,我们肯定无法动态创建对象.但我们有RTTI的能力的话,我们就知道需要创建哪种类型的对象了,再来做动态对象创建就是很容易的一个new语句了.所以动态对象创建和RTTI是紧密联系的.在MFC中,RTTI和动态对象创建这两种能力是通过MFC代码中的几个设计巧妙的宏来完成的,下面我们来看看MFC是怎么使用这几个宏完成这样的功能的.
 
我们新建一个基于单文档结构的工程,名字叫mfcstudy001,我们来看看MFC是怎么做的,我们找到CMfcstudy001Doc这个类的定义,我们可以看到在该类的定义中有这样的代码:
 
protected// create from serialization only
       CMfcstudy001Doc();
       DECLARE_DYNCREATE(CMfcstudy001Doc)
其中的DECLARE_DYNCREATE是MFC预定义的宏,在VC6的环境中我们选中这个宏,直接按F12转到该宏的定义处,可以发现这个宏的定义如下:
#define DECLARE_DYNCREATE(class_name) 
       DECLARE_DYNAMIC(class_name) 
       
static CObject* PASCAL CreateObject();
所以说进行一次宏替换后CMfcstudy001Doc类的定义中的宏展开应该是这样的:
protected// create from serialization only
DECLARE_DYNAMIC(CMfcstudy001Doc) 
       
static CObject* PASCAL CreateObject();
而该宏中又有一个叫做DECLARE_DYNAMIC的宏,我们再按F12找到这个宏的定义如下:
宏里面的##符号表示将该符号前后的字符串连接起来,所以在CMfcstudy001Doc类的定义中的宏完全展开后,应该是下面这样的代码:
protected// create from serialization only
protected
       
static CRuntimeClass* PASCAL _GetBaseClass(); 
public
       
static const AFX_DATA CRuntimeClass classCMfcstudy001Doc;
       
virtual CRuntimeClass* GetRuntimeClass() const
       
static CObject* PASCAL CreateObject();
这个宏替换后的代码添加了3个方法和一个CRuntimeClass类型的变量.这个CRuntimeClass类型是MFC预定义的结构体类型,该类型的变量是实现RTTI的关键所在.因为该结构体成员变量被声明为static的,也就是静态的.我们知道在C++中,全局变量和静态变量都会在程序进入入口函数以前(main函数或者是WinMain函数)进行分配空间实例化.所以这个CRuntimeClass类型的结构体变量成员,在程序执行第一条语句之前就生成了.如果每一个类都有这样的一个静态成员,并且该结构记录该类的类型信息的话,这就可以作为RTTI的基础,来判断类型了!实际上MFC就是这样做的,我们先按F12来看看CRuntimeClass这种结构体的定义:
struct CRuntimeClass
{
// Attributes
       LPCSTR m_lpszClassName;
       
int m_nObjectSize;
       UINT m_wSchema; 
// schema number of the loaded class
       CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
#ifdef _AFXDLL
       CRuntimeClass
* (PASCAL* m_pfnGetBaseClass)();
#else
       CRuntimeClass
* m_pBaseClass;
#endif
 
// Operations
       CObject* CreateObject();
       BOOL IsDerivedFrom(
const CRuntimeClass* pBaseClass) const;
 
// Implementation
       void Store(CArchive& ar) const;
       
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
 
       
// CRuntimeClass objects linked together in simple list
       CRuntimeClass* m_pNextClass;       // linked list of registered classes
}
;
我们从代码的定义和注释可以知道,这个结构体记录了类的名字,类的大小等信息,还有一个指向CRuntimeClass类型的指针.这个指针的名字是m_pBaseClass,所以我们猜测是指向该类的基类中的这个结构体,从而按照类的继承关系组织,成为一个根节点为CObject类中名为classCObject的CRuntimeClass结构体的多叉树.我们会在后面的分析中验证我们的猜测是正确的.结构体中CreateObject方法很明显是用于动态创建该类型的对象,我们都可以在后面的分析中验证.至于后面的Store是和序列化相关的内容,这和我们今天讨论的主题无关.我们先忽略跳过.
现在我们到CMfcstudy001Doc类的代码文件CMfcstudy001Doc.cpp中去看看.我们在文件的头部可以看到这样的一个宏: IMPLEMENT_DYNCREATE(CMfcstudy001Doc, CDocument)
按F12跳转到定义处,我们看到这个宏是这样定义的:
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) 
       CObject
* PASCAL class_name::CreateObject() 
              
return new class_name; } 
       IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 
0xFFFF
              class_name::CreateObject)
这个宏里又嵌套了一个名为IMPLEMENT_RUNTIMECLASS的宏,我们直接按F12去看看IMPLEMENT_RUNTIMECLASS,如下:
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) 
       CRuntimeClass
* PASCAL class_name::_GetBaseClass() 
              
return RUNTIME_CLASS(base_class_name); } 
       AFX_COMDAT 
const AFX_DATADEF CRuntimeClass class_name::class##class_name = 
              #class_name, 
sizeof(class class_name), wSchema, pfnNew, 
                     
&class_name::_GetBaseClass, NULL }

       CRuntimeClass
* class_name::GetRuntimeClass() const 
              
return RUNTIME_CLASS(class_name); } 
经过两次宏替换后,以前CMfcstudy001Doc.cpp文件中IMPLEMENT_DYNCREATE(CMfcstudy001Doc,CDocument)在预编译后,变成下面的代码:
 
CObject* PASCAL CMfcstudy001Doc::CreateObject()       return new CMfcstudy001Doc; }     CRuntimeClass* PASCAL CMfcstudy001Doc::_GetBaseClass()
              
return RUNTIME_CLASS(CDocument); } 
       AFX_COMDAT 
const AFX_DATADEF CRuntimeClass CMfcstudy001Doc::class CMfcstudy001Doc = 
              “CMfcstudy001Doc”, 
sizeof(class CMfcstudy001Doc), wSchema, pfnNew,
                     
& CMfcstudy001Doc::_GetBaseClass, NULL }

       CRuntimeClass
* class_name::GetRuntimeClass() const 
              
return RUNTIME_CLASS(CMfcstudy001Doc); } 
在该代码中,首先实现CreateObject方法,产生该类型的对象,第二部分是填充CMfcstudy001Doc类中的CRuntimeClass类型的结构体,我们看到由于这个宏设计得非常巧妙,在填充结构体的倒数第二个字段时,使用& CMfcstudy001Doc::_GetBaseClass,这样就按照类的继承结构由下向上将这些CRuntimeClass组织成为了以为CObject为根的多叉树.对于CObject比较特殊,因为它是最上级的类,它没有父类了,所以只要将CObject中的名为classCObject的CRuntimeClass结构体中m_pBaseClass指针设置为NULL,就可以很完美的建立这个结构体了!
实际上对于所以需要做动态类型识别和动态类型创建的类,MFC会自动的在该类的定义加上# DECLARE_DYNCREATE宏,在该类的实现代码加上#IMPLEMENT_DYNCREATE宏.有了这两个宏以后,根据我们上面的分析,我们可以知道MFC是这样做的工作.
所有这些被VC自动加上这两个宏的类,都定义了名字为class然后加上该类名的一个CRuntimeClass静态结构,以及_GetBaseClass()和GetRuntimeClass()这两个方法.
程序被双击运行后,加载到系统的内存中,在程序进入入口函数以前,所有的这些有静态的CRuntimeClass成员的类,它们中的该静态结构体被分配空间,并且被结构体内容被填充.并且由于MFC的IMPLEMENT_DYNCREATE的巧妙设计,这些静态结构体相互按照继承关系,组织成一个多叉树的结构.这些结构体中记录了对应该类的信息.可以根据类的名字字符串字段来判断类型,并且向上搜索直到顶层的CObject类中的静态结构体,实现RTTI.在这些结构体中还有CreateObject方法,该方法调用对应类中定义的静态方法CreateObject实现动态创建.做所有这些事情,我们所需要的只是得到该类的CRuntimeClass类型静态结构体classXXX(XXX表示该类的名字).而由于该结构体是静态的,所以我们可以在任何时候,使用类的名字来访问它,实现所有的这些功能.
还有一个宏,我们没有看, RUNTIME_CLASS,它的定义是这样的:
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
也就是说RUNTIME_CLASS(CMfcstudy001Doc)替换后
就变成((CRuntimeClass*)(& CMfcstudy001Doc::class CMfcstudy001Doc)),我们给出类的名字,通过这个宏替换后,就是该类中CRuntimeClass类型的静态结构的地址.
有了这个结构体的地址,我们可以用这个结构体做RTTI,动态对象创建.真是感叹MFC的设计者的巧妙构思啊!!!
回忆我们以前的讨论中有提到,在文档/视图结构的MFC程序中,CWinApp中使用一个CDocManger类型的对象管理文档模版对象,并组织成一个队列,而文档模版对象从逻辑上组织视图对象,文档对象和主窗口对象.单文档的情况下CDocManager只能在队列中加入一个文档模版对象,多文档可以在队列中加入多个文档模版对象.
但是文档模版对象是如何从”逻辑”上进行组织的呢?其实就是通过的各个类中静态的CRuntimeClass类型变量来做的,我们来看看CWinApp的InitInstance方法,这部分代码我们以前分析过,我们明白RTTI和动态对象创建机制后,在看一下:
BOOL CMfcstudy001App::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(CMfcstudy001Doc),
              RUNTIME_CLASS(CMainFrame),       
// main SDI frame window
              RUNTIME_CLASS(CMfcstudy001View));
       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;
}

 
在该函数中创建了一个CSingleDocTemplate的对象,构造函数中传递的参数中使用了RUNTIME_CLASS宏,现在我们知道这个宏实际上就是得到给定的类中的CRuntimeClass静态结构体的指针.既然是CSingleDocTemplate的构造函数中,传递了主窗口类,视图类,文档类中的CRuntimeClass静态结构体的指针.那么就可以使用该文档模版对象中得到的这些CRuntimeClass静态结构体去实现主窗口类,视图类,文档类的动态对象创建了!!!
 
我们只分析了MFC实现RTTI和动态对象创建中最关键的部分,还有一些MFC中涉及这两个技术的细节,我们没有继续深入.在对我们现在所理解的基础上去看这两个技术的代码细节是很容易的事情了,我不写出来,主要是由于我都写了的话,内容会太多了,而我现在真的很疲倦了L.但有了上面的理解,这些内容就都很简单了.
 
OK,很开心吧.MFC的设计者,微软的工程师的设计真是不禁让我拍案惊叹,实在是太巧妙了,太漂亮了.It is more than a skill, but a kind of art.下次我们再继续研究MFC的设计,内容会越来越精彩的.
To be continued......

 

原创粉丝点击