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