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.
More for Developers
Top Authors
- Voted: 13 times.
- Voted: 11 times.
- Voted: 11 times.
- Voted: 10 times.
- Voted: 8 times.
- Paul Kimmel 214 articles
- Zafir Anjum 120 articles
- Tom Archer - MSFT 83 articles
- Mark Strawmyer 76 articles
- Jeffrey Juday 65 articles