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()
|