Tuesday, October 15, 2019

COM VARIANT nameless union discrimination

Aficionados of COM may remember - because it's been a while, hey? - that the VARIANT structure is a composite of nested union and struct aggregates, as in:

typedef struct tagVARIANT { union { struct { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONGLONG llVal; LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal;
. . . // and lots more members USHORT *puiVal; ULONG *pulVal; ULONGLONG *pullVal; INT *pintVal; UINT *puintVal; struct { PVOID pvRecord; IRecordInfo *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; } VARIANT;

(The full definition is given in https://docs.microsoft.com/en-us/windows/win32/api/oaidl/ns-oaidl-variant.)

The relevant aspect(s) for this post is the apparent use of member names for the anonymous struct/union aggregates - __VARIANT_NAME_1, __VARIANT_NAME_2, etc. They are present because anonymous aggregates are not standard C++ and, prior to C11, were not part of standard C, even though they are commonly supported on many compilers: on supporting compilers, the apparent names are #defined to nothing; on non-supporting compilers they are #defined to, respectively, n1, n2, n3, brecVal, as in:


#if ... some condition ...
# define __VARIANT_NAME_1 n1
# define __VARIANT_NAME_2 n2
# define __VARIANT_NAME_3 n3
# define __VARIANT_NAME_4 brecVal
#else
# define __VARIANT_NAME_1
# define __VARIANT_NAME_2
# define __VARIANT_NAME_3
# define __VARIANT_NAME_4
#endif

This is all fine and well, except when you're writing COM code to work in C++ as well as in C, as is done for various components within the COMSTL project, as in the comstl_C_DECIMAL_compare() function (in comstl/util/DECIMAL_functions.h):

int
comstl_C_DECIMAL_compare(
    DECIMAL const* lhs
,   DECIMAL const* rhs
) /* noexcept */
{
  . . .

    COMSTL_ACCESS_VARIANT_vt_BYPTR(&vdecL) = VT_DECIMAL;
    COMSTL_ACCESS_VARIANT_decVal_BYPTR(&vdecL) = *lhs;

  . . .
}


In order to make it compile for both C and C++, the function is implemented in terms of COMSTL macros such as COMSTL_ACCESS_VARIANT_vt_BYREF(), which is defined as:

#if defined(COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_)

# define COMSTL_ACCESS_VARIANT_vt_BYPTR(pvar) \
           (pvar)->__VARIANT_NAME_1.__VARIANT_NAME_2.vt
#else

# define COMSTL_ACCESS_VARIANT_vt_BYPTR(pvar) (pvar)->vt
#endif

If the (internal / undocumented) COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_ macro is defined, then COMSTL_ACCESS_VARIANT_vt_BYPTR() obtains the vt member via the full retinue of named members; if not, then it obtains vt directly.

So, the question is, how is the COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_ macro discriminated?

It is defined in comstl/comstl.h, and up to STLSoft 1.10.1 beta-16, it was done as:

#if !defined(COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES) && \
    defined(_FORCENAMELESSUNION) && \
    !defined(NONAMELESSUNION)

# define COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES
#endif

#if defined(COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES)

# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
#elif defined(STLSOFT_COMPILER_IS_GCC)

   /* GCC has different definitions to the other compilers, so have to treat
    * differently
    */
# if defined(NONAMELESSUNION)

#  define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# endif /* NONAMELESSUNION */
#else /* ? compiler */

   /* Other compilers use the MS headers, which test against __STDC__,
    * _FORCENAMELESSUNION and NONAMELESSUNION
    */
# if (  __STDC__ && \
        !defined(_FORCENAMELESSUNION)) || \
     defined(NONAMELESSUNION)

#  define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# endif /* (  __STDC__ && !_FORCENAMELESSUNION) || NONAMELESSUNION */
#endif /* compiler */

However, when compiling for MinGW GCC 8.1 today, this no longer suffices.

Searching the web for _FORCENAMELESSUNION and NONAMELESSUNION yielded little, so I did a thorough search of the implementation headers for a bunch of compilers and the Windows SDK, in order to get a (more) complete and up-to-date definition, arriving at the following - hopefully final - definition, which will appear in STLSoft 1.10.1 beta-17:

#if 1 && \
    !defined(COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES) && \
    !defined(_FORCENAMELESSUNION) && \
    defined(NONAMELESSUNION) && \
    1

# define COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES
#endif


#if 0
#elif defined(_FORCENAMELESSUNION)

#elif defined(COMSTL_ASSUME_VARIANT_UNION_FORCE_ARMS_HAVE_NAMES)

# define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
#else

 /* The observed extant discriminations are:
  *
  * 1 (VC++ 6):
  *
  *  (__STDC__ && !defined(_FORCENAMELESSUNION)) || defined(NONAMELESSUNION)
  *
  * 2 (*):
  *
  *  (__STDC__ && !defined(_FORCENAMELESSUNION)) || defined(NONAMELESSUNION) || (!defined(_MSC_EXTENSIONS) && !defined(_FORCENAMELESSUNION))
  *
  * 3 (MinGW GCC 4.9):
  *
  *  NONAMELESSUNION
  *  
  * 4 (MinGW GCC 8.1):
  *
  *  (__STDC__ && !defined(__cplusplus) && !defined(_FORCENAMELESSUNION)) || defined(NONAMELESSUNION) || (defined (_MSC_VER) && !defined(_MSC_EXTENSIONS) && !defined(_FORCENAMELESSUNION))
  *
  * which may be better understood as:
  *
  * 1 (VC++ 6):

    0 ||

    defined(NONAMELESSUNION) ||

    (  __STDC__ && \
        !defined(_FORCENAMELESSUNION)) ||
    
    0
  *
  * 2 (*):

    0 ||

    defined(NONAMELESSUNION) ||

    (   __STDC__ &&
        !defined(_FORCENAMELESSUNION)) ||

    (   !defined(_MSC_EXTENSIONS) &&
        !defined(_FORCENAMELESSUNION)) ||

    0
  *
  * 3 (MinGW GCC 4.9):

    0 ||

    defined(NONAMELESSUNION) ||

    0

  * 4 (MinGW GCC 8.1):


    0 ||

    defined(NONAMELESSUNION) ||

    (   __STDC__ &&
        !defined(__cplusplus) &&
        !defined(_FORCENAMELESSUNION)) ||

    (   defined (_MSC_VER) &&
        !defined(_MSC_EXTENSIONS) &&
        !defined(_FORCENAMELESSUNION)) ||

    0
  */

# if 0
# elif defined(NONAMELESSUNION)

#  define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# elif defined(STLSOFT_COMPILER_IS_GCC)

#  if STLSOFT_GCC_VER >= 80000 /* NOTE: this number may be wrong - too large, but still old way with 4.9 */

#   if 0
#   elif __STDC__ && \
         !defined(__cplusplus) && \
         !defined(_FORCENAMELESSUNION)

#    define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
#   elif defined(_MSC_VER) && \
         !defined(_MSC_EXTENSIONS) && \
         !defined(_FORCENAMELESSUNION)

#    define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
#   endif /* ? GCC version */
#  endif
# elif __STDC__ && \
       defined(_FORCENAMELESSUNION)

#  define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# elif !defined(_MSC_EXTENSIONS) && \
       !defined(_FORCENAMELESSUNION)

#  define COMSTL_VARIANT_UNION_ARMS_HAVE_NAMES_
# endif
#endif

If anyone else needs to understand _FORCENAMELESSUNION and NONAMELESSUNION, I hope this post can help.

No comments: