The Pitch

I don’t want to show you how to add a command button or toolbar to Visual Studio, there are plenty of examples out there for that. I want to provide you with an example showing how writing a Visual Studio extension could benefit your studio’s workflow.

Andy Firth wrote an article on #AltDevBlogADay a few days ago on Extending the Watch Window in Visual Studio via autoexp.dat. It’s a nice introduction article to modifying the autoexp.dat file to auto-expand and format variables in the watch window.

Now, if I take a bunch of time designing a nice formatter to speed up my debugging sessions how do I share it across the company?

Well, the only way is for you to manually merge other peoples additions to the AutoExp.dat file into your own. Which inevitably leads to varying levels of adoption of the formatters and debugging times suffer as a result.

Lets fix this!

We’re going to write an extension to patch the AutoExp.dat file with one collocated with the solution file.

 

Getting Started

new_vs_packageAfter downloading the Visual Studio 2010 SP1 SDK  you’ll create a new solution using the Visual Studio Package template.  You could have also chosen the Add-in template.  Though the packages are basically just as easy to install in Visual Studio 2010 and they offer much more control should you end up needing it.

The code you’ll start out with will be a bit more verbose than this, but here’s the important bit to get started playing with different capabilities.  Visual Studio is based on a loose coupling service architecture, so to accomplish anything useful you’ll need to find the the service that handles it.  Luckily Microsoft has documented the List of Services.  If I had to pick the “main” service it would be the DTE service.  It manages the majority of the functionally you’ll be interested in playing with.

public sealed class MyPackage : Package
{
    private DTE m_dte;
 
    protected override void Initialize()
    {
        // Your package is also a service container
        IServiceContainer serviceContainer = this as IServiceContainer;
        // The DTE object contains most of the goodies you'll want to play with
        m_dte = serviceContainer.GetService(typeof(SDTE)) as DTE;
        // I could also add a service of my own if I wanted
        // serviceContainer.AddService(typeof(MyService), new MyService(), true);
    }
}

 

The AutoExp.dat Patcher

Because we’ve decided to look for an AutoExp.dat patcher file beside the solution, lets hook some events so that we know when the solution is opened and closed.

// Must hold a reference to the solution events object or the events wont 
// fire, garbage collection related
m_solutionEvents = m_dte.Events.SolutionEvents;
m_solutionEvents.Opened += SolutionOpened;
m_solutionEvents.AfterClosing += SolutionAfterClosing;
 
// Most of the events you'll find useful are catagoried under 
// DTE.Events...

Now when the solution is opened we’re going to scan for the patcher file in the same directory as the solution file and we’re going to use it to modify the AutoExp.dat file.

void SolutionOpened()
{
    string slnFile = m_dte.Solution.FullName;
    string solutionDirectory = Path.GetDirectoryName(slnFile);
    string pathfileName = Path.GetFileNameWithoutExtension(slnFile) + ".autoexp";
    string patchFile = Path.Combine(solutionDirectory, pathfileName);
    if (File.Exists(patchFile))
    {
        string autoExpPatch = File.ReadAllText(patchFile);
        string autoExpPath = Environment.ExpandEnvironmentVariables(
            "%VS100COMNTOOLS%\\..\\Packages\\Debugger\\autoexp.dat");
 
        string blockBegin = "; BEGIN AEP [AutoExpand] (" + pathfileName + ")";
        string blockEnd = "; END AEP [AutoExpand] (" + pathfileName + ")";
 
        // What follows is a bunch string parsing code that's not relevant to the article.
        // (1) We remove the previous auto expansion block we added, if we added one 
        //     in another session.
        // (2) We insert the new auto expansion patched section from our 
        //     SOLUTION_FILE_NAME.autoexp file.
        // (3) Save the new AutoExp.dat file.
    }
}

Because our extension isn’t triggered by a command and doesn’t have a UI we need to make sure Visual Studio loads our extension from the start.  You accomplish this by adding the ProvideAutoLoad attribute to the Package class, with the right GUID identifying the time to load the extension.

// --- Microsoft.VisualStudio.VSConstants.UICONTEXT_NoSolution
[ProvideAutoLoad("ADFC4E64-0397-11D1-9F4E-00A0C911004F")]
public sealed class AutoExpPatchPackage : Package
{
   //...
}

 

Results

In our test solution I’m going to start without any patch file and this is what I see when hovering over the test variable.

before_autoexppatch

After we add the patch file though and reload the solution we see something completely different.

[AutoExpand]
 
TestStruct =x=<x,g>, y=<y,g>, z=<z,g>

after_autoexppatch

Voilà we’ve created a simple extension to Visual Studio that could actually improve workflow.  We can now check-in our patcher file into source control, allowing us to share our rules with all of our fellow employees.

The extension I wrote only handles the [AutoExpand] section of the AutoExp.dat file.  With a little bit of work though you could easily handle the other sections.

You could also add some other improvements such as removing the patched portions after the solution is closed to make sure there are no conflicts between versions, if you work on multiple versions of the codebase.

Another improvement would be to automatically re-patch the AutoExp.dat file if you detect a change to the patcher file while the solution is open.

 

Download

You can download the source code for the AutoExp.dat Patcher here.

 

Resources

Extend Visual Studio! @ nickdarnell.com