引言:
我们知道MFC核心技术之一是RTTI (Runtime Type Identification, 运行时类型识别)。现在的C++本身就有RTTI功能(typeid(p))- -需要编译器的支持。而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具体实现用例
//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;
}
[Test]
MyKeyName=CA
五、程序中的宏定义解析与程序分析
::GetPrivateProfileString(//得到配置文件中的类名字符串
_T("Test"), //段的名称
_T("MykeyName"),// 键的名称
_T(""), //缺省值,
className,// 存在哪个数组,
100, //数组的大小,
_T("C:\\Users\mazi\Desktop\config.ini")//文件的路径名称
);
2、实现了RTTI,那么这些宏定义具体做了什么事?
重新编译,在项目文件的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 成员函数