Download source files - 9 Kb
Introduction
Some months ago, I had to create objects which constitute the data structure of
a MFC MDI document. This structure is fairly complex, meaning the objects in the
structure are referenced more than one time within the structure. The objects are seen from
the plug-ins of the application as COM objects; plug-ins manipulate COM objects through the COM
interface of the document only. The objects also have to be serialized into the document file.
COM implementation
The retained solution was to implement them with the MFC (with a derivation from
CCmdTarget
and then using the macros supplied with the MFC) as the framework was done
with MFC and this mechanism is well integrated. See the MSDN for more information on how
to provide COM implementation with MFC.
Serialization
The serialization of the object does not use
IPersistStream
as you might expect because
of the multiple references inside the data structure; this would lead to the development
of a serialization algorithm which is not desirable.
In fact, I simply use the serialization provided with the MFC. Why? well, the structure of
the data involves multiple references of objects within the data structure and the MFC
provides a neat way to serialize such structure - and more, since the objects are implemented
with MFC then the serialization is provided for free (as the object inherits from CObject
).
The problem
Well, we have COM interfaces, we have a robust serialization, so where is the problem?
In fact, the data structure is accessed using COM rules and so relies on a correct reference
counting mainly for the life time management of the objects. The problem is that the MFC
serialization does not reference count CCmdTarget
derived objects when loading (CCmdTarget
initialized the ref count to 1 which is not correct in case of multiple references).
So your code will break if the object is referenced more than one time (this will appear
when Release()'ing COM objects) !
A solution
Obvously, we have to modify the loading algorithm of
CArchive
, but how?
The first idea is to create a CArchiveEx
classes which inherits from CArchive
and then
overloading the necessary methods but as there is no virtual method, it is not
possible and anyway, it would lead to important modification to the MFC framework (you will
have to modify CDocument::OnOpenDocument()
in order to use the new class).
So, are we stuck? no!
In fact, if you look at the DECLARE_SERIAL
and IMPLEMENT_SERIAL
macros,
definition you will notice that these macros define the operator >>
in order to load
an object from a CArchive
stream. That's it ! Just modify the operator >> and do the correct reference
counting.
So here are macros replacement for IMPLEMENT_SERIAL and DECLARE_SERIAL (the bold lines
indicates the difference from the original one) :
#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; }
The new
operator >>
takes care of doing the correct reference counting when the object is
loaded more than one time. Notice that there is a boolean flag (
_m_bAlreadyLoaded
)
which tells that we should not increment the ref count the first time. You might say that I could
have set the ref count to zero (0) just after allocating a new object and just
ExternalAddRef()
inside
the
operator >>
but it would change the allocation policy for
CreateObject()
(it
creates by default an object with a ref count of one) so I think this way is better for
compatibility.
The overhead of these macros is that it adds the BOOL variable _m_bAlreadyLoaded
to
your class.
How to use this code
Just replace the macros
DECLARE_SERIAL
and
IMPLEMENT_SERIAL
respectively with
DECLARE_SERIAL_COM
and
IMPLEMENT_SERIAL_COM
and it should work (your class must inherit
from
CCmdTarget
at least !).
The provided macros are fully compatible both with CArchive
serialization and in terms of parameters.
The macros are also compatible with the creation policy of CreateObject()
of CCmdTarget
(I
mean the ref count is set to one (1) as expected).
About the code
The sources given only test the correctness of the solution with a small data set provided by
a dummy class which implements a COM interface.
The test just creates some date in memory and do some serialization on it; it also dumps the objects
of the data structure to show that the reference count is correct.
Look at the source comments for more details on the way the test is done.