previous page
next page

Additional Persistence Implementations

Given what you've already seen, you might find it interesting to demonstrate an additional persistence implementation built using functionality we've already covered.

IPersistMemory

Let's look at implementing the IPersistMemory interface:

interface IPersistMemory : IPersist {                
    HRESULT IsDirty();                               
    HRESULT Load([in, size_is(cbSize)] LPVOID pvMem, 
        [in] ULONG cbSize);                          
    HRESULT Save([out, size_is(cbSize)] LPVOID pvMem,
        [in] BOOL fClearDirty, [in] ULONG cbSize);   
    HRESULT GetSizeMax([out] ULONG* pCbSize);        
    HRESULT InitNew();                               
};                                                   

The IPersistMemory interface allows a client to request that the object save its state to a fixed-size memory block (identified with a void*). The interface is very similar to IPersistStreamInit, except that it uses a memory block instead of an expandable IStream. The cbSize argument to the Load and Save methods indicates the amount of memory accessible through pvMem. The IsDirty, GetSizeMax, and InitNew methods are semantically identical to those in IPersistStreamInit.

Implementing the IPersistMemory interface

You've seen that ATL provides the IPersistStreamInitImpl class that saves and restores the state of an object to a stream. The COM API CreateStreamOnHGlobal returns an IStream implementation that reads and writes to a global memory block. We can use the two together and easily implement IPersistMemory using the functionality IPersistStreamInitImpl provides.

With the exception of the Load and Save methods, all methods in our IPersistMemory implementation simply delegate to the same named method in ATL's IPersistStreamInit implementation.

template <class T, class S = IPersistStreamInit>
class ATL_NO_VTABLE IPersistMemoryImpl : public IPersistMemory {
public:
    // IPersist
    STDMETHODIMP GetClassID(CLSID *pClassID) {
        ATLTRACE(atlTraceCOM, 2, _T("IPersistMemoryImpl::GetClassID\n"));
        T* pT = static_cast<T*>(this);
        S* psi = static_cast <S*> (pT);
        return psi->GetClassID(pClassID);
    }

    // IPersistMemory
    STDMETHODIMP IsDirty() {
        ATLTRACE(atlTraceCOM, 2,
            _T("IPersistMemoryImpl::IsDirty\n"));
        T* pT = static_cast<T*>(this);
        S* psi = static_cast <S*> (pT);
        return psi->IsDirty();
    }

    STDMETHODIMP Load(void* pvMem, ULONG cbSize) {
        ATLTRACE(atlTraceCOM, 2,
            _T("IPersistMemoryImpl::Load\n"));
        T* pT = static_cast<T*>(this);

        // Get memory handle. We need an actual HGLOBAL
        // here because it's required for CreateStreamOnHGlobal
        HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, cbSize);
        if (h == NULL) return E_OUTOFMEMORY;
        LPVOID pv = GlobalLock(h);
        if (!pv) return E_OUTOFMEMORY;

        // Copy to memory block
        CopyMemory (pv, pvMem, cbSize);
        CComPtr<IStream> spStrm;
        // Create stream on memory
        HRESULT hr = CreateStreamOnHGlobal (h, TRUE, &spStrm);
        if (FAILED (hr)) {
            GlobalUnlock (h);
            GlobalFree (h);
            return hr;
        }
        // Stream now owns the memory

        // Load from stream
        S* psi = static_cast <S*> (pT);
        hr = psi->Load (spStrm);

        GlobalUnlock (h);
        return hr;
    }

    STDMETHODIMP Save(void* pvMem, BOOL fClearDirty,
        ULONG cbSize) {
        ATLTRACE(atlTraceCOM, 2,
            _T("IPersistMemoryImpl::Save\n"));
        T* pT = static_cast<T*>(this);

        // Get memory handle
        HGLOBAL h = GlobalAlloc (GMEM_MOVEABLE, cbSize);
        if (NULL == h) return E_OUTOFMEMORY;

        // Create stream on memory
        CComPtr<IStream> spStrm;
        HRESULT hr = CreateStreamOnHGlobal (h, TRUE, &spStrm);
        if (FAILED (hr)) {
            GlobalFree (h);
            return hr;
        }
        // Stream now owns the memory

        // Set logical size of stream to physical size of memory
        // (Global memory block allocation rounding causes
        // differences)
        ULARGE_INTEGER uli;
        uli.QuadPart = cbSize ;
        spStrm->SetSize (uli);

        S* psi = static_cast <S*> (pT);
        hr = psi->Save (spStrm, fClearDirty);
        if (FAILED (hr)) return hr;

        LPVOID pv = GlobalLock (h);
        if (!pv) return E_OUTOFMEMORY;
        // Copy to memory block
        CopyMemory (pvMem, pv, cbSize);

        return hr;
    }

    STDMETHODIMP GetSizeMax(ULONG* pcbSize) {
        if (pcbSize == NULL) return E_POINTER;
        *pcbSize = 0;

        T* pT = static_cast<T*>(this);
        S* psi = static_cast <S*> (pT);
        ULARGE_INTEGER uli ;
        uli.QuadPart = 0;
        HRESULT hr = psi->GetSizeMax (&uli);
        if (SUCCEEDED (hr)) *pcbSize = uli.LowPart;

        return hr;
    }

    STDMETHODIMP InitNew() {
        ATLTRACE(atlTraceCOM, 2,
            _T("IPersistMemoryImpl::InitNew\n"));
        T* pT = static_cast<T*>(this);
        S* psi = static_cast <S*> (pT);
        return psi->InitNew();
    }
};

Notice the use of static_cast to downcast the this pointer to the deriving class and then up-cast the resulting pointer to an IPersistStreamInit*. We do this so that we get a compile-time error when the class deriving from IPersistMemoryImpl doesn't also derive from IPersistStreamInit. This approach does require the deriving class to not implement IPersistStreamInit in an "unusual" way, such as on a tear-off interface or via aggregation.

Alternatively, we could have retrieved the IPersistStreamInit interface using QueryInterface:

T* pT = static_cast<T*>(this);
CComQIPtr<S> psi = pT->GetUnknown() ;

However, then we might find out at runtime that no IPersistStreamInit implementation is available, which means the object then ends up saying that it implements IPersistMemory without the capability to do so. I prefer compile-time errors whenever possible, so I chose the former approach accepting its limitations.

Using the IPersistMemoryImpl Template Class

An object uses this IPersistMemory implementation this way:

class ATL_NO_VTABLE CDemagogue :
    ...
    public IPersistStreamInitImpl<CDemagogue>,
    public IPersistMemoryImpl<CDemagogue>,
    public CSupportDirtyBit {
    ...
BEGIN_COM_MAP(CDemagogue)
    ...
    COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
    COM_INTERFACE_ENTRY(IPersistStreamInit)
    COM_INTERFACE_ENTRY(IPersistMemory)
END_COM_MAP()


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)