在探讨MFCMDI文档的数据结构时,首先需要理解其复杂性。在这种结构中,对象可能在结构内部被多次引用。这意味着,如果使用标准的序列化方法,可能会导致对象被多次创建,从而破坏了原有的引用关系。为了解决这个问题,需要采用一种特殊的序列化机制,以确保对象的引用关系得以正确维护。
在MFC框架中,通常使用CCmdTarget类作为基类,并通过MFC提供的宏来实现COM接口。这种实现方式与MFC框架高度集成,因此是推荐的做法。具体实现细节可以参考MSDN文档。
在序列化对象时,不能简单地使用IPersistStream接口,因为这会导致复杂的序列化算法,这并不是想要的。相反,可以使用MFC提供的序列化机制,因为它能够很好地处理对象的多重引用问题。
尽管有了COM接口和健壮的序列化机制,但问题依然存在。由于数据结构是通过COM规则访问的,因此对象的生命周期管理主要依赖于正确的引用计数。然而,MFC的序列化机制在加载时并不对CCmdTarget派生对象进行引用计数。这意味着,如果对象被多次引用,代码可能会在Release()调用时崩溃。
为了解决这个问题,需要修改CArchive的加载算法。一种方法是创建一个CArchiveEx类,继承自CArchive并重载必要的方法。但是,由于CArchive没有虚方法,这种方法并不可行,而且会导致MFC框架的重大修改。
另一种方法是修改DECLARE_SERIAL和IMPLEMENT_SERIAL宏,以实现正确的引用计数。以下是修改后的宏定义:
#define DECLARE_SERIAL_COM(class_name) \
_DECLARE_DYNCREATE(class_name) \
_AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb); \
BOOL _m_bAlreadyLoaded;
#define IMPLEMENT_SERIAL_COM(class_name, base_class_name, wSchema) \
CObject* PASCAL class_name::CreateObject() \
{ \
class_name *pOb = new class_name; \
pOb->_m_bAlreadyLoaded = FALSE; \
return pOb; \
} \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, class_name::CreateObject) \
_AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ \
pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
if (pOb != NULL) \
{ \
if (pOb->_m_bAlreadyLoaded) \
pOb->ExternalAddRef(); \
else \
pOb->_m_bAlreadyLoaded = TRUE; \
} \
return ar; \
}
新的operator>>宏确保在对象被多次加载时进行正确的引用计数。注意,这里有一个布尔标志_m_bAlreadyLoaded,它告诉在第一次时不应该增加引用计数。
要使用这段代码,只需将DECLARE_SERIAL和IMPLEMENT_SERIAL宏分别替换为DECLARE_SERIAL_COM和IMPLEMENT_SERIAL_COM即可。类至少需要继承自CCmdTarget。这些宏与CArchive序列化和参数完全兼容,并且与CCmdTarget的CreateObject()创建策略兼容(即引用计数默认设置为1)。