Visual C++中的几个宏

本文转载自:

百度百科:DECLARE_DYNCREATE

百度百科:IMPLEMENT_DYNCREATE

百度百科:DECLARE_SERIAL

百度百科:IMPLEMENT_SERIAL

DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC

DECLARE_DYNCREATE和DECLARE_DYNAMIC的区别


DECLARE_DYNCREATE

简介

编辑
DECLARE_DYNCREATE( class_name )
参数:
class_name 类的实际名字(不用引号括起来)。

说明

编辑
使用DECLARE_DYNCREATE宏可以使每个CObject的派生类的对象具有运行时动态创建的能力。框架利用这种能力来动态创建对象,例如,当它在 串行化过程中从 磁盘读取对象的时候。文档、视图和 框架类必须支持动态创建,因为框架需要动态地创建它们。
在类的.H模块中加入DECLARE_DYNCREATE宏,然后在每个需要访问这个类的对象的.CPP模块中包含这个模块。
如果在类声明中包含了DECLARE_DYNCREATE,那么必须在类的实现中包含IMPLEMENT_DYNCREATE宏。
关于DECLARE_DYNCREATE宏的更多信息参见“Visual C++程序员指南”中的“CObject类主题”。

IMPLEMENT_DYNCREATE

IMPLEMENT_DYNCREATE(class_name,base_class_name)
说明:
通过DECLARE_DYNCREATE宏来使用IMPLEMENT_DYNCREATE宏,以允许CObject派生类对象在运行时自动建立。主机使用此功能自动建立对象,例如,当它在 串行化过程中从磁盘读取一个对象时,它在类工具里加入IMPLEMENT_DYNCREATE宏。若用户使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏,那么接着使用RUNTIME_CLASS宏和CObject::IsKindOf成员函数以在运行时确定对象类。若declare_dyncreate包含在定义中,那么IMPLEMENT_DYNCREATE必须包含在类工具中。

DECLARE_SERIAL

概念

编辑
DECLARE_SERIAL / IMPLEMENT_SERIAL 宏的技术详解(转) DECLARE_DYNAMIC表明的是支持类型信息, 有了这个宏,我们就可以判断一个类究竟是什么类,比如
class A;
class B:public A;
A a;
B b;
有一个 指针 class *pA 它指向一个对象, 请问你怎么知道pA指向的是a对象还是b对象,这时如果有类型信息,我们就可以知道pA到底是什么对象, 其实,它内部的实现原理是一个字符串,所以,进行这个判断时,实际上是字符串比较.
DECLARE_DYNCREATE是动态创建的意思.这个有点类似Com的 类工厂.
它实际上是用类CRunTime class记录了类的静态创建函数的地址.这个特性在很多地方需要使用.就在下面说的DECLARE_SERIAL就是一个经典的例子.
动态创建主要用在 "我不知道要创建的对象就是是什么类,但是我知道它肯定是从某个基类派生的".
DECLARE_SERIAL是指序列化特性,它是一个完全自动化的 存储机制,它可以将一个 对象数组(可能含有A,B,C类的对象)存储进去,而且能够根据存储的情况准确的载入进来,这看起来很简单, 但是,有一个问题我们必须考虑, 就是怎么写这个程序,使得载入的时候能够正确创建相应的A,B,C类的对象呢(注意,这里是三个不同的类).而且MFC的设计人员当初编写这个机制的时候根本不知道到底会出现什么类,也许还会出现D类. 怎么办呢?
可以肯定, 存储机制中必须要有能够判断类种类的代码.所以,序列化机制DECLARE_SERIAL包含了DECLARE_DYNAMIC,这样在存储进入文件的时候,可以将类名称存储到文件中.
OK,我们载入的时候可以知道我们要载入什么类了,但是,我们又要怎么去创建它呢? 所以DECLARE_SERIAL也包含了DECLARE_DYNCREATE,它用于创建对象.
那么,DECLARE_SERIAL到底有什么特殊的地方呢?首先,它必须实现operator>>(具体原因可以看看深入浅出,还有 版本控制,这样,我们在处理序列化时,可以很灵活.
首先记住一点,DECLARE_SERIAL最主要的用途是一种智能存储.所以我们可以不用这个智能特性.
当我们没有DECLARE_SERIAL,而有void CMessg::Serialize(CArchive& ar)时,我们只能这样进行存储
CDocument::Serialize(ar)
{
if (ar.isstoring())
{
//存储一个对象
pMessg->Serialize(ar);
}
else
{
//必须非常明确的指出New一个 CMessg对象;
pMessg = new CMessg;
pMessg->Serialize(ar);
}
}
在上面这个例子中,根本没有利用MFC为我们设计的序列化只能机制.
再看下面一个例子
CDocument::Serialize(ar)
{
if (ar.isstoring())
{
//存储一个对象
ar << pMessg;
}
else
{
//必须非常明确的指出New一个 CMessg对象;
ASSERT(pMessg == NULL);
ar >> pMessg;
}
}

步骤

编辑
很神奇吧, ar是怎么根据文件(强调一下,是根据文件,而不是硬编码)判断需要创建什么类的.
它大概有这么几个步骤:
1. 因为DECLARE_SERIAL重载了>>操作符,所以可以保证是调用CMessg类的>>函数.
2. >>函数实际上调用的是ar的ReadObject(CRuntimeClass*)函数
3. ReadObject首先从文件中读取类判断信息(可能是一个字符串,可能是一个类索引),得到Class对应的ClassName;
4. 程序的模块状态中有所有的RuntimeClass的列表,因此,查找对应的程序支持的RuntimeClass(对比ClassName),获得对应的RuntimeClass;
5. RuntimeClass中含有创建对象的方法CreateObject,调用它,创建对应的对象.这里,因为CreateObject实际就是 New 一个对象,类似 new CMessg; 所以,为了支持序列化,必须有没有参数的 构造函数.
6. 创建对象之后,调用Seralize(ar),读入真正的对象的信息.
7. 将对象的 指针返回.
8. pMessg就指向一个对应的对象了.
MFC 六大关键技术之仿真
DECLARE_SERIAL / IMPLEMENT_SERIAL 宏
 
要将<< 和>> 两个运算子多载化,还要让Serialize 函数神不知鬼不觉地放入类别声明
之中,最好的作法仍然是使用宏。
类别之能够进行文件读写动作,前提是拥有动态生成的能力,所以,MFC 设计了两个宏
DECLARE_SERIAL 和IMPLEMENT_SERIAL:
#define DECLARE_SERIAL(class_name) \
DECLARE_DYNCREATE(class_name) \
friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb);
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject) \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \
为了在每一个对象被处理(读或写)之前,能够处理琐屑的工作,诸如判断是否第一次
出现、记录版本号码、记录文件名等工作,CRuntimeClass 需要两个函数Load 和Store
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
CRuntimeClass* m_pBaseClass;
CObject* CreateObject();
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// CRuntimeClass objects linked together in simple list
static CRuntimeClass* pFirstClass; // start of class list
CRuntimeClass* m_pNextClass; // linked list of registered classes
};
你已经在上一节看过Load 函数,当时为了简化,我把它的参数拿掉,改为由屏幕上获
得类别名称,事实上它应该是从文件中读一个类别名称。至于Store 函数,是把类别名
称写入文件中:
// Runtime class serialization code
CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum)
{
WORD nLen;
char szClassName[64];
CRuntimeClass* pClass;
ar >> (WORD&)(*pwSchemaNum) >> nLen;
if (nLen >= sizeof(szClassName) || ar.Read(szClassName, nLen) != nLen)
return NULL;
szClassName[nLen] = ~\0~;
for (pClass = pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (lstrcmp(szClassName, pClass->m_lpszClassName) == 0)
return pClass;
}
return NULL; // not found
}
void CRuntimeClass::Store(CArchive& ar) const
// stores a runtime class description
{
WORD nLen = (WORD)lstrlenA(m_lpszClassName);
ar << (WORD)m_wSchema << nLen;
ar.Write(m_lpszClassName, nLen*sizeof(char));
}
class CScribDoc : public CDocument
{
DECLARE_DYNCREATE(CScribDoc)
...
};
class CStroke : public CObject
{
DECLARE_SERIAL(CStroke)
public:
void Serialize(CArchive&);
...
};
class CRectangle : public CObject
{
DECLARE_SERIAL(CRectangle)
public:
void Serialize(CArchive&);
...
};
class CCircle : public CObject
{
DECLARE_SERIAL(CCircle)
public:
void Serialize(CArchive&);
...
};
以及在.CPP 档中做这样的动作:
IMPLEMENT_DYNCREATE(CScribDoc, CDocument)
IMPLEMENT_SERIAL(CStroke, CObject, 2)
IMPLEMENT_SERIAL(CRectangle, CObject, 1)
IMPLEMENT_SERIAL(CCircle, CObject, 1)
然后呢?分头设计CStroke、CRectangle 和CCircle 的Serialize 函数吧。
当然,毫不令人意外地,MFC 源代码中的CObList 和CDWordArray 有这样的内容:
// in header files
class CDWordArray : public CObject
{
DECLARE_SERIAL(CDWordArray)
public:
void Serialize(CArchive&);
...
};
class CObList : public CObject
{
DECLARE_SERIAL(CObList)
public:
void Serialize(CArchive&);
...
};
// in implementation files
IMPLEMENT_SERIAL(CObList, CObject, 0)
IMPLEMENT_SERIAL(CDWordArray, CObject, 0)
而CObject 也多了一个虚拟函数Serialize:
class CObject
{
public:
virtual void Serialize(CArchive& ar);
...
}

IMPLEMENT_SERIAL

IMPLEMENT_SERIAL
IMPLEMENT_SERIAL( class_name, base_class_name, wSchema )
参数:
class_name 类的实际名字(不用引号括起来)。
base_class_name 基类的名字(不用引号括起来)。
wSchema 一个UINT类型的版本号,将被用在存档中,使得解串行程序能够识别并处理早期版本的程序所生成的数据。它的值不能是-1。
说明:
这个宏为动态的CObject派生类对象生成必要的C++代码,使它能够在运行时访问类名及其在继承关系中的位置。在.CPP模块中使用IMPLEMENT_SERIAL宏,然后一次性地连接生成的 目标代码。
你可以使用AFX_API来为使用了 DECLARE_SERIAL和IMPLEMENT_SERIAL宏的类自动引出 CArchive提取操作符。用下面的代码把类声明(在.H文件中)括起来:
#undef AFX_API
#define AFX_API AFX_EXT_CLASS
<这里是你的类声明>
#undef AFX_API
#define AFX_API
有关的更多信息参见“Visual C++程序员指南”中的“CObject类”主题。

DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC宏

 IMPLEMENT_DYNAMIC是实现“运行时类型识别”宏,与之相对应的是DECLARE_DYNAMIC(声明“运行时类型识别”宏)。也就是说你在.CPP文件中如果看见有IMPLEMENT_DYNAMIC,则在.H文件中必定有DECLARE_DYNAMIC的声明。 
DECLARE_DYNAMIC/DEClARE_DYNAMIC是为了确定运行时对象属于哪一个类而定义的宏。 
DEClARE_DYNCREATE/IMPLEMENT_DYNCREATE是为了“动态创建"类的实例而定义的宏。new可以用来创建对象,但不是动态的。比如说,你要在程序中实现根据拥护输入的类名来创建类的实例,下面的做法是通不过的: 
char szClassName[60]; 
cin >> szClassName; 
CObject* pOb=new szClassName; //通不过 
这里就要用到DEClARE_DYNCREATE/IMPLEMENT_DYNCREATE定义的功能了。


DECLARE_DYNCREATE与DECLARE_DYNAMIC区别

DECLARE_DYNAMIC 表示可以运行时识别该类

DECLARE_DYNCREATE 包含了DECLARE_DYNAMIC的功能,并且可以在运行过程中动态创建对象。如果需要动态创建类对象,需要使用这个宏定义。

 

DECLARE_DYNCREAT定义如下:

#define DECLARE_DYNCREATE(class_name) \

    DECLARE_DYNAMIC(class_name)\

    static CObjectPASCALCreateObject();(这一句就是DECLARE_DYNCREATE多出来的一句)

 

 

这里是DECLARE_DYNAMIC声明的(本质上就是声明了一个CruntimClass,并且提供了一个可以获取CruntimeClass的函数)

DECLARE_DYNAMIC(class_name)

public:

    staticconst CRuntimeClassclass##class_name;

    virtual CRuntimeClassGetRuntimeClass()const;

 

RUNTIME_CLASS的本质,就是获取该类的类型为CruntTimeClass的成员变量

((CRuntimeClass*)(&class_name::class##class_name))


你可能感兴趣的:(Visual C++中的几个宏)