Writing and Reading COM objects using CArchive
class Dummy
{
void Serialize(CArchive& ar);
private:
double m_Double;
IPersistStreamInit* m_ipCom1; // raw interface pointer
CComPtr<IPersistStreamInit> m_spCom2;// smart interface pointer defined in AtlBase.h
IPersistStreamInitPtr m_spCom3; // smart interface pointer based on _com_ptr_t
// created by #import directive
};
The question here is: Is there a way to serialize COM objects within the
standard Serialize() method? To get the answer, I turned a few
ATL/MFC books upside-down, do some Internet browsing and ... got zero hits.
This forced me to find my own solution. There is a chance that a
solution (better than presented below) exists, but I simply could not find it.
In order to store a COM class on the CArchive (using the solution below), the COM class MUST implement the IPersistStreamInit interface. This interface knows how to read/write via IStream interface, which is passed as an argument of the IPersistStreamInit::Save/Load methods.
If you refer to the documentation about IStream you will find the CreateStreamOnHGlobal() API function call that creates an IStream object on the top of HGLOBAL memory block. I can take advantage of this and store the initialized memory block into the CArchive. Below is a pseudo code that does just that:
SaveComToArchive(CArchive& ar, IPersistStreamInit* pObject)
{
// Note: This is a pseudo code - it will not compile!
// Create IStream
IStream* pStream = CreateStreamOnHGlobal();
// Write COM object's class UUID!
pStream->WriteObjectClassUUID(pObject->GetClassUUID());
// save the object
pObject->Save(pStream);
// Get the memory block maintained by the IStream
HGLOBAL hMem;
hMem = pStream->GetGlobalMemory();
// Copy the block on the CArchive
ar.Write(hMem,size_of_hMem_block);
// Release the IStream
pStream->Release;
}
The pseudo code for loading a COM object from the CArchive is very similar.
It turns out that a code like this works just fine, and I believe,
It should work for all COM objects that
properly implement IPersistStreamInit interface.
Since global memory is used as a buffer, it is obvious that we will
use this solution for COM that stores data of small and medium size.
Below you will find a link to the implemented code. There you will find two functions and two operators, that have the following prototype:
HRESULT WriteComObjectToArchive(CArchive& ar, IPersistStreamInit* ipObject); HRESULT ReadComObjectFromArchive(CArchive& ar, IPersistStreamInit** ippObject); CArchive& operator << (CArchive& ar, IPersistStreamInit* ipObject); CArchive& operator >> (CArchive& ar, IPersistStreamInit** ippObject);Note: I deliberatelly put IPersistStreamInit* in each call rather than IUnknown*. Namely, I wanted to emphasize that COM object must implement the IPersistStreamInit interface.
Let me show how to use these functions. If you look at the implementation of the ReadComObjectFromArchive() (please, see the source code), you will see, that there are two options concerned with ippObject argument:
- (a) If *ippObject==NULL, create a new COM object and load data for it,
- (b) If *ippObject!=NULL load the data into existing COM object.
void Dummy::Serialize(CArchive& ar)
{
HRESULT hr;
if(ar.IsStoring()) {
ar << m_Double;
hr = WriteComObjectToArchive(ar,m_ipCom1); // raw pointer
// if(FAILED(hr)) ... check hr here to catch errors
hr = WriteComObjectToArchive(ar,m_spCom2); // CComPtr<> pointer
hr = WriteComObjectToArchive(ar,m_ipCom3); // _com_ptr_t<> pointer
}
// loading
else {
ar >> m_Double;
// case a) - create new COM objects during the read
m_ipCom1 = NULL; // it must be NULL in order to create a new object!
hr = ReadComObjectFromArchive(ar,&m_ipCom1);
// if the call is successful, the m_ipCom1 points to a new COM object,
// with reference count set to 1!
// The &operator of CComPtr<> will reset the pointer to NULL for us and
// release any previous object.
hr = ReadComObjectFromArchive(ar,&m_spCom2);
// The &operator of _com_ptr_t<> will reset the pointer to NULL for us and
// release any previous object.
hr = ReadComObjectFromArchive(ar,&m_spCom3);
// if the calls were successful, the m_spCom2 and m_spCom3 point to
// a new COM objects, both with reference count set to 1!
// case b) - use existing COM objects and simply load the data!
// m_ipCom1 must point to IPersistStreamInit of a valid COM object.
hr = ReadComObjectFromArchive(ar,&m_ipCom1);
// if the call is successful, the m_ipCom1 was loaded with data from stream
// and the reference count remained the same.
IPersistStreamInit* pPSI;
// The &operator of CComPtr<> will reset the pointer to NULL for us, so
// we can't use it in the case b).
pPSI = m_spCom2; // The pointer inside the m_spCom2 must be non NULL and valid.
hr = ReadComObjectFromArchive(ar,&pPSI);
pPSI = m_spCom3; // The pointer inside the m_spCom3 must be non NULL and valid.
hr = ReadComObjectFromArchive(ar,&pPSI);
// if the calls were successful, the m_spCom2 and m_spCom3 now hold new data
}
}
Now, I show you the same function as above, but using the operators and
omitting most comments. In this version you do not get the HRESULT.
Operators will throw _com_error exception in the case of error.
void Dummy::Serialize(CArchive& ar)
{
if(ar.IsStoring()) {
ar << m_Double;
ar << m_ipCom1; // raw pointer
ar << m_spCom2; // CComPtr<> pointer
ar << m_spCom3; // _com_ptr_t<> pointer
}
// loading
else {
ar >> m_Double;
// case a) - create new COM objects during the read
m_ipCom1 = NULL; // it must be NULL in order to create a new object!
ar >> &m_ipCom1;
ar >> &m_spCom2;
ar >> &m_spCom3;
// case b) - use existing COM objects and simply load the data!
ar >> &m_ipCom1;
IPersistStreamInit* pPSI;
pPSI = m_spCom2;
ar >> &pPSI
pPSI = m_spCom3;
ar >> &pPSI
}
}
Download source - 4 KB The source code includes two short files ArchiveCom.h(.cpp). Add these files to your MFC project and add the #include "ArchiveCom.h" line in appropriate source implementation (CPP) files.
Download demo project - 29 KB The demo project creates a simple COM named Dummy as an EXE server. This COM implements IPersistentStreamInit interface and a few properties. The demo also creates a client named Test, which has a simple CTestDummy C++ class that uses the several Dummy COM objects and implements Serialize() method. In the main() of the demo a CArchive is created, the CTestDummy class object is initialized and written to the archive. A second (empty) CTestDummy object is created is then loaded from the CArchive stream.
To see the implementation of the CTestDummy class, open the Test.cpp file.

Comments
Yet another way ...
Posted by Legacy on 10/25/1999 12:00amOriginally posted by: doublej
You can take advantage of compound document for this task.
1. Derive your document from COleDocument
2. In constructor call EnableCompoundFile( TRUE )
3. In Serialize use member m_lpRootStg to create substorages and streams for any tree structure of your com objects.
Example (no error checking):
.
.
COleStreamFile file;
file.CreateStream( m_lpRootStg, _T("Name"), mode );
CComPtr<IPersistStream>* pPS;
pYourObject->QueryInterface( IID_IPersistStream, (LPVOID*)&pPS );
OleSaveToStream( pPS, file.GetStream() );
file.Close();
.
.
!!! Headache !!!:
All records made by CArchive are in compound files written to stream 'CONTENTS'. If this stream is empty then the compound file is deleted by MFC internal code (after serialize is finished), so you must write at least one byte using CArchive!!!
This method has many advantages. You can structure your data in file, load only necessary parts, leave old data in file (for undo or history), take advantage of transactions, etc.
ReplyThere's a simpler way already built into MFC
Posted by Legacy on 05/26/1999 12:00amOriginally posted by: Douglas Peterson
Reply