I have a type which represents "Battleship" coordinates:
struct BattleshipCoordinates
{
int row; // zero-based row offset
int col; // zero-based column offset
}
Note that the coordinates are stored natively as zero-based index offsets. I would like to display these in the debugger in a more 'natural' view for Battleship coordinates (i.e. when the structure contains {0, 0} I would like the debugger to display "A1" for the upper left corner). I would like to accomplish this custom formatting with a .natvis file.
I am able to translate the values to their respective chars ('A' and '1'), but the debugger displays them in an offputting format with extra formatting:
<Type Name="BattleshipCoordinates">
<DisplayString>{(char)(row + 'A')}{(char)(col + '1')}</DisplayString>
</Type>
There are a number of issues with this approach; the current result for {0,0} is displayed in the debugger as 65'A'49'1'. I would like to remove the extra formatting (numbers and quotation marks) and just display simply "A1". Additionally, that syntax would break as soon as the column reaches double-digit values.
What secret sauce am I missing? Is there some method through which I can stream together multiple values?
If I could access stringstreams, I could just use: ostr << static_cast<char>(row + 'A') << (col + 1). If I could call one of the available to_string functions in my code, that would also work; but to my knowledge, none of that is available in natvis syntax...
It turns out this sort of thing is possible, provided that you can include some character tables in your program. This is because the natvis format [1]nasb can be used to print any byte in program memory as a single character. In fact it's the only way to print single characters. This cryptic format code means:
[1] interpret pointer as an array with 1 element
na don't print the address of the pointer, regardless of debugger settings
sb interpret pointer as a C-string that can be null-terminated
If you need to generate arbitrary ASCII, you must put a table representing all 128 ASCII characters into your program — and take measures to ensure it's not removed by the linker's 'unused data' optimization. Here I've substituted ? for various unprintable characters:
constexpr char ALPH_ASCII[129] = // 128 ASCII characters + null terminator
"\0???????????????????????????????"
" !\"#$%&'()*+,-./0123456789:;<=>?@"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
"abcdefghijklmnopqrstuvwxyz{|}~?";
Then we can offset our table pointer by any integer or char we like in debugger expressions. Here's an example expression for printing a number represented by (bool) sign and (unsigned) magnitude:
struct my_ones_complement {bool my_sign_bit; unsigned my_magnitude;};
{ALPH_ASCII+((my_sign_bit?'-':'+'),[1]nasb}{my_magnitude,d}
For a more involved example, suppose you want to display an integer value as base64. Start by putting a table of all 64 digits into your program.
constexpr char ALPH_B64U[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
struct ID_60Bit {uint64_t value;}
Then, in your natvis file (inside AutoVisualizer but outside any Type) you can add an intrinsic to convert the bottom six bits of any number to a pointer to one of these base64 digits. We'll call it D64, short for for "Digit in Base 64".
<Intrinsic Name="D64" Expression="ALPH_B64U+(val&63)">
<Parameter Name="val" Type="uint64_t"/></Intrinsic>
<Intrinsic Name="CD64" Expression="ALPH_B64U+(val?(val&63):64)">
<Parameter Name="val" Type="uint64_t"/></Intrinsic>
The second intrinsic CD64, where C stands for "conditional", will offset our alphabet pointer by 64 if the argument is zero. The 64th character in our alphabet is a null terminator, which won't print anything. This trick is useful for making some characters print conditionally without making a new tag.
Now, we can visualize our 60-bit ID into its base64 representation, e.g. #Gu4+8z. If the value is relatively small, the first few calls to CD64 will get an argument of zero and won't print anything. Base64 uses A as its 'zero' character, so this trims the leading As from an ID like #AAAAGu4+8z.
<Type Name="ID_60Bit">
<!-- Print as 1 to 10 digits of Base64 -->
<DisplayString>
#{CD64((value)>>54),[1]nasb}{CD64((value)>>48),[1]nasb
}{CD64((value)>>42),[1]nasb}{CD64((value)>>36),[1]nasb
}{CD64((value)>>30),[1]nasb}{CD64((value)>>24),[1]nasb
}{CD64((value)>>18),[1]nasb}{CD64((value)>>12),[1]nasb
}{CD64((value)>>6 ),[1]nasb}{ D64((value) ),[1]nasb}</DisplayString>
</Type>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With