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:
template <typename... Interfaces>
struct BaseUnknown:
public Interfaces...
{
BaseUnknown() :
mRefCount(1){}
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:
struct IA: public IUnknown
{};
struct IB: public IUnknown
{};
struct IC: public IUnknown
{};
Defining COM object which implements all these interfaces has the next view:
struct ABCimplementation:
public BaseUnknown<
IA,
IB,
IC
>
{
};
Compiler will create the template BaseUnknown
in the next form:
template<
IA,
IB,
IC
>
struct BaseUnknown:
public IA,
public IB,
public IC
{
};
The main trick is done with the next code:
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
:
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:
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:
- Easy include or exclude interface. For example:
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.
- 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.
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.
extern ULONG gObjectCount;
template <typename... Interfaces>
struct BaseDispatch:
public BaseUnknown<
Interfaces...>
{
BaseDispatch()
{
++gObjectCount;
}
STDMETHOD(GetTypeInfoCount)(
__RPC__out UINT *pctinfo){
do
{
if (pctinfo != nullptr)
*pctinfo = 0;
} while (false);
return S_OK;
}
STDMETHOD(GetTypeInfo)(
UINT iTInfo,
LCID lcid,
__RPC__deref_out_opt ITypeInfo **ppTInfo){
do
{
if (ppTInfo != nullptr)
*ppTInfo = nullptr;
} while (false);
return S_OK;
}
STDMETHOD(GetIDsOfNames)(
__RPC__in REFIID riid,
__RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
__RPC__in_range(0, 16384) UINT cNames,
LCID lcid,
__RPC__out_ecount_full(cNames) DISPID *rgDispId){
return E_NOTIMPL;
}
STDMETHOD(Invoke)(
_In_ DISPID dispIdMember,
_In_ REFIID riid,
_In_ LCID lcid,
_In_ WORD wFlags,
_In_ DISPPARAMS *pDispParams,
_Out_opt_ VARIANT *pVarResult,
_Out_opt_ EXCEPINFO *pExcepInfo,
_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:
IDispatch
interface stub
- overloading virtual method
findInterface
-
#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:
- 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.
- 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