Click here to Skip to main content
15,878,959 members
Articles / Programming Languages / C++11
Tip/Trick

Variadic Template Technique and COM - Unusual Solution of the Usual Problem

Rate me:
Please Sign up or sign in to vote.
4.16/5 (9 votes)
20 Mar 2016CPOL4 min read 19.5K   6   2
Simple variadic template for implementation of IUnknown and IDispatch interfaces

Introduction

This tip presents a simple trick for resolving the usual problem of COM technology - implementation of AddRef, Release and QueryInterface methods of IUnknown and IDispatch interfaces.

Background

According to COM philosophy - each COM object must have its own implementation of IUnknown and IDispatch interfaces. For simple COM object with the one interface, there is an easy solution, but it needs to copy/paste some code. ATL has some template for simplifying such an operation, but it has a weak side - it needs to use COM Map macroses. Using of COM Map can be easy, but it needs to write code in some sections of code. Special problem with implementation in COM object one, two or more interfaces which needs to rewriting COM Map. It can be a problem if it needs to create COM object with the different interfaces for different purposes - for example, Free and Commercial versions.

It would be suitable to have a template which can recognize the current amount of interfaces and creates the suitable QueryInterface method for COM object. Such task can be resolved by variadic template.

Using the Code

The solution is presented in the form of a template with variadic template argument. The template BaseUnknown is very simple and is presented in the next listing:

C++
template <typename... Interfaces>
    struct BaseUnknown:
        public Interfaces...
    {
        BaseUnknown() :
            mRefCount(1){}

        // IUnknown implementation

        STDMETHODIMP QueryInterface(
            REFIID aRefIID,
            _COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject)
        {
            HRESULT lresult = S_OK;

            do
            {
                if (aPtrPtrVoidObject == NULL)
                {
                    lresult = E_POINTER;

                    break;
                }


                bool lboolResult = findInterface(aRefIID, aPtrPtrVoidObject);

                if (!lboolResult)                 
                {                     
                    *aPtrPtrVoidObject = NULL;

                    lresult = E_NOINTERFACE;                 
                }

            } while (false);

            if (SUCCESSED(lresult))             
            {                 
                AddRef();             
            }

            return lresult;         
        }                      
        
        STDMETHODIMP_(ULONG) AddRef()         
        {             
            return ++mRefCount;        
        }

        STDMETHODIMP_(ULONG) Release()         
        {             
            auto lCount = --mRefCount;

            if (lCount == 0)             
            {                 
                 delete this;             
            }

            return lCount;         
        }

    protected:

        virtual ~BaseUnknown(){}

        virtual bool findInterface(             
             REFIID aRefIID, 
             _COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject)         
            {             
                 if (aRefIID == IID_IUnknown)             
                 {                 
                       return castToIUnknow(                     
                              aPtrPtrVoidObject,                     
                              static_cast<Interfaces*>(this)...);             
                 }             
                 else             
                 {                 
                       return castInterfaces(                     
                              aRefIID,                     
                              aPtrPtrVoidObject,                     
                              static_cast<Interfaces*>(this)...);             
                 }         
            }         

    private:         

        std::atomic<ULONG> mRefCount;                 

       template<typename Interface, typename... Args>         
       bool castToIUnknow(             
             _COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject,             
             Interface* aThis,             
             Args... aRest)         
       {             
             return castToIUnknow(aPtrPtrVoidObject, aThis);         
       }

       template<typename Interface>         
       bool castToIUnknow(             
       _COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject,             
       Interface* aThis)         
       {             
             bool lresult = false;

            *aPtrPtrVoidObject = static_cast<IUnknown*>(aThis);

            if (*aPtrPtrVoidObject != nullptr)                 
                  lresult = true;

            return lresult;         
       }

       template<typename Interface, typename... Args>         
       bool castInterfaces(             
            REFIID aRefIID,             
            _COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject,             
            Interface* aThis,             
            Args... aRest)         
      {             

            bool lresult = castInterfaces(aRefIID, aPtrPtrVoidObject, aThis);

            if (!lresult)             
            {                 
                lresult = castInterfaces(                     
                          aRefIID,                     
                          aPtrPtrVoidObject,                     
                          aRest...);             
            }

            return lresult;         
     }
        
     template<typename Interface>         
     bool castInterfaces(             
           REFIID aRefIID,             
           _COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject,             
           Interface* aThis)         
    {             
            bool lresult = aRefIID == __uuidof(Interface);

            if (lresult)             
            {                 
                *aPtrPtrVoidObject = aThis;

                if (*aPtrPtrVoidObject == nullptr)
                    lresult = false; 
            }

            return lresult; 
     }
};

This template is very simple and for developers who understand template technique, it is easy to understand how it works. For others, I can show how it works.

Let's define three COM interfaces:

C++
struct IA: public IUnknown
{};

struct IB: public IUnknown
{};

struct IC: public IUnknown
{};

Defining COM object which implements all these interfaces has the next view:

C++
struct ABCimplementation:
        public BaseUnknown<
        IA, 
        IB, 
        IC
        >
{
};

Compiler will create the template BaseUnknown in the next form:

C++
    template<
        IA, 
        IB, 
        IC
        >
struct BaseUnknown:
        public IA, 
        public IB, 
        public IC
{

// Code

};

The main trick is done with the next code:

C++
return castInterfaces(
    aRefIID,
    aPtrPtrVoidObject,
    static_cast<Interfaces*>(this)...);
    
    Into:
    
return castInterfaces(
    aRefIID,
    aPtrPtrVoidObject,
    static_cast<(
        IA, 
        IB, 
        IC
        )
    *>(this)...);

In this code, the variadic template argument Interfaces which is a pack of regular template arguments has become a template argument of static_cast - static_cast is a TEMPLATE, but it can take only one regular template argument. Such problem is resolved by unpack signature ... which commands to compiler to take all packed regular template arguments from Interfaces and implements into the static_cast. However, template static_cast can take only one argument! The compiler instantiates for each regular template arguments from Interfaces the template static_cast and injects them into the variadic template method castInterfaces:

C++
return castInterfaces(
    aRefIID,
    aPtrPtrVoidObject,
    static_cast<IA*>(this),
    static_cast<IB*>(this),
    static_cast<IC*>(this)
    );

The next template methods process each of the new instantiated arguments in the recursion:

C++
template<typename Interface, typename... Args>
bool castInterfaces(
	REFIID aRefIID,
	_COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject,
	Interface* aThis,
	Args... aRest)
{
	bool lresult = castInterfaces(aRefIID, aPtrPtrVoidObject, aThis);

	if (!lresult)
	{
		lresult = castInterfaces(
			aRefIID,
			aPtrPtrVoidObject,
			aRest...);
	}

	return lresult;
}

template<typename Interface>
bool castInterfaces(
	REFIID aRefIID,
	_COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject,
	Interface* aThis)
{
	bool lresult = aRefIID == __uuidof(Interface);

	if (lresult)
	{
		*aPtrPtrVoidObject = aThis;

		if (*aPtrPtrVoidObject == nullptr)
			lresult = false;
	}

	return lresult;
}

This solution is easy than ATL COM Map and independent from that, and has TWO useful features:

  1. Easy include or exclude interface. For example:
    C++
                    struct ABCimplementation:
                            public BaseUnknown<
                            IA, 
    #if defined(Comercial)  IB, #endif
                            IC
                            >
    {
    };

    After changing of condition Commercial the macros exclude or include IB interface, and in the next compiling of COM object compiler will exclude or include that interface in QueryInterface method.

  2. Easy log information about wrong querying of interface. It allows to define only one point of logging such mistake for all COM objects in project.
    C++
    if (SUCCEEDED(lresult))
    {
        AddRef();
    }
    else
    {
        Log(lresult, __FUNCTIONW__, "Wrong IID: ", aRefIID);
    }
    

BaseDispatch:

Programming on Windows needs to use many primary interfaces for the effective implementation of code. One of those interfaces is IDispatch - COM interface for working with COM server in script languages as JavaScript or Python. I have developed template BaseDispatch which simplify implementation and managment calsses with IDispatch interface on C++ 11.

C++
extern ULONG gObjectCount;
    
template <typename... Interfaces>
    struct BaseDispatch:
		public BaseUnknown<
		Interfaces...>
    {
		BaseDispatch()
		{
			++gObjectCount;
		}
				
		// IDispatch interface stub

		STDMETHOD(GetTypeInfoCount)(
		/* [out] */ __RPC__out UINT *pctinfo){

			do
			{
				if (pctinfo != nullptr)
					*pctinfo = 0;

			} while (false);
			
			return S_OK;
		}

		STDMETHOD(GetTypeInfo)(
			/* [in] */ UINT iTInfo,
			/* [in] */ LCID lcid,
			/* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo){
			do
			{
				if (ppTInfo != nullptr)
					*ppTInfo = nullptr;

			} while (false);

			return S_OK;
		}

		STDMETHOD(GetIDsOfNames)(
			/* [in] */ __RPC__in REFIID riid,
			/* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
			/* [range][in] */ __RPC__in_range(0, 16384) UINT cNames,
			/* [in] */ LCID lcid,
			/* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId){
			return E_NOTIMPL;
		}

		STDMETHOD(Invoke)(
			/* [annotation][in] */
			_In_  DISPID dispIdMember,
			/* [annotation][in] */
			_In_  REFIID riid,
			/* [annotation][in] */
			_In_  LCID lcid,
			/* [annotation][in] */
			_In_  WORD wFlags,
			/* [annotation][out][in] */
			_In_  DISPPARAMS *pDispParams,
			/* [annotation][out] */
			_Out_opt_  VARIANT *pVarResult,
			/* [annotation][out] */
			_Out_opt_  EXCEPINFO *pExcepInfo,
			/* [annotation][out] */
			_Out_opt_  UINT *puArgErr){
			return E_NOTIMPL;
		}

	protected:

		virtual ~BaseDispatch()
		{
			--gObjectCount;
		}

#if defined(ENABLEDISPATCH)		

		virtual bool findInterface(
			REFIID aRefIID,
			_COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject)
		{
			if (aRefIID == IID_IDispatch)
			{
				return castToIDispatch(
					aPtrPtrVoidObject,
					static_cast<Interfaces*>(this)...);
			}
			else
			{
				return BaseUnknown::findInterface(
					aRefIID,
					aPtrPtrVoidObject);
			}
		}
#endif

	private:

		template<typename Interface, typename... Args>
		bool castToIDispatch(
			_COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject,
			Interface* aThis,
			Args... aRest)
		{
			return castToIDispatch(aPtrPtrVoidObject, aThis);
		}

		template<typename Interface>
		bool castToIDispatch(
			_COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject,
			Interface* aThis)
		{
			bool lresult = false;

			*aPtrPtrVoidObject = static_cast<IDispatch*>(aThis);

			if (*aPtrPtrVoidObject != nullptr)
				lresult = true;

			return lresult;
		}

};

This template has two parts:

  1. IDispatch interface stub 
  2. overloading virtual method findInterface 
  3. #if defined(ENABLEDISPATCH)		
    
    		virtual bool findInterface(
    			REFIID aRefIID,
    			_COM_Outptr_ void __RPC_FAR *__RPC_FAR *aPtrPtrVoidObject)
    		{
    			if (aRefIID == IID_IDispatch)
    			{
    				return castToIDispatch(
    					aPtrPtrVoidObject,
    					static_cast<Interfaces*>(this)...);
    			}
    			else
    			{
    				return BaseUnknown::findInterface(
    					aRefIID,
    					aPtrPtrVoidObject);
    			}
    		}
    #endif

This template allows to split development of dual COM objects (with static binding and IDispatch interface) on two steps: 

  1. Implementation of code for static binding with of IDispatch interface stub for normal compiling of COM objects with dual interfaces. In this case calling interface with IID_IDispatch will lead to error E_NOINTERFACE because base template BaseUnknown has information only about original static binding interface - and it is OK, because in this step all efforts are concentrated on development of base logic of the COM object.
  2. Define macros ENABLEDISPATCH for overloading of virtual method findInterface in BaseDispatch template. It leads to including of interface with IID_IDispatch into calling of QueryInterface method of base IUnknown interface. As a result, it will posible to get IDispatch interface from the COM object. Of course, COM object MUST implement IDispatch interface for replacing of IDispatch interface stub which is defined in BaseDispatch template.       

This template allows postpone development of IDispatch interface features, build they on the basement of implementation of the static binding interface and create simple switcher for enabling or disabling IDispatch interface features for different versions of software solution (for example freeware and commercial versions).

 

Points of Interest

This solution presents a useful feature of variadic template technique - instantiating interfaces of object and enumerating all of them - it allows to get information about structure of object in the runtime.

History

  • 19th November, 2015: Initial version
  • 21st March, 2016: Update version with adding code for  IDispatch interface

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHandling complex interfaces Pin
waleri5-Apr-16 3:47
waleri5-Apr-16 3:47 
AnswerRe: Handling complex interfaces Pin
Evgeny Pereguda5-Apr-16 4:11
Evgeny Pereguda5-Apr-16 4:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.