Adding Marshal-by-Value Semantics Using Persistence


When you pass an interface pointer as a parameter to a remote (out-of-apartment) method call, the default in COM is to pass by reference. In other words, the object stays where it is, and only a reference to the object is given to the recipient of the call. This typically means that references to the object involve round-trips back to the object, which can be quite expensive. An object can override this pass-by-reference default by implementing the IMarshal interface.

The primary reason most developers implement IMarshal on an object is to give it pass-by-value semantics. In other words, when you pass an interface pointer to a remote method call, you prefer that COM pass a copy of the object to the method. All references to the object are then local and do not involve round-trips back to the "original" object. When an object implements IMarshal in such a way that it has pass-by-value semantics, we typically say that the object marshals by value.

interface IMarshal : public IUnknown {                        STDMETHOD GetUnmarshalClass([in] REFIID riid,                 [unique,in] void* pv,                                       [in] DWORD dwDestContext,                                   [unique,in] void* pvDestContext,                            [in] DWORD mshlflags, [out] CLSID* pCid);                 STDMETHOD GetMarshalSizeMax([in] REFIID riid,                 [unique,in] void* pv,                                       [in] DWORD dwDestContext,                                   [unique,in] void* pvDestContext,                            [in] DWORD mshlflags,                                       [out] DWORD* pSize) ;                                      STDMETHOD MarshalInterface([unique,in] IStream* pStm,         [in] REFIID riid, [unique][in] void* pv,                    [in] DWORD dwDestContext,                                   [unique,in] void* pvDestContext,                            [in] DWORD mshlflags);                                     STDMETHOD UnmarshalInterface([unique,in] IStream* pStm,       [in] REFIID riid, [out] void** ppv);                     STDMETHOD ReleaseMarshalData([unique,in] IStream* pStm);    STDMETHOD DisconnectObject([in] DWORD dwReserved);       };                                                          


Given a complete implementation of IPersistStream or IPersistStreamInit, it's quite easy to build a marshal-by-value implementation of IMarshal.

A class typically implements marshal-by-value by returning its own CLSID as the result of the GetUnmarshalClass method. The IPersistStream::GetClassID method produces the needed CLSID.

The GetMarshalSizeMax method must return the number of bytes needed to save the persistent state of the object into a stream. The IPersistStream::GetSizeMax method produces the needed size.

The MarshalInterface and UnmarshalInterface methods need to write and read, respectively, the persistent state of the object into the provided stream. Therefore, we can use the Save and Load methods of IPersistStream for this functionality.

ReleaseMarshalData and DisconnectObject method can simply return S_OK.

Here's a template class that uses an object's IPersistStreamInit interface to provide a marshal-by-value implementation.[1] Once again, I decided to down-cast and up-cast using static_cast to obtain the IPersistStreamInit interface, so I receive an error at compile time when the deriving class doesn't implement IPersistStreamInit.

[1] This technique has a history, as does most software development. Jonathon Bordan wrote the first IMarshalByValueImpl after being inspired by a Microsoft System Journal article by Don Box. Brent Rector then modified Jonathon's example to the present form.

template <class T> class ATL_NO_VTABLE IMarshalByValueImpl : public IMarshal {   STDMETHODIMP GetUnmarshalClass(REFIID /* riid */,     void* /* pv */,     DWORD /* dwDestContext */,     void* /* pvDestContext */,     DWORD /* mshlflags */, CLSID *pCid) {     T* pT = static_cast<T*>(this);     IPersistStreamInit* psi =       static_cast<IPersistStreamInit*>(pT);     return psi->GetClassID (pCid);   }   STDMETHODIMP GetMarshalSizeMax(REFIID /* riid */,     void* /* pv */,     DWORD /* dwDestContext */,     void* /* pvDestContext */,     DWORD /* mshlflags */, DWORD* pSize) {     T* pT = static_cast<T*>(this);     IPersistStreamInit* psi =       static_cast <IPersistStreamInit*> (pT);     ULARGE_INTEGER uli = { 0 };     HRESULT hr = psi->GetSizeMax(&uli);     if (SUCCEEDED (hr)) *pSize = uli.LowPart;     return hr;   }   STDMETHODIMP MarshalInterface(IStream *pStm, REFIID /* riid */,     void* /* pv */, DWORD /* dwDestContext */,     void* /* pvDestCtx */, DWORD /* mshlflags */) {     T* pT = static_cast<T*>(this);     IPersistStreamInit* psi = static_cast <IPersistStreamInit*> (pT);     return psi->Save(pStm, FALSE);   }   STDMETHODIMP UnmarshalInterface(IStream *pStm, REFIID riid,     void **ppv) {     T* pT = static_cast<T*>(this);     IPersistStreamInit* psi =       static_cast <IPersistStreamInit*> (pT);     HRESULT hr = psi->Load(pStm);     if (SUCCEEDED (hr)) hr = pT->QueryInterface (riid, ppv);     return hr;   }   STDMETHODIMP ReleaseMarshalData(IStream* /* pStm */) {     return S_OK;   }   STDMETHODIMP DisconnectObject(DWORD /* dwReserved */) {     return S_OK;   } }; 


You can use this template class to provide a marshal-by-value implementation for your object. You need to derive your class from the previous IMarshalByValueImpl class (to get the IMarshal method implementations) and the IPersistStreamInitImpl class. You must also add a COM_INTERFACE_ENTRY for IMarshal to the class's interface map. Here's an example:

class ATL_NO_VTABLE CDemagogue :     ...     public IPersistStreamInitImpl<CDemagogue>,     public CSupportDirtyBit,     public IMarshalByValueImpl<CDemagogue> {     ...   BEGIN_COM_MAP(CDemagogue)       COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)       COM_INTERFACE_ENTRY(IPersistStreamInit)       COM_INTERFACE_ENTRY(IMarshal)   END_COM_MAP()   ... }; 


Note that adding marshal-by-value support to your class this way means that all instances of the class use pass-by-value semantics. It is not possible to pass one object instance by reference and another instance by value (assuming that both instances have the same marshaling context: in-proc, local, or different machine).




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

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