Visualizing MFC Containers in autoexp.dat

MFC containers are more or less officially deprecated in favor of STL. Even so, when navigating in legacy code the need often arises to watch CArrays, CLists, CMaps and the like. autoexp.dat provides only STL visualizers out of the box, but you can just paste the lines below into autoexp’s [Visualizer] section, and have a similar debugging experience with MFC code:

[Visualizer]
; This section contains visualizers for STL and ATL containers
; DO NOT MODIFY     (HAAAAAAAAA. -o.s.)

...

;---------------------------------------------------------------------
;  MFC Types
;---------------------------------------------------------------------
CArray<*,*> |CObArray|CByteArray|CDWordArray|CPtrArray|CStringArray|CWordArray|CUIntArray|CTypedPtrArray<*,*>{
 preview([$c,!])
 children(
            #(
              #array (
                  expr: $c.m_pData[$i],
                  size: $c.m_nSize
                     )
             )
         )
}

CList<*,*>|CObList|CPtrList|CStringList|CTypedPtrList<*,*>{
 preview([$c,!])
 children(
          #(
              #list  (
                  head: $c.m_pNodeHead,
                  next: pNext
                     ) : $e.data
               )
            )
}

CMap<*,*,*,*>::CAssoc|CMapPtrToWord::CAssoc|CMapPtrToPtr::CAssoc|CMapStringToOb::CAssoc|CMapStringToPtr::CAssoc|CMapStringToString::CAssoc|CMapWordToOb::CAssoc|CMapWordToPtr::CAssoc|CTypedPtrMap<*,*,*>::CAssoc{
preview(#("key= ",$e.key,", value= ", $e.value))
}

CMap<*,*,*,*>|CMapPtrToWord|CMapPtrToPtr|CMapStringToOb|CMapStringToPtr|CMapStringToString|CMapWordToOb|CMapWordToPtr|CTypedPtrMap<*,*,*>{
children (
    #(
        #if ($c.m_nHashTableSize >= 0 && $c.m_nHashTableSize <= 65535) (
            #array (
                expr : ($c.m_pHashTable)[$i],
                size : $c.m_nHashTableSize
                   ) : #list(
                             head : $e,
                             next : pNext
                            ) : $e
            ) #else (
             #(  __ERROR – Hash table too large!!!__: 1,Table size: $c.m_nHashTableSize)
         )
       )
)
}

[EDIT: CAssoc visualizer fix, thanks to @Gerald].

[EDIT: CMap visualizer fix, thanks to @avek]

Here’s what you’d get:

I’m aware of the formatting issues in the snippet above, but the autoexp parser is notoriously fragile and I didn’t want to risk extra spaces for proper line-wrapping.

And btw, unlike Avery (the original autoexp Jedi), I prefer to avoid cluttering the visualizers with ‘raw’ watch entries.  If you ever need to watch into, say, raw members of a CList, just postfix the variable with ‘,!’ , as in:

Coding Binary as Binary

If you’re comfortable with hex, you have no business in this post.  If on the other hand, when faced with a mask like 0xFFA8 you’re forced – like me – to translate it on paper into 11111111 10101000,  then by all means, read on.

Being the hex-challenged (hexicapped?) that I am, I badly need to somehow express 0x7F800000 as (0111 1111 1000 0000 0000 0000 0000 0000). With some nerve, I’m not willing to sacrifice any run-time performance to get there.  Good news are, this can be done. Better news are, it involves some neat preprocessor and metaprogramming tricks!

C++ Template Metaprogramming gives an excellent starting point:

template <unsigned long N>
struct binary
{
   static unsigned const value
     = binary<N/10>::value << 1   // prepend higher bits
       | N%10;                    // to lowest bit
};
template <>                           // specialization
struct binary<0>                      // terminates recursion
{
static unsigned const value = 0;
};
// usage:
unsigned const five  =  binary<101>::value;

This trick of recursive template instantiation is already considered standard metaprogramming, and will not be covered here. Sadly, an attempt to apply it directly to real-life constants -

unsigned const mask  =  binary<01111111100000000000000000000000>::value;

Fails miserably. A decimal literal like 1111111100000000000000000000000 goes way above anything the tokenizer can interpret as an integer. In fact, the largest such constant is 18446744073709551615LL,  which still is 11 orders of magnitude too low.

Ok then, that one is easy: let’s break the input into 4 arguments:

unsigned const mask  =  binary4<01111111, 10000000, 00000000, 00000000>::value;

We can apply the template recursion on each argument separately, and eventually shift the four results and push them into a single DWORD.  Why oh why then, do we still get ridiculous failures??

Alas, the real fun begins: a leading zero is interpreted as an octal prefix! An argument like 01111111 is interpreted as the decimal 299593 – and the template recursion breaks entirely.

What if we create a separate template recursion, specifically for octal numbers? Doesn’t seem so hard, just replace N%10 with N%8 in the code. But wait… how can we know whether an argument was originally interpreted from an octal literal (01111111) or a decimal (1111111)?

Pushing forward – let’s take the less-than-elegant assumption that each argument is exactly 8 binary digits. The highest number to be interpreted as octal in this setting is 01111111, which is 299593 in decimal.  We can just compare: any argument equal to or below 299593 is subject to an octal template recursion, and any argument above – to a decimal one.

Ummm, wait. we need to perform that comparison, and branch on its result at compile time.

This is in fact possible, and the given reference is an excellent source for such tricks (hints: grok boost::mpl::bool_, and trade specialization for overloading). Anyway, for me personally, this is also where the fun stops. A much dumber approach is in order, and luckily – one exists. We can pre-process the input to pad 1 to its left, and subtract 100000000 in the code.

#define PAD1(N) (1##N)
#define BINARY32(N1, N2, N3, N4)   binary4<PAD1(N1), PAD1(N2), PAD1(N3), PAD1(N4)>::value

template<DWORD N>
struct binary_Unpad
{
	// for now assume input is 1 + 8 binary digits
	static const DWORD Unpadded = N - 100000000,
			value = binary<Unpadded>::value; // back to classic recursion
};

template <DWORD N1, DWORD N2, DWORD N3, DWORD N4>
struct binary4
{
	static const DWORD val1 = binary_Unpad<N1>::value,
			val2 = binary_Unpad<N2>::value,
			val3 = binary_Unpad<N3>::value,
			val4 = binary_Unpad<N4>::value,

			value = ( (val1 << 24) |
						(val2 << 16) |
						(val3 << 8)  |
						(val4) ) ;
};

It is also possible to assert on input digits being 0/1, and on being exactly 8 such digits. Personally, I was very happy at this point, to just be able to write-

#define  FLOAT_EXP_MASK     BINARY32(01111111, 10000000, 00000000, 00000000)

Some day I’ll write about viewing binary as binary – more autoexp stuff coming up there.