Custom Marshaling

So far, this chapter has focused on standard marshaling and ORPC-based method remoting. For a large class of objects, this is all that is needed to achieve the proper balance between performance, semantic correctness, and simplicity of implementation. There are objects for which the default behavior of ORPC method remoting is inefficient to the point of being unusable. For these objects, COM supports custom marshaling. As mentioned earlier in this chapter, custom marshaling allows the object implementor to provide a custom proxy implementation that will be created in the importing apartment. Objects indicate that they wish to support custom marshaling by exporting the IMarshal interface:

[ uuid(00000003-0000-0000-C000-000000000046),local,object ]
interface IMarshal : IUnknown {

// get CLSID for custom proxy (CoMarshalInterface)
   HRESULT GetUnmarshalClass(
        [in] REFIID riid, [in, iid_is(riid)] void *pv,
        [in] DWORD dwDestCtx, [in] void *pvDestCtx,
        [in] DWORD mshlflags, [out] CLSID *pclsid);

// get size of custom marshaled objref (CoGetMarshalSizeMax)
   HRESULT GetMarshalSizeMax(
        [in] REFIID riid, [in, iid_is(riid)] void *pv,
        [in] DWORD dwDestCtx, [in] void *pvDestCtx,
        [in] DWORD mshlflags, [out] DWORD *pSize);

// write out custom marshaled objref (CoMarshalInterface)
   HRESULT MarshalInterface([in] IStream *pStm, 
        [in] REFIID riid, [in, iid_is(riid)] void *pv,
        [in] DWORD dwDestCtx, [in] void *pvDestCtx,
        [in] DWORD mshlflags);

// read objref and return proxy (CoUnmarshalInterface)
   HRESULT UnmarshalInterface([in] IStream *pStm,
                           [in] REFIID riid, 
                      [out,iid_is(riid)] void **ppv);

// revoke a marshal (CoReleaseMarshalData)
   HRESULT ReleaseMarshalData([in] IStream *pStm);

// tear down connection-state (CoDisconnectObject)
   HRESULT DisconnectObject([in] DWORD dwReserved);
}

The comments that precede the method definitions indicate which COM APIs call each method.

When CoMarshalInterface is called on an object that supports custom marshaling, the marshaled object reference has a somewhat different format, as shown in Figure 5.7. Note that after the standard MEOW header, the marshaled object reference simply contains a CLSID used to create the custom proxy and an opaque array of bytes that will be used to initialize the custom proxy. CoMarshalInterface discovers the custom proxy’s CLSID by calling the IMarshal::GetUnmarshalClass method on the object. CoMarshalInterface fills the opaque byte array by calling the object’s IMarshal::MarshalInterface implementation. It is in MarshalInterface that the object gets its one and only chance to send an initialization message to the new custom proxy simply by writing the message to the supplied byte stream. When CoUnmarshalInterface is called, this message will be given to a newly created custom proxy via the proxy’s IMarshal::UnmarshalInterface method. This means that both the object and the custom proxy must implement IMarshal. The object’s MarshalInterface method writes the initialization message. The proxy’s UnmarshalInterface reads the initialization message. Once the UnmarshalInterface method returns, COM is no longer involved in any proxy/object communications. It is the job of the custom proxy to implement the interface methods in a way that is semantically correct. If a method call needs to be remoted to the object, then it is the proxy’s job to do this. If the method can be implemented in the client’s apartment, then the proxy can do this as well.

Figure 5.7 Custom Marshal Object Reference

One of the benefits of custom marshaling is that the client is oblivious that it is being used. In fact, the client cannot reliably detect whether an interface is a standard proxy, a custom proxy, or the actual object. Custom marshaling is an object-by-object decision. Two instances of the same class could elect to use standard or custom marshaling independently. When an object elects to implement custom marshaling, it must do so for all interfaces. If an object wishes to custom marshal only for a subset of all possible marshaling contexts (e.g., in-process, local, different machine), the object can get an instance of the standard marshaler and forward its IMarshal methods for unsupported contexts so that all contexts can be supported. In fact, if an object were simply to forward all IMarshal methods to the standard marshaler unconditionally, the object would in effect always use standard marshaling. To get a pointer to the standard marshaler, objects can call the CoGetStandard-Marshal method:

HRESULT CoGetStandardMarshal(
      [in]REFIID riid,       // type of itf marshaled?
[in, iid_is(riid)] IUnknown *pUnk, // the itf to marshal
      [in] DWORD dwDestCtx,  // MSHCTX
      [in] void *pvDestCtx,  // reserved
      [in] DWORD mshlflags,  // normal vs. table
      [out] IMarshal **ppMarshal); // ptr to std. Marshal

Assume that an object uses a technique for custom marshaling that works only on the local host but not when communicating with off-host apartments. The object’s implementation of GetMarshalSizeMax would look something like this:

STDMETHODIMP CustStd::GetMarshalSizeMax(ULONG *pcb,
                REFIID riid, void *pv, DWORD dwDestCtx, 
                void *pvDestCtx,  DWORD mshlflags) {
// if context is supported, do work!
   if (dwDestCtx==MSHCTX_LOCAL || dwDestCtx==MSHCTX_INPROC) 
      return this->MyCustomMarshalingRoutine(pcb);

// unsupported context, delegate to std marshal
   IMarshal *pMsh = 0;
   HRESULT hr = CoGetStandardMarshal(riid, pv, dwDestCtx, 
                                  pvDestCtx, mshlflags,
                                  &pMsh);
   if (SUCCEEDED(hr)) {
      hr = pMsh->GetMarshalSizeMax(pcb, riid, pv, dwDestCtx, 
                               pvDestCtx, mshlflags);
      pMsh->Release();
   }
   return hr;
}

What isn’t shown in this code fragment is how to write the initialization message when custom marshaling is actually desired. This is because there is no standard implementation of any of the IMarshal methods (hence the term custom marshaling). However, there are several common scenarios in which custom marshaling is extremely beneficial and implementing IMarshal in these scenarios is somewhat regular. By far the most common application of IMarshal is to implement marshal-by-value.

Marshal-by-value is most useful for objects that never change their state once initialized. COM wrappers to structures are a common example of an object that is simply initialized, passed to another object to be queried, and then destroyed. Such an object is a prime candidate for custom marshaling. To implement marshal-by-value, the object implementation is almost always an in-process server. This allows the object and proxy to share the same implementation class. The idea behind marshal-by-value is that the custom proxy becomes a clone of the original object. This implies that the marshaled object reference must contain the entire state of the original object and (for simplicity) that the CLSID for the custom proxy must be the same as that of the original object.

Assume the following class definition of a COM wrapper around a simple two-dimensional point:

class Point : public IPoint, public IMarshal {
   long m_x; long m_y;
public:
   Point(void) : m_x(0), m_y(0) {}
   IMPLEMENTS_UNKNOWN(Point)
   BEGIN_INTERFACE_TABLE(Point)
      IMPLEMENTS_INTERFACE(IPoint)
      IMPLEMENTS_INTERFACE(IMarshal)
   END_INTERFACE_TABLE()
// IPoint methods
// IMarshal methods
};

To support marshal-by-value, the class’s MarshalInterface method would need to serialize the state of the object as the initialization message for the proxy:

STDMETHODIMP Point::MarshalInterface(IStream *pStm, REFIID,
                   void *, DWORD, void *, DWORD) {
// write out endian header
   DWORD dw = 0xFF669900; 
   HRESULT hr = pStm->Write(&dw, sizeof(DWORD), 0);
   if (FAILED(hr)) return hr;
   dw = m_x;
   hr = pStm->Write(&dw, sizeof(DWORD), 0);
   if (FAILED(hr)) return hr;
   dw = m_y;
   return pStm->Write(&dw, sizeof(DWORD), 0);
}

Assuming that the object’s class is implemented as an in-process server, the custom proxy can simply be a second instance of the same class, which implies the following implementation of GetUnmarshalClass:

STDMETHODIMP Point::GetUnmarshalClass(REFIID, void *, DWORD,
                           void *, DWORD, CLSID *pclsid) {
  *pclsid = CLSID_Point; // this class’s CLSID  
  return hr;
}

To ensure that enough room is allocated for the initialization message, the object’s GetMarshalSizeMax method needs to return the correct byte count:

STDMETHODIMP Point::GetMarshalSizeMax(REFIID, void *, DWORD,
                           void *, DWORD, DWORD *pcb) {
   *pcb = 3 * sizeof(DWORD); // m_x + m_y + header
   return hr;
}

When the marshaled object reference is unmarshaled by CoUnmarshal-Interface, the fact that the marshaled object reference is a custom marshal will cause a new custom proxy to be created. The object reference contains the custom proxy’s CLSID that the original object returned in its GetUnmarshalClass method. Once a new custom proxy is created, its UnmarshalInterface method is given the initialization message that the object wrote in its MarshalInterface implementation:

STDMETHODIMP Point::UnmarshalInterface(IStream *pStm,
                              REFIID riid, void ** ppv) {
   *ppv = 0;
// read endian header
   DWORD dw; ULONG cbRead;
   HRESULT hr = pStm->Read(&dw, sizeof(DWORD), &cbRead);
   if (FAILED(hr) || cbRead != sizeof(DWORD))
    return RPC_E_INVALID_DATA;
   bool bSwapEndian = dw == 0x009966FF;
// read m_x and m_y
   hr = pStm->Read(&dw, sizeof(DWORD), &cbRead);  m_x = dw;
   if (FAILED(hr) || cbRead != sizeof(DWORD))
    return RPC_E_INVALID_DATA;
   hr = pStm->Read(&dw, sizeof(DWORD), &cbRead); m_y = dw;
   if (FAILED(hr)) || cbRead != sizeof(DWORD))
    return RPC_E_INVALID_DATA;
// byte swap members if necessary
  if (bSwapEndian)
    byteswapdata(&m_x, &m_y);
// return pointer to this object
  return this->QueryInterface(riid, ppv);
}

Note that the implementation of MarshalInterface and UnmarshalInterface need to ensure that the marshaled state is readable on any platform. This means manually dealing with alignment, byte ordering, and data type size differences.

The preceding implementation of UnmarshalInterface simply returns a pointer to the newly created custom proxy. For a simple marshal-by-value object, this may be satisfactory. However, more generic implementations of UnmarshalInterface may want to detect multiple unmarshals that correspond to the same COM identity and return a pointer to the same proxy identity to maintain the identity relationship of the proxy to the object. This not only can save resources but also keeps the programming model cleaner.

© 1998 by Addison Wesley Longman, Inc. All rights reserved.