ATL provides a bunch of macros for creating so-called COM maps – chains of rules of how the QueryInterface() call behaves on a given object. The map begins with BEGIN_COM_MAP and ends with END_COM_MAP. In between the the following can be used (among others):
- COM_INTERFACE_ENTRY, COM_INTERFACE_ENTRY2 – to ask C++ to simply cast this class to the corresponding COM interface
- COM_INTERFACE_ENTRY_FUNC – to ask C++ to call a function that will retrieve the interface
Now the problem is I want to use COM_INTERFACE_ENTRY_FUNC for every interface I expose so that I can log all the calls – I believe it will help me debugging my component when it is deployed in the field. The implementation of CComObjectRootBase::InternalQueryInterface contains an ATLASSERT:
ATLASSERT(pEntries->pFunc == _ATL_SIMPLEMAPENTRY);
which implies that the following is allright:
BEGIN_COM_MAP
COM_INTERFACE_ENTRY( IMyInterface1 )
COM_INTERFACE_ENTRY_FUNC( __uuidof(IMyInterface2), 0, OnQueryMyInterface2 )
END_COM_MAP
since here the first entry results in _ATL_SIMPLEMAPENTRY type entry but the following is not:
BEGIN_COM_MAP
COM_INTERFACE_ENTRY_FUNC( __uuidof(IMyInterface1), 0, OnQueryMyInterface1 )
COM_INTERFACE_ENTRY_FUNC( __uuidof(IMyInterface2), 0, OnQueryMyInterface2 )
END_COM_MAP
since here the entry type will not be _ATL_SIMPLEMAPENTRY.
This makes no sense at all. Why am I enforced into having a “please, C++, do the static_cast” entry as the first entry of the COM map?
Upd: Resolved after many more hour of debugging, answer added.
Inside ATL there’s AtlInternalQueryInterface() that actually does scan the COM map. Inside it there’s this code:
this code actually relies on the first entry of the table being of _ATL_SIMPLEMAPENTRY type since it expects that _ATL_INTMAP_ENTRY::dw stores an offset from the current object this pointer to the necessary interface. In the use cited in the question if the first entry is this one:
the entry will be of wrong type, but the _ATL_INTMAP_ENTRY::dw will be zero (second parameter to the macro) and the code will happily work each time returning this pointer as IUnknown*. But if the second macro parameter which corresponds to a pass this value into the function specified as the third parameter variable is not zero the program will use that value as the offset and could run into undefined behaviour.