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

引言:

我们知道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
{
// Attributes
	LPCSTR m_lpszClassName; 	//类名,一般是指包含CRuntimeClass对象的类的名称
	int m_nObjectSize;	//包含CRuntimeClass对象的类sizeof的大小,不包括它分配的内存
	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;	//m_pBaseClass的指针(函数)是MFC运行时确定类层次的关键,它一个简单的单向链表
#endif

// Operations//这个函数给予CObject 派生类运行时动态建立的能力
	CObject* CreateObject();
//这个函数使用 m_pBaseClass或 m_pfnGetBaseClass遍历整个类层次确定是否pBaseClass指向的类是基类,
//使用它可以判断某类是否是从pBaseClass指向的类在派生来。
	BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

	// dynamic name lookup and creation
	static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);
	static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);
	static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
	static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);

// Implementation
	void 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 classes
	const 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 CObject
class 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是序列号,特征值

// CA

CA::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"

// CMyBase
IMPLEMENT_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 成员函数



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值