Setting up a simple visualizer for the Visual Studio debugger is all very well but sometimes it’s easier to use something other than the simple language that autoexp.dat exposes.

For instance it would be feasible to write a visualizer to turn something that turned an integer into the human readable version of the OLE date it represents. But it would be quite painful.

The lo-tech way to handle this to load Excel, type in the value and then format as a date. That is a pain. Looking at our internal development wiki shows three utilities that also do this with various user interfaces. But wouldn’t it be nicer to be able to see the value directly within the debugger.

Given the extension available for the Visual Studio debugger and WinDBG it would be surprising if this was impossible, what’s annoying is how scantily the information on doing this is made available.

Most of the information is buried in the EEAddin sample in C:\Program Files\Microsoft Visual Studio 9.0\Samples\1033\AllVCLanguageSamples.zip. Within that archive the sample is at C++\Debugging\EEaddin.

There’s two parts to writing these visualizers. The first is writing the extension dll in C or C++, the second updating AutoExp.dat to use it.

Writing the code

The visualizer lives in a standard Windows dll and have a specific function signature. So for our example we define our visualization function.

#ifdef __cplusplus
extern "C" {
#endif

MYDBGVIEW_API HRESULT WINAPI oleDate(DWORD dwAddress, 
                                     DEBUGHELPER *pHelper, 
                                     int nBase, 
                                     BOOL bUniStrings,
                                     char *pResult,
                                     size_t max, 
                                     DWORD reserved);
#ifdef __cplusplus
} // extern "C"
#endif

Where the documentation for the arguments is:

dwAddress The address of the variable being inspected.
pHelper Pointer to a helper structure for accessing debugger information.
nBase Base to display any numeric values in.
bUniStrings Whether to use unicode strings (can't generally be ignored).
pResults Buffer to write the representation into.
max Size of the pResults buffer
reserved Reserved for future use, can be ignored.

(Note the WINAPI definition which ensures that the function uses the __stdcall convention. The examples in EEAddin don’t seem to do this but it is required otherwise you will cause access violations inside Visual Studio.)

namespace
{
    template<typename T> HRESULT get_type(DEBUGHELPER *pHelper, 
                                                DWORD dwAddress,
                                                T& value)
    {
        DWORDLONG qwRealAddress;
        DWORD dwGot;

        HRESULT hr = S_OK;
        if (pHelper->dwVersion < 0x20000)
        {
           // Visual C++ 6.0 version
           qwRealAddress = dwAddress;
           hr = pHelper->ReadDebuggeeMemory(pHelper, dwAddress,
                                            sizeof(T), &value, &dwGot);
        }
        else
        {
           qwRealAddress = pHelper->GetRealAddress(pHelper);
           hr = pHelper->ReadDebuggeeMemoryEx(pHelper, qwRealAddress,
                                              sizeof(T), &value, &dwGot);
        }
        
        if (hr != S_OK)
            return E_FAIL;
        if (sizeof(T) != dwGot)
            return E_FAIL;

        return S_OK;
    }
}

MyDBGVIEW_API HRESULT WINAPI oleDate(DWORD dwAddress, 
                                     DEBUGHELPER *pHelper, 
                                     int nBase, 
                                     BOOL bUniStrings,
                                     char *pResult,
                                     size_t max, 
                                     DWORD reserved)
{
    COleDateTime dt;
    
    HRESULT hr = get_type(pHelper, dwAddress, dt);
    if (SUCCEEDED(hr))
    {
        CString str = dt.Format();
        strncpy_s(pResult, max, str.GetString(), str.GetLength());
        pResult[max-1] = 0;
    }
    return hr;
}

Finally you’ll want to an a module definition file to the DLL to ensure that name that’s exported is easily referenced.

EXPORTS
	oleDate

Updating AutoExp.dat

With the code built the final step is update Studio’s AutoExp.dat file (in the Common7\Packages\Debugger directory of the installation) to point to the DLL and function name that you want to use for that type.

ATL::COleDateTime=$ADDIN(D:\Code\mydbgview\Release\mydbgview.dll,oleDate)

The Result

So what do we get out of all of this? Well without the visualizer if you inspect a COleDateTime structure in the debugger you get something like this:

Debugger view before

With the visualizer add this becomes:

Debugger view after

Further Information

Josh Poley’s MSDN blog article “Custom Debugger Auto Expansion Tips” is the only worthwhile article I’ve found on this stuff.