The Persistence Implementations


Let's look at how the persistence implementations work, using the property bag persistence implementation as the example. All persistence implementations are similar.

The Property Map

The property map macros basically add a static member function called GetPropertyMap to your class. GetPropertyMap returns a pointer to an array of ATL_PROPMAP_ENTRY structures. The structure looks like this:

struct ATL_PROPMAP_ENTRY {           LPCOLESTR szDesc;                DISPID dispid;                   const CLSID* pclsidPropPage;     const IID* piidDispatch;         DWORD dwOffsetData;              DWORD dwSizeData;                VARTYPE vt;                  };                               


For example, here's a property map and the resulting macro expansion:

BEGIN_PROP_MAP(CDemagogue)     PROP_ENTRY("Speech", DISPID_SPEECH, CLSID_NULL)     PROP_ENTRY_EX("Name", DISPID_NAME, CLSID_NULL,       IID_INamedObject)     PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4) END_PROP_MAP() 


This property map expands to this:

__if_not_exists(__ATL_PROP_NOTIFY_EVENT_CLASS) {     typedef ATL::_ATL_PROP_NOTIFY_EVENT_CLASS         __ATL_PROP_NOTIFY_EVENT_CLASS; } static ATL::ATL_PROPMAP_ENTRY* GetPropertyMap() {   static ATL::ATL_PROPMAP_ENTRY pPropMap[] = {   {OLESTR(("Speech"), DISPID_SPEECH, &CLSID_NULL,       __uudiof(IID_IDispatch), 0, 0, 0},   {OLESTR("Name"), DISPID_ NAME, &CLSID_NULL,       &IID_INamedObject, 0, 0, 0},   {OLESTR("_cx"), 0, &CLSID_NULL, NULL,       offsetof(_PropMapClass, m_sizeExtent.cx),       sizeof(((_PropMapClass*)0)-> m_sizeExtent.cx), VT_UI4},   {NULL, 0, NULL, &IID_NULL, 0, 0, 0}   };   return pPropMap; } 


The szDesc field of the structure holds the name of the property. It's used only by the property bag persistence implementation.

The dispid field contains the property's dispatch identifier. All the persistence implementations need this so they can access the property via one of the object's IDispatch implementations by calling the Invoke method.

The pclsidPropPage field contains a pointer to the CLSID for the property page associated with the object. It's not used during persistence.

The piidDispatch field contains a pointer to the IID of the dispatch interface that supports this property. The specified dispid is unique to this interface.

Only PROP_DATA_ENTRY macros use the last three fields. The dwOffsetData field contains the offset of the specified member variable from the beginning of a class instance. The dwSizeData field contains the size of the variable in bytes, and the vt field contains the variable's VARTYPE (VARIANT type enumeration code).

The various persistence implementations basically iterate over this map and load or save the properties listed. For properties listed using PROP_ENTRY and PROP_ENTRY_EX, the implementations call IDispatch::Invoke with the specified dispid to get or put the property.

Invoke transfers each property via a VARIANT. The stream persistence implementation simply wraps the variant in a CComVARIANT instance and uses its ReadFromStream and WriteToStream methods to do all the hard work. Therefore, stream persistence supports all VARIANT types that the CComVARIANT persistence implementation supports (discussed in Chapter 3, "ATL Smart Types"). The property bag implementation has it even easier because property bags deal directly in VARIANTs.

For properties listed using the PROP_DATA_ENTRY macro, things aren't quite so simple. The IPersistStreamInit implementation directly accesses the object instance at the specified offset for the specified length. This reads or writes the specified number of bytes directly to or from the object.

However, the IPersistPropertyBag implementation must read and write properties held in a VARIANT. Therefore, this implementation copies the member variable of the object to a VARIANT before writing the property to the bag, and copies a VARIANT to the member variable after reading the property from the bag. The current implementation of IPersistPropertyBag persistence supports only a limited set of VARIANT types; worse, it silently fails to load and save properties with any VARTYPEs other than these:

  • VT_UI1, VT_I1: Read and write the variable as a BYTE.

  • VT_BOOL: Reads and writes the variable as a VARIANT_BOOL.

  • VT_UI2, VT_I2: Read and write the variable as a short.

  • VT_UI4, VT_I4, VT_INT, VT_UINT: Read and write the variable as a long.

  • VT_BSTR: Reads and writes the variable as a BSTR.

  • Any other VT_*: Silently fail.

IPersistPropertyBagImpl

The IPersistPropertyBagImpl<T> class implements the IPersistPropertyBag interface methods. IPersistPropertyBag, like all the persistence interfaces, derives from IPersist, which has one method: GetClassID.

interface IPersist : IUnknown                { HRESULT GetClassID([out] CLSID* pclsid); } 


All the persistence-implementation classes have the same implementation of GetClassID. They call a static member function named GetObjectCLSID method to retrieve the CLSID. This method must be in the deriving class (your object's class) or one of its base classes.

template <class T>                                                             class ATL_NO_VTABLE IPersistPropertyBagImpl                                        : public IPersistPropertyBag {                                             public:                                                                            ...                                                                            STDMETHOD(GetClassID)(CLSID *pClassID) {                                           ATLTRACE(atlTraceCOM, 2, _T("IPersistPropertyBagImpl::GetClassID\n"));         if (pClassID == NULL)                                                              return E_POINTER;                                                          *pClassID = T::GetObjectCLSID();                                               return S_OK;                                                               }};                                                                        


Normally, your class obtains its GetObjectCLSID static member function from CComCoClass:

template <class T, const CLSID* pclsid = &CLSID_NULL>             class CComCoClass {                                               public:                                                               ...                                                               static const CLSID& WINAPI GetObjectCLSID() {return *pclsid;} };                                                                


This implies that a class must be createable for it to use the persistence classes. This is reasonable because it doesn't do much good to save a class to some persistent medium and then be unable to create a new instance when loading the object from that medium.

The IPersistPropertyBagImpl<T> class also implements the remaining IPersistPropertyBag methods, including, for example, the Load method. IPersistPropertyBagImpl<T>::Load calls T::IPersistPropertyBag_Load to do most of the work. This allows your class to provide this method when it needs a custom implementation of Load. Normally, your object (class T) doesn't provide an IPersistPropertyBag_Load method, so this call vectors to a default implementation provided by the base class IPersistPropertyBagImpl<T>::IPersistPropertyBag_Load method. The default implementation calls the global function AtlIPersistPropertyBag_Load. This global function iterates over the property map and, for each entry in the map, loads the property from the property bag:

template <class T>                                                       class ATL_NO_VTABLE IPersistPropertyBagImpl                                  : public IPersistPropertyBag {                                       public:                                                                      ...                                                                      // IPersistPropertyBag                                                   //                                                                       STDMETHOD(Load)(LPPROPERTYBAG pPropBag,          LPERRORLOG pErrorLog) {                      ATLTRACE(atlTraceCOM, 2, _T("IPersistPropertyBagImpl::Load\n"));               T* pT = static_cast<T*>(this);                                           ATL_PROPMAP_ENTRY* pMap = T::GetPropertyMap();                                ATLASSERT(pMap != NULL);                                                      return pT->IPersistPropertyBag_Load(pPropBag,                                pErrorLog, pMap);                                                     }                                                                        HRESULT IPersistPropertyBag_Load(LPPROPERTYBAG pPropBag,                        LPERRORLOG pErrorLog, ATL_PROPMAP_ENTRY* pMap) {                         T* pT = static_cast<T*>(this);                                          HRESULT hr = AtlIPersistPropertyBag_Load(pPropBag,                               pErrorLog, pMap, pT, pT->GetUnknown());                              if (SUCCEEDED(hr))                                                              pT->m_bRequiresSave = FALSE;                                         return hr;                                                          }                                                                             ...                                                                  };                                                                      


This implementation structure provides three places where we can override methods and provide custom persistence support for a non-VARIANT-compatible property. We can override the Load method itself, in effect directly implementing the IPersistPropertyBag method. Alternatively, we can let ATL implement Load while our object implements IPersistPropertyBag_Load. Finally, we can let ATL implement Load and IPersistPropertyBag_Load while we provide a replacement global function called AtlIPersistPropertyBag_Load and play some linker tricks so that our object uses our global function instead of the ATL-provided one.

The most natural method is to implement Load. Normally, in this implementation, you call the base class Load method to read all properties described in the property map, and then read any custom, non-VARIANT-compatible properties:

HRESULT CMyObject::Load(LPPROPERTYBAG pPropBag,     LPERRORLOG pErrorLog) {     HRESULT hr = IPersistPropertyBagImpl<CMyObject>::Load(pPropBag,pErrorLog);     if (FAILED (hr)) return hr;     // Read an array of VT_I4     // This requires us to create a "name" for each array element     // Read each element as a VARIANT, then re-create the array     ... } 


This approach has a few disadvantages. It's a minor point, but the object now requires four methods for its persistence implementation: its Load, the base class Load, the base class IPersistPropertyBag_Load, and the AtlIPersistPropertyBag_Load. We could copy the base class Load implementation into the object's Load method, but that makes the object more fragile because future versions of ATL might change its persistence-implementation technique.

Another slight disadvantage to this approach is that it is clear from the ATL implementation that the ATL designers intended for your object to override IPersistPropertyBag_Load. Note the following code fragment from the default implementation of Load:

STDMETHOD(Load)(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog) {     ...                                                               T* pT = static_cast<T*>(this);                                    ...                                                               return pT->IPersistPropertyBag_Load(pPropBag, pErrorLog, pMap); }                                                                 


Instead of directly calling IPersistPropertyBag_Load, which is present in the same class as the Load method, the code calls the method using a pointer to the deriving classyour object's class. This provides the same functionality as making the method virtual, without the overhead of a virtual function.

Generally, the best solution is to let the object provide its own implementation of the IPersistPropertyBag_Load method. In this implementation, the object can call the global function AtlIPersistProperyBag_Load and save any non-VARIANT-compatible properties it possessed. Here's an example from the BullsEye control described in Chapter 11, "ActiveX Controls." It contains a property that is an array of long integers. This can't be described as a VARIANT-compatible type because it's not a SAFEARRAY, so I can't list the property in the property map.

HRESULT CBullsEye::IPersistPropertyBag_Load(     LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog,     ATL_PROPMAP_ENTRY* pMap) {     if (NULL == pPropBag) return E_POINTER;     // Load the properties described in the PROP_MAP     HRESULT hr = AtlIPersistPropertyBag_Load(pPropBag, pErrorLog,        pMap, this, GetUnknown());     if (SUCCEEDED(hr)) m_bRequiresSave = FALSE;     if (FAILED (hr)) return hr;     // Load the indexed property - RingValues     // Get the number of rings     short sRingCount;     get_RingCount (&sRingCount);     // For each ring, read its value     for (short nIndex = 1; nIndex <= sRingCount; nIndex++) {         // Create the base property name         CComBSTR bstrName = OLESTR("RingValue");         // Create ring number as a string         CComVariant vRingNumber = nIndex;         hr = vRingNumber.ChangeType (VT_BSTR);         ATLASSERT (SUCCEEDED (hr));         // Concatenate the two strings to form property name         bstrName += vRingNumber.bstrVal;         // Read ring value from the property bag         CComVariant vValue = 0L;         hr = pPropBag->Read(bstrName, &vValue, pErrorLog);         ATLASSERT (SUCCEEDED (hr));         ATLASSERT (VT_I4 == vValue.vt);         if (FAILED (hr)) {             hr = E_UNEXPECTED;             break;         }         // Set the ring value         put_RingValue (nIndex, vValue.lVal);      }      if (SUCCEEDED(hr)) m_bRequiresSave = FALSE;      return hr;  } 


The Save method works symmetrically. The IPersistPropertyBagImpl<T> class implements the Save method. IPersistPropertyBagImpl<T>::Save calls T::IPersistPropertyBag_Save to do the work. Again, your object (class T) doesn't normally provide an IPersistPropertyBag_Save method, so this call vectors to a default implementation that the base class IPersistPropertyBagImpl<T>::IPersistPropertyBag_Save method provides. The default implementation calls the global function AtlIPersistPropertyBag_Save. This global function iterates over the property map and, for each entry in the map, saves the property to the property bag.

template <class T>                                                       class ATL_NO_VTABLE IPersistPropertyBagImpl                                  : public IPersistPropertyBag {                                       public:                                                                      ...                                                                      // IPersistPropertyBag                                                   //                                                                       STDMETHOD(Save)(LPPROPERTYBAG pPropBag, BOOL fClearDirty,                    BOOL fSaveAllProperties) {                                               ATLTRACE(atlTraceCOM, 2, _T("IPersistPropertyBagImpl::Save\n"));         T* pT = static_cast<T*>(this);                                           ATL_PROPMAP_ENTRY* pMap = T::GetPropertyMap();                           ATLASSERT(pMap != NULL);                                                 return pT->IPersistPropertyBag_Save(pPropBag,                                fClearDirty, fSaveAllProperties, pMap);                          }                                                                        HRESULT IPersistPropertyBag_Save(LPPROPERTYBAG pPropBag,                     BOOL fClearDirty, BOOL fSaveAllProperties,                               ATL_PROPMAP_ENTRY* pMap) {                                               T* pT = static_cast<T*>(this);                                           HRESULT hr;                                                              hr = AtlIPersistPropertyBag_Save(pPropBag, fClearDirty,                      fSaveAllProperties, pMap, pT, pT->GetUnknown());                     if (fClearDirty && SUCCEEDED(hr)) {                                          pT->m_bRequiresSave=FALSE;                                           }                                                                        return hr;                                                           }                                                                    };                                                                       


Finally, IPersistPropertyBagImpl implements the InitNew method this way:

STDMETHOD(InitNew)() {                                 ATLTRACE(atlTraceCOM, 2,                               _T("IPersistPropertyBagImpl::InitNew\n"));     T* pT = static_cast<T*>(this);                     pT->m_bRequiresSave = TRUE;                        return S_OK;                                   }                                                  


Therefore, you need to override InitNew directly when you have any initialization to perform when there are no properties to load.

IPersistStreamInitImpl

The implementation contained in IPersistStreamInitImpl is quite similar to the one just described. The Load and Save methods call the IPersistStreamInit_Load and IPersistStreamInit_Save methods, which are potentially provided by the deriving object but typically provided by the default implementation in IPersistStreamInitImpl. These implementations call the global helper functions AtlIPersistStreamInit_Load and AtlIPersistStreamInit_Save.

template <class T>                                        class ATL_NO_VTABLE IPersistStreamInitImpl                  : public IPersistStreamInit {                           public:                                                     ...                                                       // IPersistStream                                         STDMETHOD(Load)(LPSTREAM pStm) {                            ATLTRACE(atlTraceCOM, 2,                                    _T("IPersistStreamInitImpl::Load\n"));                  T* pT = static_cast<T*>(this);                            return pT->IPersistStreamInit_Load(pStm,                    T::GetPropertyMap());                                 }                                                         HRESULT IPersistStreamInit_Load(LPSTREAM pStm,              ATL_PROPMAP_ENTRY* pMap) {                                T* pT = static_cast<T*>(this);                            HRESULT hr =                                                 AtlIPersistStreamInit_Load(pStm, pMap, pT,                  pT->GetUnknown());                                   if (SUCCEEDED(hr)) pT->m_bRequiresSave = FALSE;           return hr;                                              }                                                         STDMETHOD(Save)(LPSTREAM pStm, BOOL fClearDirty) {          T* pT = static_cast<T*>(this);                            ATLTRACE(atlTraceCOM, 2,                                    _T("IPersistStreamInitImpl::Save\n"));                  return pT->IPersistStreamInit_Save(pStm, fClearDirty,       T::GetPropertyMap());                                 }                                                         HRESULT IPersistStreamInit_Save(LPSTREAM pStm,              BOOL fClearDirty, ATL_PROPMAP_ENTRY* pMap) {              T* pT = static_cast<T*>(this);                            return AtlIPersistStreamInit_Save(pStm, fClearDirty,        pMap, pT, pT->GetUnknown());                          }                                                       };                                                        


IPersistStreamInitImpl also implements the InitNew method this way:

STDMETHOD(InitNew)() {                              ATLTRACE(atlTraceCOM, 2,                          _T("IPersistStreamInitImpl::InitNew\n"));     T* pT = static_cast<T*>(this);                  pT->m_bRequiresSave = TRUE;                   return S_OK;                                  }                                               


Therefore, as with property bags, you need to override InitNew directly when you have any initialization to perform when there are no properties to load.

The implementation of the IsDirty method assumes the presence of a member variable named m_bRequiresSave somewhere in your class hierarchy.

STDMETHOD(IsDirty)() {                                 ATLTRACE(atlTraceCOM, 2,                             _T("IPersistStreamInitImpl::IsDirty\n"));        T* pT = static_cast<T*>(this);                     return (pT->m_bRequiresSave) ? S_OK : S_FALSE; }                                                  


The persistence implementations originally assumed that only ActiveX controls would use them, as if controls were the only objects that needed a persistence implementation. Although ATL has greatly reduced the coupling between the control classes and the persistence implementation, the CComControlBase class normally provides the m_bRequiresSave variable and the SetDirty and Getdirty helper functions usually used to access the variable.

class ATL_NO_VTABLE CComControlBase {                        public:                                                          void SetDirty(BOOL bDirty) { m_bRequiresSave = bDirty; }     // Obtain the dirty state for the control                    BOOL GetDirty() { return m_bRequiresSave; }                  ...                                                          unsigned m_bRequiresSave:1;                              };                                                           


To use the persistence-implementation classes in an object that doesn't derive from CComControlBase, you need to define the m_bRequiresSave variable in your class hierarchy somewhere. Typically, for convenience, you also define the SetDirty and Getdirty helper methods. Noncontrols can use this class to provide this persistence support:

class ATL_NO_VTABLE CSupportDirtyBit { public:   CSupportDirtyBit() : m_bRequiresSave(FALSE) {}   void SetDirty(BOOL bDirty) {     m_bRequiresSave = bDirty ? TRUE : FALSE;   }   BOOL GetDirty() { return m_bRequiresSave ? TRUE : FALSE; }   BOOL m_bRequiresSave; }; 


Finally, the IPersistStreamInitImpl class provides the following implementation of GetSizeMax:

STDMETHOD(GetSizeMax)(ULARGE_INTEGER* pcbSize) {                 HRESULT hr = S_OK;                                             T* pT = static_cast<T*>(this);                                 if (pcbSize == NULL)                                             return E_POINTER;                                            ATL_PROPMAP_ENTRY* pMap = T::GetPropertyMap();                 ATLENSURE(pMap != NULL);                                       // Start the size with the size of the ATL version             // we write out.                                               ULARGE_INTEGER nSize;                                          nSize.HighPart = 0;                                            nSize.LowPart = sizeof(DWORD);                                 CComPtr<IDispatch> pDispatch;                                  const IID* piidOld = NULL;                                     for (int i = 0; pMap[i].pclsidPropPage != NULL; i++) {           if (pMap[i].szDesc == NULL)                                      continue;                                                    // check if raw data entry                                     if (pMap[i].dwSizeData != 0) {                                   ULONG ulSize=0;                                                //Calculate stream size for BSTRs special case                 if (pMap[i].vt == VT_BSTR) {                                     void* pData = (void*)(pMap[i].dwOffsetData +                     (DWORD_PTR)pT);                                              ATLENSURE(                                                       pData >= (void*)(DWORD_PTR)pMap[i].dwOffsetData                && pData >= (void*)(DWORD_PTR)pT );                          BSTR bstr=*reinterpret_cast<BSTR*>(pData);                     ulSize=CComBSTR::GetStreamSize(bstr);                        } else {                                                         ulSize = pMap[i].dwSizeData;                                 }                                                              nSize.QuadPart += ulSize;                                      continue;                                                    }                                                              CComVariant var;                                               if (pMap[i].piidDispatch != piidOld) {                           pDispatch.Release();                                           if (FAILED(pT->GetUnknown()->                                    QueryInterface(*pMap[i].piidDispatch,                          (void**)&pDispatch))) {                                        ATLTRACE(atlTraceCOM, 0,                                         _T("Failed to get a dispatch pointer for "                        "property #%i\n"), i);                                    hr = E_FAIL;                                                   break;                                                       }                                                              piidOld = pMap[i].piidDispatch;                              }                                                              if (FAILED(pDispatch.GetProperty(pMap[i].dispid, &var))) {       ATLTRACE(atlTraceCOM, 0,                                         _T("Invoked failed on DISPID %x\n"),                           pMap[i].dispid);                                             hr = E_FAIL;                                                   break;                                                       }                                                              nSize.QuadPart += var.GetSize();                             }                                                              *pcbSize = nSize;                                              return hr;                                                   }                                                              


Previous versions of ATL simply returned E_NOTIMPL from the GetSizeMax function. As of this writing, the MSDN documentation claims that ATL still does not implement this function. In any event, the implementation that ATL 8 actually provides is fairly straightforward. GetSizeMax loops through all the entries in the property map and accumulates the total size each entry requires in the nSize variable. If it finds a "raw" PROP_DATA_ENTRY, it simply increments the total nSize by the size of the member specified in the PROP_DATA_ENTRY macro. Alternatively, if it finds a PROP_ENTRY or PROP_ENTRY_EX in the map, it queries the object for the specified IDispatch interface and wraps the resulting interface pointer in a CComPtr. Recall from Chapter 3, "ATL Smart Types," that the CComPtr smart pointer template class provides a convenient specialization for IDispatch that exposes property "getters" and "setters." GetSizeMax uses CComPtr<IDispatch>::GetProperty to retrieve a VARIANT value for the property specified in the property map entry. The function wraps the returned VARIANT in a CComVariant and uses that class's GetSize function to increment the nSize total for the object.

IPersistStorageImpl

The ATL implementation of IPersistStorage is very simplistic. The Save method creates a stream called "Contents" within the provided storage and depends on an IPersistStreamInit implementation to write the contents of the stream.

template <class T>                                                  class ATL_NO_VTABLE IPersistStorageImpl                                 : public IPersistStorage {                                      public:                                                                 STDMETHOD(Save)(IStorage* pStorage, BOOL fSameAsLoad) {                 ATLTRACE(atlTraceCOM, 2,                                              _T("IPersistStorageImpl::Save\n"));                               CComPtr<IPersistStreamInit> p;                                      p.p = IPSI_GetIPersistStreamInit();                                 HRESULT hr = E_FAIL;                                                if (p != NULL) {                                                        CComPtr<IStream> spStream;                                          static LPCOLESTR vszContents = OLESTR("Contents");                  hr = pStorage->CreateStream(vszContents,                              STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,                0, 0, &spStream);                                                 if (SUCCEEDED(hr)) hr = p->Save(spStream, fSameAsLoad);         }                                                                   return hr;                                                      }                                                                   ...                                                             };                                                                  


Similarly, the Load method opens the "Contents" stream and uses the IPersistStreamInit implementation to read the contents of the stream.

STDMETHOD(Load)(IStorage* pStorage) {                                ATLTRACE(atlTraceCOM, 2, _T("IPersistStorageImpl::Load\n"));     CComPtr<IPersistStreamInit> p;                                   p.p = IPSI_GetIPersistStreamInit();                              HRESULT hr = E_FAIL;                                             if (p != NULL) {                                                     CComPtr<IStream> spStream;                                       hr = pStorage->OpenStream(OLESTR("Contents"), NULL,                STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, &spStream);         if (SUCCEEDED(hr)) hr = p->Load(spStream);                     }                                                                return hr;                                                     }                                                                


The InitNew and IsDirty implementations retrieve the object's IPersistStreamInit interface pointer (using a helper function to get the interface) and delegate to the same named method in that interface:

STDMETHOD(IsDirty)(void) {                          ATLTRACE(atlTraceCOM, 2,                          _T("IPersistStorageImpl::IsDirty\n"));        CComPtr<IPersistStreamInit> p;                  p.p = IPSI_GetIPersistStreamInit();             return (p != NULL) ? p->IsDirty() : E_FAIL; }                                               STDMETHOD(InitNew)(IStorage*) {                     ATLTRACE(atlTraceCOM, 2,                          _T("IPersistStorageImpl::InitNew\n"));        CComPtr<IPersistStreamInit> p;                  p.p = IPSI_GetIPersistStreamInit();             return (p != NULL) ? p->InitNew() : E_FAIL; }                                               


One of the main reasons an object supports IPersistStorage is so the object can incrementally read and write its state. Unfortunately, the ATL implementation doesn't support this. The implementation does not cache the provided IStorage interface provided during the Load and Save calls, so it's not available for later incremental reads and writes. Not caching the IStorage interface makes implementing the last two methods trivial, however:

STDMETHOD(SaveCompleted)(IStorage* /* pStorage */) {                        ATLTRACE(atlTraceCOM, 2,                                                    _T("IPersistStorageImpl::SaveCompleted\n"));                            return S_OK;                                                            }                                                                         STDMETHOD(HandsOffStorage)(void) {                                          ATLTRACE(atlTraceCOM, 2, _T("IPersistStorageImpl::HandsOffStorage\n"));   return S_OK;                                                            }                                                                         


Generally, most objects that need the functionality IPersistStorage provides can't use the implementation ATL provides. They must derive directly from IPersistStorage and implement all the methods explicitly.




ATL Internals. Working with ATL 8
ATL Internals: Working with ATL 8 (2nd Edition)
ISBN: 0321159624
EAN: 2147483647
Year: 2004
Pages: 172

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net