[Previous] [Contents] [Next]

Marshal-by-Value

One specialized use of custom marshaling is known as marshal-by-value. When interface pointers are unmarshaled in COM+, the client always receives a pointer, usually through a proxy, to the original object; this is known as marshal-by-reference because the client receives a reference to the object. With marshal-by-value, on the other hand, the client receives its very own copy of the object rather than a reference to the original object. This is most useful for objects whose state is immutable—such as an object that is initialized with some data during creation and whose state never changes after that. Monikers are a great example: after you create a moniker, you can never change the object that it names. If you want to name a different object, you must create a new moniker.

For objects with immutable state, marshal-by-reference makes little sense because it requires that every call to the object be made across whatever marshaling context separates the client and the object. Obviously, the overhead is greatest if calls are made across machines. Marshal-by-value support can offer a significant optimization because a clone of the object is created in the client's apartment. Monikers, for example, have built-in marshal-by-value support.

Adding marshal-by-value support in COM+ is not easy because the standard marshaler provides marshal-by-reference semantics. You must use custom marshaling to accomplish this. Basically, the technique used to support marshal-by-value is simply to serialize the entire state of the object into the marshaling stream during the IMarshal::MarshalInterface method call. Since COM+ automatically returns the marshaling stream to the proxy, the proxy can read the object's state in the IMarshal::UnmarshalInterface method and create what is effectively a clone of the original object in the client's address space. Objects that support marshal-by-value must be designed as in-process components so they can be instantiated in the client and server processes.

As you've seen, implementing the IMarshal interface is not easy. However, if you examine the methods of IMarshal and IPersistStream,11 you'll find that they share several methods that have similar purposes, as shown in the table below.

IMarshal Methods IPersistStream Methods
GetMarshalSizeMax GetSizeMax
GetUnmarshalClass GetClassID
MarshalInterface Save
UnmarshalInterface Load

This realization means that you can implement several of the methods of the IMarshal interface by delegating to the methods of IPersistStream. Since many more objects support IPersistStream than IMarshal, this is an important discovery. Part of the challenge of supporting marshal-by-value semantics is obtaining the object's state; for objects that implement the IPersistStream interface, this is almost trivial because the object automatically saves its state to a stream in the IPersistStream::Save method. You can actually provide an aggregatable implementation of IMarshal that performs custom marshaling simply by delegating to the outer object's implementation of IPersistStream; Figure 14-4 illustrates this architecture.

Click to view at full size.

Figure 14-4. An object that implements IPersistStream obtains support for IMarshal by aggregating a marshal-by-value object.

On the companion CD, in the Samples\Custom Marshaling\Marshal By Value folder, you'll find an implementation of an aggregatable object that provides an implementation of IMarshal with marshal-by-value semantics for any object that aggregates it and implements IPersistStream. The following code fragment shows the relevant methods of the IMarshal implementation that delegate to the outer object's IPersistStream methods:

HRESULT CMarshalByValue::GetUnmarshalClass(REFIID riid, 
    void* pv, DWORD dwDestContext, void* pvDestContext, 
    DWORD dwFlags, CLSID* pClsid)
{
    IPersistStream* pPersistStream = 0;
    HRESULT hr = m_pUnknownOuter->QueryInterface(
        IID_IPersistStream, (void**)&pPersistStream);

    // Call the outer object's GetClassID method.
    pPersistStream->GetClassID(pClsid);

    pPersistStream->Release();
    return S_OK;
}

HRESULT CMarshalByValue::GetMarshalSizeMax(REFIID riid, 
    void* pv, DWORD dwDestContext, void* pvDestContext, 
    DWORD dwFlags, DWORD* pSize)
{
    IPersistStream* pPersistStream = 0;
    m_pUnknownOuter->QueryInterface(IID_IPersistStream, 
        (void**)&pPersistStream);

    // Call the outer object's GetSizeMax method.
    ULARGE_INTEGER size;
    pPersistStream->GetSizeMax(&size);
    *pSize = size.LowPart;

    pPersistStream->Release();
     return S_OK;
}

HRESULT CMarshalByValue::MarshalInterface(IStream* pStream, 
    REFIID riid, void* pv, DWORD dwDestContext, 
    void* pvDestContext, DWORD dwFlags)
{
    AddRef();

    IPersistStream* pPersistStream = 0;
    m_pUnknownOuter->QueryInterface(IID_IPersistStream, 
        (void**)&pPersistStream);

    // Call the outer object's Save method.
    HRESULT hr = pPersistStream->Save(pStream, TRUE);

    pPersistStream->Release();
    return hr;
}

HRESULT CMarshalByValue::UnmarshalInterface(IStream* pStream, 
    REFIID riid, void** ppv)
{
    IPersistStream* pPersistStream = 0;
    m_pUnknownOuter->QueryInterface(IID_IPersistStream, 
        (void**)&pPersistStream);

    // Call the outer object's Load method.
    pPersistStream->Load(pStream);

    pPersistStream->Release();
    return m_pUnknownOuter->QueryInterface(riid, ppv);
}