Why ATL Uses Template Classes

Introduction

This article discusses why ATL uses template classes. This is not a discussion of ATL features, but just the principle behind the Template library.

Let us start with an implementation of the IUnknown interface. We all know that this is an interface that has to be supported by all COM objects. So, I thought of implementing this interface, thus making it convenient for everyone writing COM classes to extend it and be happy about IUnknown implementation. My objective is to implement IUnknown once and use it in all the COM classes I write in the future, thus reducing the burden of implementing it every time I write a COM class.

Let us try a method where we implement IUnknown in a class called IUnknownImpl and extend it to derive the IUnknown functionality in other classes.

Here is the C++ definition of the IUnknown interface:

Class IUnknown
{
public:
  virtual HRESULT QueryInterface(REFIID refiid, void **ppv) = 0;
  virtual ULONG    AddRef() = 0;
  virtual ULONG    Release() = 0;
};

The simplest possible implementation of IUnknown interface is as follows:

Class IUnknownImpl : public IUnknown
{
  ULONG m_cRef;
  REFIID  m_IID;

  public:

  IUnknownImpl(REFIID iid) : m_cRef(0), m_IID(iid)
  {
  }

  virtual HRESULT QueryInterface(REFIID refiid, void **ppv)
  {
    if(refiid  == IID_IUnknown || refiid == m_IID)
      *ppv = this;
    //I can not typecast this interface since 
    //I don't have an interface to which I need to typecast
    else
      return E_NOINTERFACE;

    static_cast<IUnknown*>(this)->AddRef();

    return S_OK;
  }

  virtual ULONG AddRef()
  {
  return InterlockedIncrement(m_cRef);
  }

  virtual ULONG Release()
  {
    ULONG ul = 0;
    if(0 == (ul = InterlockedDecrement(m_cRef)))
    delete this;

    return ul;
  }
};

Let us define a COM interface called IMyInterface. This interface must extend IUnknown for obvious reasons.

Class IMyInterface : public IUnknown
{
  public:
  virtual HRESULT MyFunction(int iCount) = 0;
};

Because I already have IUnknown implementation, I need not implement IUnknown methods again. I will derive it from the IUnknownImpl class.

Class MyInterfaceImpl : public IMyInterface, IUnknownImpl
{
  public:
    MyInterfaceImpl() : IUnknownImpl(IID_IMyInterface)
    {
    }

    virtual HRESULT MyFunction(int iCount)
    {
    //do something
    return S_OK;	
    } 
};

Thus we implemented IMyInterface. But this implementation has a problem, visible readily to the minds of seasoned C++ developers. It has the grave diamond problem, as shown in the following illustration.

Let us try to modify IUnknownImpl class so that we avoid this problem. This implementation, instead of extending IUnknown, will implement IUnknown methods of any interface derived from IUnknown.

Template <class BASE_INTERFACE, REFIID iid >
    //BASE_INTERFACE should derive from IUnknown
Class IUnknownImpl2 : public BASE_INTERFACE
{
  ULONG m_cref ;

  public:
  IUnknownImpl(): m_cRef(0) {}

  virtual HRESULT QueryInterface(REFIID refiid, void **ppv)
  {
  if(refiid  == IID_IUnknown || refiid == iid)
    *ppv = static_cast<BASE_INTERFACE*>(this);
    // I have to typecast it to BASE_INTERFACE*, I know that
  else
    return E_NOINTERFACE;

        static_cast<IUnknown*>(this)->AddRef();

        return S_OK;
  }

  virtual ULONG AddRef()
  {
      return InterlockedIncrement(m_cRef);
  }

  virtual ULONG Release()
  {
    	   ULONG ul = 0;
  if(0 == (ul = InterlockedDecrement(m_cRef)))
    delete this;

         return ul;
  }
};

Now I can derive my class MyInterfaceImpl2 from IUnknownImpl2 in this way.

Class MyInterfaceImpl2 : IUnknownImpl2 < IMyInterface,
                                            IID_MYInterface>
{
// my methods
};

The inheritance pattern is now changed to:

The active template library is intended to take care of the COM basic functionalities like Object lifetime and Threading model management, implementation of standard interfaces IUnknown, IDispatch, IClassFactory, and so forth. Using this simple Template library, we can concentrate on implementing our component's core functionality and forget about the other issues mentioned above.

Templates help us to write generic parameterized classes. As we saw in the above examples, with template-based implementation, we overcame the diamond problem while deriving from an implementation; we could typecast the "this" pointer to the required interface wherever it was required. For further discussion of ATL internals, I would recommend "ATL Internals" by Brent Rector and Chris Sells.



Comments

  • How to integrate a win32 console ADO program into ATL Service

    Posted by Legacy on 10/24/2002 12:00am

    Originally posted by: ruilin yang

    Guru's,

    Please help me. I am a relatively new c++/MFC programers. I created an ATL service module and compiled successfully. I also write a seperate win32 console program using ADO to do database manapulations, which works fine.

    However, I like my ATL Service program to call (or integrate) my Win32 console program. When I add my win32 console .cpp to my ATL project I can not get compiled. There are certainly a lots conflicts. I do not need any displaying windows.

    Please give me some advance for the integration.

    thanks in advance.

    Ruilin


    Reply
  • This only works for single-interfaced components

    Posted by Legacy on 07/31/2002 12:00am

    Originally posted by: Alex P.

    Your solution only seems to work if your component
    
    supports only a single interface (which is usually not the
    case in the real world). Consider what happens when you
    add a new interface:

    class MyInterfaceImpl2 : public IUnknownImpl2< IMyInterface, IID_MyInterface >,
    public IUnknownImpl2< IMyInterface2, IID_MyInterface2 >
    {
    //...
    };

    This would create yet another dreaded diamond problem
    since now you have two implementations of the methods
    in IUnknown and an ambiguity problem when you try to call
    QueryInterface() on the component (which would be the case
    in the implementation of IClassFactory::CreateIntance()
    for that component).

    How would you solve this?

    Alex P.

    Reply
  • need virtual destructor

    Posted by Legacy on 06/03/2002 12:00am

    Originally posted by: Daniel Anderson

    you need a virtual destructor in one of your IUnknown base class. Without it delete this in tour implementation will not do what is expected. Wrong destructor will be called.

    Daniel

    Reply
  • Why Couldn't use the grave diamond problem resolution??

    Posted by Legacy on 05/30/2002 12:00am

    Originally posted by: Eric

    I understand the probleme of the grave diamond! But in C++ we simply resolve this like the following code...

    class A
    {
    ...
    };

    class B : public virtual A
    {
    ...
    };

    class C : public virtual A
    {
    ...
    };

    class D : public B, public C
    {
    ...
    };

    the class B and D derive from the class public A but by specifying A as Virtual in the declarations of class B and C we eliminate the grave diamond problem!!

    With this technic rather than have the following internal structure:

    A A // 2 instance of A is created...
    B C
    D

    We obtain the following correct internal structure:

    A // only 1 instance of A is created...
    B C
    D


    Why ATL don't implement it like that?

    Eric

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

Most Popular Programming Stories

More for Developers

RSS Feeds