MFC的RTTI机制的宏实现示例解析

来源:互联网 发布:十大网络主播评选结果 编辑:程序博客网 时间:2024/06/05 04:36

引言:

我们知道MFC核心技术之一是RTTI (Runtime Type Identification, 运行时类型识别)。现在的C++本身就有RTTI功能(typeidp- -需要编译器的支持。而MFC早92年用宏实现了运行时类型识别功能。

一、为什么需要RTTI

当涉及到处理异类容器和根基类层次(如 MFC)时,不可避免要对对象类型进行动态判断,也就是动态类型的侦测。

(例如,我们用抽象基类的指针指向其派生类的新建对象。在一些情况下,需要判断该指针指向的是哪一个类)

如何确定对象的动态类型呢?这就需要运行时类型识别。

二、MFC中实现RTTI的基础知识

1、RUNTIME_CLASS 获得运行时类的CRuntimeClass结构指针 RUNTIME_CLASS( class_name )

解释:RUNTIME_CLASS宏使程序能实时创建类的实例。为了让这个宏起作用,定义的类必须从CObject类派生而来,并且在派生类的定义中必须使用宏DECLARE_DYNAMIC,DECLARE_DYNCREATE或DECLARE_SERIAL,
在派生类的实现文件中必须使用宏IMPLEMENT_DYNAMIC,IMPLEMENT_DYNCREATE或IMPLEMENT_SERIAL。


2、DECLARE_DYNAMIC 提供基本的运行时类型识别(声明) DECLARE_DYNAMIC( class_name )
      IMPLEMENT_DYNAMIC 提供基本的运行时类型识别(实现) IMPLEMENT_DYNAMIC (class_name, base_class_name )

解释:DECLARE_DYNAMIC只能使CObject派生类对象具有基本的类型识别功能,可以通过CObject::IsKindOf(ClassName)测试对象与给定类ClassName的关系。


3、DECLARE_DYNCREATE 动态创建(声明) DECLARE_DYNCREATE( class_name )
   IMPLEMENT_DYNCREATE 动态创建(实现) IMPLEMENT_DYNCREATE( class_name,base_class_name )

解释:DECLARE_DYNCREATE包括了DECLARE_DYNAMIC的功能。

new可以用来创建对象,但不是动态的。比如说,你要在程序中实现根据拥护输入的类名来创建类的实例,下面的做法是通不过的: 
         char szClassName[60]; 
         cin >> szClassName; 
         CObject* pOb=new szClassName; //通不过 
这里就要用到DEClARE_DYNCREATE/IMPLEMENT_DYNCREATE定义的功能了。


4、DECLARE_SERIAL 对象序列化(声明) DECLARE_SERIAL( class_name )
   IMPLEMENT_SERIAL 对象序列化(实现)IMPLEMENT_SERIAL(class_name,base_class_name,wSchema)

解释:DECLARE_SERIAL包括了DECLARE_DYNAMIC和 DECLARE_DYNCREATE的功能。


5、对于MFC中每个从CObject派生的类来说,都有一个相关的CRuntimeClass结构体,在程序运行时可以访问该结构体来获取对象及其基类的信息。CRuntimeClass是一个结构体,并且其本身并没有基类。

struct CRuntimeClass{// AttributesLPCSTR m_lpszClassName; //类名,一般是指包含CRuntimeClass对象的类的名称int m_nObjectSize;//包含CRuntimeClass对象的类sizeof的大小,不包括它分配的内存UINT m_wSchema; // schema number of the loaded classCObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class指向一个建立实例的构造函数#ifdef _AFXDLLCRuntimeClass* (PASCAL* m_pfnGetBaseClass)();#elseCRuntimeClass* m_pBaseClass;//m_pBaseClass的指针(函数)是MFC运行时确定类层次的关键,它一个简单的单向链表#endif// Operations//这个函数给予CObject 派生类运行时动态建立的能力CObject* CreateObject();//这个函数使用 m_pBaseClass或 m_pfnGetBaseClass遍历整个类层次确定是否pBaseClass指向的类是基类,//使用它可以判断某类是否是从pBaseClass指向的类在派生来。BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;// dynamic name lookup and creationstatic CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);static CObject* PASCAL CreateObject(LPCSTR lpszClassName);static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);// Implementationvoid Store(CArchive& ar) const;static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);// CRuntimeClass objects linked together in simple list// 单向链表,每个类的CRuntimeClass结构体对象的 m_pNextClass 都指向其直接基类的CRuntimeClass结构体对象// 这一点可以在IMPLEMENT_RUNTIMECLASS 宏定义中看到CRuntimeClass* m_pNextClass;       // linked list of registered classesconst AFX_CLASSINIT* m_pClassInit;};

三、MFC种实现RTTI的前提条件

1、类必须直接或间接继承于CObject类

2、必须声明和定义:(DECLARE_DYNAMIC和DECLARE_DYNCREATE)或者DECLARE_SERIAL宏

3、IMPLEMENT_XXXX(CA,CObject);必须写在类的cpp文件中,#include下面,函数的上面:

      DECLARE_XXXX(CA);写在类的.h文件中,//一定要写在类的大括号中,在public域中

四、RTTI具体实现用例

通过MFC的RTTI技术,实现程序外利用配置文件实时更改程序的执行效果。
1、新建工程



2、
//A.h#pragma once#include"MyBase.h"// CA 命令目标//class CA : public CObjectclass CA : public CMyBase{public:CA();virtual ~CA();//  第一次宏替换//DECLARE_DYNAMIC(CA);//一定要写在大括号中,在public域中//  第二次宏替换//DECLARE_DYNCREATE(CA);//  第三次宏替换DECLARE_SERIAL(CA);//可以冲磁盘读数据并写回int add(int a, int b);};

// A.cpp : 实现文件//#include "stdafx.h"#include "MFCTestProject.h"#include "A.h"//第一次宏替换//IMPLEMENT_DYNAMIC(CA,CObject);//本类名和基类名//第二次宏替换//IMPLEMENT_DYNCREATE(CA, CObject);//本类名和基类名//第三次宏替换IMPLEMENT_SERIAL(CA, CObject,1);//第三个参数是正整型,1是序列号,特征值// CACA::CA(){}CA::~CA(){}int CA::add(int a, int b){return a + b;}

//B.h#pragma once#include "MyBase.h"class CB :public CMyBase{public:CB();virtual ~CB();int add(int a, int b);DECLARE_SERIAL(CB);};

//B.cpp#include "stdafx.h"#include "B.h"IMPLEMENT_SERIAL(CB, CObject, 1);//第三个参数是正整型,1是序列号,特征值CB::CB(){}CB::~CB(){}int CB::add(int a, int b){return a - b;}

//MyBase.h#pragma once// CMyBase 命令目标class CMyBase : public CObject{public:CMyBase();virtual ~CMyBase();virtual int add(int a, int b);DECLARE_SERIAL(CMyBase);};

// MyBase.cpp : 实现文件//#include "stdafx.h"#include "MyBase.h"// CMyBaseIMPLEMENT_SERIAL(CMyBase, CObject, 1);CMyBase::CMyBase(){}CMyBase::~CMyBase(){}int CMyBase::add(int a, int b){return -1;}// CMyBase 成员函数

//MFCTestProject.h#pragma once#include "resource.h"

// MFCTestProject.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include "MFCTestProject.h"#include"A.h"#ifdef _DEBUG#define new DEBUG_NEW#endif// 唯一的应用程序对象CWinApp theApp;using namespace std;int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]){int nRetCode = 0;HMODULE hModule = ::GetModuleHandle(NULL);if (hModule != NULL){// 初始化 MFC 并在失败时显示错误if (!AfxWinInit(hModule, NULL, ::GetCommandLine(), 0)){// TODO:  更改错误代码以符合您的需要_tprintf(_T("错误:  MFC 初始化失败\n"));nRetCode = 1;}else{// TODO:  在此处为应用程序的行为编写代码。CA a;//第一次宏替换测试:得到当前类的类名//CRuntimeClass *p=a.GetRuntimeClass();//cout << p->m_lpszClassName;//CRuntimeClass下的类名//第二次宏替换测试:创建新对象//CA*p = (CA*)CA::CreateObject;////或者//CA*pp = (CA*)CA::GetThisClass()->CreateObject();//第三次宏替换测试:通过字符串查到类的构造信息,通过此构造信息可以创建对象。实现了完全的开闭原则//反射//CRuntimeClass *p = CRuntimeClass::FromName("CA");//遍寻链表,找到这个类名对应的节点//CA*p = (CA*)p->CreateObject();////或者//CRuntimeClass::CreateObject("CA");//system("pause");CMyBase *p = NULL;/////////////////配置文件:.ini文件WCHAR className[100];::GetPrivateProfileString(_T("Test"), _T("MykeyName"), _T(""), className, 100, _T("C:\\Users\\mazi\\Desktop\\config1.ini"));//得到配置文件中的字符串.第一个参数为段的名称,第二个参数为键的名称。//缺省值,存在哪个数组,数组的大小, 文件的路径名称p = (CMyBase*)CRuntimeClass::CreateObject(className);///////////////////int res=p->add(3, 5);cout << res << endl;system("pause");}}else{// TODO:  更改错误代码以符合您的需要_tprintf(_T("错误:  GetModuleHandle 失败\n"));nRetCode = 1;}return nRetCode;}

配置文件:config.ini
[Test]MyKeyName=CA

五、程序中的宏定义解析与程序分析

1、以上程序中,先分别对DECLARE_DYNAMIC、DECLARE_DYNCREATE、DECLARE_SERIAL三个宏进行测试实验,查看实验效果(只用到A类[直接继承于CObject])。最后再用DECLARE_SERIAL宏实现RTTI,通过配置文件来决定程序的运行效果。
其中:GetPrivateProfileString函数从.ini配置文件中读取参数
::GetPrivateProfileString(//得到配置文件中的类名字符串_T("Test"), //段的名称_T("MykeyName"),// 键的名称_T(""), //缺省值,className,// 存在哪个数组,100, //数组的大小,_T("C:\\Users\mazi\Desktop\config.ini")//文件的路径名称);

2、实现了RTTI,那么这些宏定义具体做了什么事?

将宏替换输出到文件,进行查看,右击项目--》属性--》C/C++预处理器--》将预处理到文件、保留注释改为(是)--》应用、确定。
重新编译,在项目文件的Debug文件中找到后缀名为.i的文件打开查看最后
可以看到宏替换的内容:
//宏替换究竟都替换了什么??//第三次   IMPLEMENT_SERIAL(CA, CObject,1);其实MFC内部代码做了以下内容:CObject* __stdcall CA::CreateObject(){return new CA;}extern AFX_CLASSINIT _init_CA;//AFX_CLASSINIT结构体,对全局变量进行声明(一个结构体变量)CRuntimeClass* __stdcall CA::_GetBaseClass(){return (CObject::GetThisClass());}__declspec(selectany) CRuntimeClass CA::classCA ={"CA",sizeof(class CA),1,CA::CreateObject,&CA::_GetBaseClass,0,&_init_CA};CRuntimeClass* __stdcall CA::GetThisClass(){return ((CRuntimeClass*)(&CA::classCA));}CRuntimeClass* CA::GetRuntimeClass() const{return ((CRuntimeClass*)(&CA::classCA));}AFX_CLASSINIT _init_CA((CA::GetThisClass()));//结构体 结构体变量名(结构体参数),这个参数其实是一个构造函数的首地址CArchive& __stdcall operator>>(CArchive& ar, CA* &pOb){pOb = (CA*) ar.ReadObject((CA::GetThisClass()));//把对象读到pOb中return ar;};//第二次 //IMPLEMENT_DYNCREATE(CA, CObject);其实MFC内部代码做了以下内容:CObject* __stdcall CA::CreateObject() //返回CObject* 类型,子类对象可以赋值到父类指针,通用。自己使用的时候需要强制类型转换为子类的类型。{return new CA; //需要自己释放}CRuntimeClass* __stdcall CA::_GetBaseClass(){return (CObject::GetThisClass());}__declspec(selectany) const CRuntimeClass CA::classCA ={"CA",sizeof(class CA),0xFFFF,CA::CreateObject, //传递函数指针,将函数指针放在classCA对象中&CA::_GetBaseClass,0,0};CRuntimeClass* __stdcall CA::GetThisClass(){return ((CRuntimeClass*)(&CA::classCA));}CRuntimeClass* CA::GetRuntimeClass() const{return ((CRuntimeClass*)(&CA::classCA));};//本类名和基类名//第一次   //IMPLEMENT_DYNAMIC(CA,CObject);其实MFC内部代码做了以下内容:CRuntimeClass* __stdcall CA::_GetBaseClass() //得到基础类{return (CObject::GetThisClass()); //}//CRuntimeClass是结构体,不是类。给CA类中静态成员变量classCA(是一个CRuntimeClass结构体)赋初始值。。__declspec(selectany)编译器说明符,__declspec(selectany) const CRuntimeClass CA::classCA = {"CA", //第一个参数:类名sizeof(class CA),//第二个参数,类的大小0xFFFF,0,&CA::_GetBaseClass,0,0};CRuntimeClass* __stdcall CA::GetThisClass() //静态函数,可以直接访问静态成员classCA{return ((CRuntimeClass*)(&CA::classCA));}CRuntimeClass* CA::GetRuntimeClass() const //实例函数也可以访问静态变量{return ((CRuntimeClass*)(&CA::classCA));};//本类名和基类名// CA// CA 成员函数



原创粉丝点击