ATL features a set of macros for so-called COM maps. COM map is a table that associates an interface GUID with an offset that is to be added to this
pointer to get to the corresponding subobject - the whole stuff works as replacement to explicit static_cast
for the upcast inside IUnknown::QueryInterface()
.
The map entries are built by using offsetofclass
macro:
#define _ATL_PACKING 8
#define offsetofclass(base, derived)\
((DWORD_PTR)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)
which I'll rewrite as the following pseudocode "function" for readability in this question:
derived* derivedPointer = (derived*)_ATL_PACKING;
base* basePointer = static_cast<base*>(derivedPointer);
DWORD_PTR offset = (DWORD_PTR)(basePointer)-_ATL_PACKING;
Looks reasonable - it obtains a pointer to an imaginary derived object, then does an explicit static_cast
to shift the pointer, then computes distance between those imaginary objects.
The question is why is the constant 8 there? Why do we need that constant and why is it chosen to be 8?
The non-zero constant is there because the macro does not work with null pointers. We know that the value of a null pointer is a null pointer constant which evaluates to
0
:This is the relevant clause with respect to converting from a derived class to a base class pointer type:
Basically null pointers prevent pointer arithmetic from kicking in during a derived-to-base conversion; you will just get another null pointer. The arithmetic is used to "fix" non-null pointers during such conversions so that it points to the proper subobject. The
offsetofclass
macro depends on this arithmetic to determine the offset.The number
8
used is arbitrary. You could've used any number there like1
or4
, as long as it wasn't zero.