Writing and Reading COM objects using CArchive

Imagine you have to work with your custom COM objects in a MFC application.
These COM objects are stored in a container (client), which is a GUI intensive
MFC application with quite sophisticated class hierarchy.
In many C++ classes in the client, I have a situation where a classic C++ class
possesses one or more COM objects. Additionally, these classes implement
standard Serialize() method to store their contents to the CArchive stream.
Below is a pseudo code of typical client C++ class:


class Dummy
{
void Serialize(CArchive& ar);

private:
double m_Double;
IPersistStreamInit* m_ipCom1; // raw interface pointer
CComPtr&ltIPersistStreamInit&gt 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.

Both options are presented in sample call bellow. In most cases you will
be interested in the (a) case only.


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.

More by Author

Must Read