Export (0) Print
Expand All
CLR Generics Versus C++ Templates
Copy Constructors, Assignment Operators, and More
Counting MDI Children, Browsing for Folders
Dialog Templates, RegexTest
Disabling Print Screen, Calling Derived Destructors, and More
Enum Declarations, Template Function Specialization
Form Validation with Regular Expressions in MFC
Generic Programming Under .NET
Generic Programming: Template Specialization
Hello, C++/CLI
Installing a Hook, Strings in Managed C++, and More
Layered Windows, Blending Images
Making Static Links Keyboard-Capable, Launching URLs from Your App
Persisting View State Update, Using Managed Extensions in a DLL
Power Your App with the Programming Model and Compiler Optimizations of Visual C++
Reflecting on Generic Types
Unreferenced Parameters, Adding Task Bar Commands, and More
Wrappers: Use Our ManWrap Library to Get the Best of .NET in Native C++ Code -- MSDN Magazine, April 2005
Expand Minimize

Extending the ATL Framework Part 1: Examining ATL Support for Persistence and Building a Marshalling by Value Implementation

Visual Studio .NET 2003
 

Martin Lapierre
DevInstinct.com

February 2002

Summary: This first part of the ATL Extension Series examines the core of ATL support for persistence, builds an implementation of marshalling by value (MBV), and requires familiarity with COM, ATL, persistence, and marshalling. Part 2 and 3 of the series will continue to explore the ATL framework design, and use the design as a model to create extended support for persistence and marshalling. (11 printed pages)

Download Sample

Contents

Introduction
Exploring the Framework
Marshalling by Value
Adding MBV Support to ATL
Reusable Design
ATL Extension Code Updates
Conclusion

Introduction

This first part of the ATL Extension Series examines the core of ATL support for persistence, builds an implementation of marshalling by value (MBV), and requires familiarity with COM, ATL, persistence, and marshalling. Part 2 and 3 of the series will continue to explore the ATL framework design, and use the design as a model to create extended support for persistence and marshalling.

Exploring the Framework

ATL supports persistence for properties declared in a property map by defining (in atlcom.h) three class templates:

  • IPersistStreamInitImpl
  • IPersistStorageImpl
  • IPersistPropertyBagImpl

To add persistence to a class, simply derive it from one of these three templates.

IPersistStreamInitImpl behavior and methods are close to matching what is needed for the implementation of marshalling discussed in this article since it persists properties on a stream. For example, examine the design of this saving mechanism:

template <class T>
class ATL_NO_VTABLE IPersistStreamInitImpl : public IPersistStreamInit
{
public:

// ... code skipped ...
  STDMETHOD(Save)(LPSTREAM pStm, BOOL fClearDirty)
  {
    T* pT = static_cast<T*>(this);
    ATLTRACE2(atlTraceCOM, 0, _T("IPersistStreamInitImpl::Save\n"));
    return pT->IPersistStreamInit_Save(pStm, fClearDirty, 
T::GetPropertyMap());
  }
// ... code skipped ...
  HRESULT IPersistStreamInit_Save(LPSTREAM pStm, BOOL fClearDirty, 
ATL_PROPMAP_ENTRY* pMap)
  {
    T* pT = static_cast<T*>(this);
    return AtlIPersistStreamInit_Save(pStm, fClearDirty, pMap, pT, 
pT->GetUnknown());
  }
};

#ifdef _ATL_DLL
ATLAPI AtlIPersistStreamInit_Save(LPSTREAM pStm, BOOL fClearDirty, 
                                  ATL_PROPMAP_ENTRY* pMap, void* pThis, 
IUnknown* pUnk);
#else
ATLINLINE ATLAPI AtlIPersistStreamInit_Save(LPSTREAM pStm, BOOL /* 
fClearDirty */, 
                                  ATL_PROPMAP_ENTRY* pMap, void* pThis, 
IUnknown* pUnk)
{
// ... code skipped ...
}
#endif //_ATL_DLL

The previous code shows three interesting design considerations:

  • The ATL_NO_VTABLE macro optimizes memory by preventing this class from having a virtual table, resulting in a smaller code size for this class.
  • While there is no v–table, the downcast made in the Save method enables redefinition for IPersistStreamInit_Save in the derived class. Consequently, this method is the perfect point of interception for custom functionality.
  • The implementation of the Save operation is not part of the class; it is made available in the AtlIPersistStreamInit_Save global function, which is called by IPersistStreamInit_Save. Having the Save operation exposed in this manner allows custom implementations to persist a given property map without the map being part of the original class.
    Note   Using the ATLTRACE2 macro in conjunction with the Save method is a useful debugging feature for tracing COM calls.

Later in this article the principles noted above will be applied and MBV support to the ATL framework will be added, but first, a quick introduction to MBV.

Marshalling by Value

Marshalling by value (MBV) is often used to create a clone of a remote component whose interface pointer is transmitted over the Web. This produces a copy of the component that is manipulated locally in order to save unnecessary network calls.

The marshalling process examines the context of the marshalled interface and if the process detects a cross-machine operation it writes its data on the stream instead of a reference to its interface. Consequently, a new instance of the component is created on the local computer.

The technique adopted for this article is the support of MBV through delegation to the IPersistStream and IPersistStreamInit interfaces. This technique, working in conjunction with ATL, deviates from the traditional approach used by many authors.

To describe this delegation technique, methods of the IPersistStream and IMarshal interfaces are compared:

IPersistStream IMarshal
HRESULT GetClassID(CLSID *pClassID); HRESULT GetUnmarshalClass(…,
CLSID * pCid);
HRESULT Load(IStream *pStm); HRESULT UnmarshalInterface
(IStream *pStm, …);
HRESULT Save(IStream *pStm, …); HRESULT MarshalInterface(IStream *pStm, …);
HRESULT GetSizeMax
(ULARGE_INTEGER *pcbSize);
HRESULT GetMarshalSizeMax(…,
ULONG * pSize);

The mechanisms involved here are similar. Both interfaces have a method to obtain the CLSID of the component needed for the read operation:

  • UnmarshalInterface — To read from the stream.
  • MarshalInterface — To write to the stream.
  • GetMarshalSizeMax — To obtain the maximum size needed on the stream for the write operation.

The delegation technique simply forwards IMarshal calls to the appropriate IPersistStream methods using the previously defined persistence implementation for streams.

Adding MBV Support to ATL

First, it is ideal to control the choice of whether to use standard marshalling, or MBV based on the marshalling context. To achieve this goal, the following illustrates how to declare an enumeration of marshalling options-flags and includes a macro to help determine if a context is specified by a flags value.

enum _MSHOPT
{
  MSHOPT_LOCAL            = 0x1, 
  MSHOPT_NOSHAREDMEM      = 0x2, 
  MSHOPT_DIFFERENTMACHINE = 0x4, 
  MSHOPT_INPROC           = 0x8 
}; 
#define MSH_ISMBV( dwDestContext, mshlopt ) \
  ((dwDestContext == MSHCTX_LOCAL && (mshlopt) & MSHOPT_LOCAL) || \
   (dwDestContext == MSHCTX_NOSHAREDMEM && (mshlopt) & MSHOPT_NOSHAREDMEM)
 || \
   (dwDestContext == MSHCTX_DIFFERENTMACHINE && (mshlopt) & 
MSHOPT_DIFFERENTMACHINE) || \
   (dwDestContext == MSHCTX_INPROC && (mshlopt) & MSHOPT_INPROC))

Second, define the abstract class template IMarshalBaseImpl (derived from IMarshal), for common marshalling functionality and add a DWORD member for the marshalling flags. The template parameter corresponds to the Concrete class, which is required to complete this exercise.

Since many custom marshalling implementations use the same component for marshalling and unmarshalling, this template includes the mechanism of the GetUnmarshalClass method, and simply returns the component's CLSID by delegating GetUnmarshalClass calls to an interception method called IMarshal_GetUnmarshalClass. Implementations requiring a different CLSID for the unmarshal class (for example, a smart proxy) will be able to redefine this method in the derived class and provide the appropriate value.

template <class T>
class ATL_NO_VTABLE IMarshalBaseImpl : public IMarshal
{
public:
  DWORD m_mshlopt;
  IMarshalBaseImpl() : m_mshlopt(MSHOPT_NOSHAREDMEM|MSHOPT_DIFFERENTMACHINE){}

  // IMarshal
  STDMETHOD(GetUnmarshalClass)
  (
    REFIID riid, 
    void __RPC_FAR *pv, 
    DWORD dwDestContext, 
    void __RPC_FAR *pvDestContext, 
    DWORD mshlflags, 
    CLSID __RPC_FAR *pCid
  )
  {
    ATLTRACE2(atlTraceCOM, 0, _T("IMarshalBaseImpl::GetUnmarshalClass\n"));
    T* pT = static_cast<T*>(this);
    return pT->IMarshal_GetUnmarshalClass(riid, pv, dwDestContext, 
                                          pvDestContext, mshlflags, pCid,
pT->m_mshlopt);
  }
// ... code skipped ...
  HRESULT IMarshal_GetUnmarshalClass
  (
    REFIID riid, 
    void __RPC_FAR *pv, 
    DWORD dwDestContext, 
    void __RPC_FAR *pvDestContext, 
    DWORD mshlflags, 
    CLSID __RPC_FAR *pCid,
    DWORD mshlopt
  )
  {
    T* pT = static_cast<T*>(this);
    return AtlIMarshal_GetUnmarshalClass<T>
           (riid, pv, dwDestContext, pvDestContext, mshlflags, pCid, pT,
mshlopt);
  }
}; // class IMarshalBaseImpl

The actual operation is again delegated to the global function template AtlIMarshal_GetUnmarshalClass, which could be reused in more complex implementations to marshal CComCoClass-based class members. The function uses the MSH_ISMBV macro to determine the appropriate marshalling operation.

template <class T>
inline HRESULT AtlIMarshal_GetUnmarshalClass
(
  REFIID riid, 
  void __RPC_FAR *pv, 
  DWORD dwDestContext, 
  void __RPC_FAR *pvDestContext, 
  DWORD mshlflags, 
  CLSID __RPC_FAR *pCid,
  T *pT,
  DWORD mshlopt = MSHOPT_NOSHAREDMEM|MSHOPT_DIFFERENTMACHINE
)
{
  ATLASSERT(pT != NULL);

  if (MSH_ISMBV(dwDestContext, mshlopt))
  {
    // Return the component's CLSID.
    *pCid = pT->GetObjectCLSID();
    return S_OK;
  }
  else 
  {
    // Use standard marshalling.
    CComPtr<IMarshal> ccpIMarshal;
    CComPtr<IUnknown> ccpIUnk(static_cast<IUnknown*>(pv));
    TestHR(CoGetStandardMarshal(riid, ccpIUnk, dwDestContext, 
pvDestContext, mshlflags, &ccpIMarshal))
    return ccpIMarshal->GetUnmarshalClass(riid, pv, dwDestContext, 
pvDestContext, mshlflags, pCid);
  }
} 

To complete IMarshalBaseImpl, add simple tracing code and interception methods for the ReleaseMarshalData and DisconnectObject methods. Even if the delegate methods do nothing, the constant structure of the class will make it easier to use. For details, see the ATLExtMarshal.h file.

Additionally, two steps remain before this exercise is completed:

  1. Derive the class template IMarshalOnStreamImpl from IMarshalBaseImpl to handle MBV.
  2. Define specialized function templates along the lines of the ATL framework to delegate IMarshal method calls to IPersistStream or IPersistStreamInit.

The IMarshalOnStreamImpl implementation of GetMarshalSizeMax, MarshalInterface, and UnmarshalInterface features the same delegation technique used for GetUnmarshalClass by exposing to interception the IMarshalOnStream_GetMarshalSizeMax, IMarshalOnStream_MarshalInterface, and IMarshalOnStream_UnmarshalInterface methods.

Functionality rests in MBV function templates. To handle MBV, these three global function templates must be defined:

  • AtlIMarshalOnStream_GetMarshalSizeMax
  • AtlIMarshalOnStream_MarshalInterface
  • AtlIMarshalOnStream_UnmarshalInterface

AtlIMarshalOnStream_MarshalInterface checks the marshalling context as AtlIMarshal_GetUnmarshalClass does and applies MBV only when appropriate. In this case, the write operation is delegated to the IPersistStreamInit or IPersistStream Save method.

template <class T>
inline HRESULT AtlIMarshalOnStream_MarshalInterface
( 
  IStream __RPC_FAR *pStm,
  REFIID riid,
  void __RPC_FAR *pv,
  DWORD dwDestContext,
  void __RPC_FAR *pvDestContext,
  DWORD mshlflags,
  T *pT,
  DWORD mshlopt = MSHOPT_NOSHAREDMEM|MSHOPT_DIFFERENTMACHINE
)
{
  ATLASSERT(pT != NULL);

  if (MSH_ISMBV(dwDestContext, mshlopt))
  {
    // Use the persistence implementation.
    CComQIPtr<IPersistStream> ccpIPS;
    CComQIPtr<IPersistStreamInit> ccpIPSI;

    if (ccpIPSI = pT->GetUnknown()) // Look for IPersistStreamInit.
      return ccpIPSI->Save(pStm, FALSE);
    else if (ccpIPS = pT->GetUnknown()) // Look for IPersistStream.
      return ccpIPS->Save(pStm, FALSE);
    else // Persistence not implemented.
      return E_FAIL;
  }
  else 
  {
    // Use standard marshalling.
    CComPtr<IMarshal> ccpIMarshal;
    CComPtr<IUnknown> ccpIUnk(static_cast<IUnknown*>(pv));
    TestHR(CoGetStandardMarshal(riid, ccpIUnk, dwDestContext, 
pvDestContext, mshlflags, &ccpIMarshal))
    return ccpIMarshal->MarshalInterface(pStm, riid, pv, dwDestContext,
pvDestContext, mshlflags);
  }
}

The AtlIMarshal_GetUnmarshalClass function is shorter and is not required to handle the marshalling context. It simply delegates the read operation to the Load method of the persistence interface.

template <class T>
inline HRESULT AtlIMarshalOnStream_UnmarshalInterface
( 
  IStream __RPC_FAR *pStm,
  REFIID riid,
  void __RPC_FAR *__RPC_FAR *ppv,
  T *pT
)
{
  ATLASSERT(pT != NULL);

  CComQIPtr<IPersistStream> ccpIPS;
  CComQIPtr<IPersistStreamInit> ccpIPSI;

  if (ccpIPSI = pT->GetUnknown()) // Look for IPersistStreamInit.
    TestHR(ccpIPSI->Load(pStm))
  else if (ccpIPS = pT->GetUnknown()) // Look for IPersistStream.
    TestHR(ccpIPS->Load(pStm))
  else // Persistence not implemented.
    return E_FAIL;

  return pT->QueryInterface(riid, (void**)ppv);
}

Reusable Design

IMarshalOnStreamImpl is the class template that should be used as a base class for adding MBV to a component.

class ATL_NO_VTABLE CMyComponent : 
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CMyComponent, &CLSID_MyComponent>,
  public IDispatchImpl<IMyComponent, &IID_IMyComponent, &LIBID_MyLib>,
  public IPersistStream,
  public IMarshalOnStreamImpl<CMyComponent>
{
  // ... code skipped ...
  BEGIN_COM_MAP(CMyComponent)
    COM_INTERFACE_ENTRY(IMyComponent)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IPersistStream)
    COM_INTERFACE_ENTRY(IMarshal)
  END_COM_MAP()
  // ... code skipped ...
};

Because IMarshalOnStreamImpl is based on ATL, a flexible framework is now present and functionality can be added. For example, redefine IMarshalOnStreamImpl delegate methods to add smart proxy support. For in-process marshalling use the free-threaded marshaller.

The AtlIMarshalOnStream_GetUnmarshalClass function, which is directed to delegate GetUnmarshalClass calls, which in turn are routed to the persisting interface's GetClassID, are not defined because this function category is generic and can handle any CComCoClass-based class members (which may implement IPersistStream or IPersistStreamInit, but not IMarshal). The responsibility of the unmarshalling process must then fall to the marshalled component, not its members.

ATL Extension Code Updates

For the latest code update, see ATLExtension Update.

Conclusion

The foundation for more advanced extensions to ATL is set. Part 2 of the ATL Extension Series will cover automatic persistence and marshalling of STL collections and will show how the ATL framework makes reusability a reality.

About the Author

Martine Lapierre is a freelance writer and consultant hosted in Montreal, Quebec, Canada. He holds a BS in Computer Science from the University of Montreal and has been designing desktop, n-tiers, and Web applications on the Windows platform since 1995.

References

Show:
© 2014 Microsoft