Quantcast
Channel: Moving Forward » Dot Net
Viewing all articles
Browse latest Browse all 2

Building Audio Assist

$
0
0

Audio Assist is a fun, silly plugin for Visual Studio. I’ve had a number of people ask me to open source the code, and maybe explain a little bit of how it’s put together. Despite my reservations about giving away proprietary secrets, I’ve decided to do just that. You can now find it on my GitHub account. While extending Visual Studio isn’t the hardest thing on earth (mostly tedious), there’s a couple gotchas that are worth mentioning.

Getting Started

To begin developing Visual Studio extensions you’ll need the Visual Studio SDK. I based Audio Assist off a publicly available code sample that might be worth taking a look at as well. In the example they additionally add a menu item for the Add-In. My code is a greatly simplified version of that example, which will make it easier for someone just getting started to understand.

It’s COM All the Way Down

Visual Studio uses a complex COM model to allow for extensibility. Microsoft has been nice enough to provide managed assemblies to wrap these COM components for us, making it easy to extend Visual Studio from managed code. Audio Assist is built as an Add-In for Visual Studio, which turns out to be just a COM-visible managed assembly, bundled with a manifest file that gives Visual Studio the namespace and class name to load, and controls how and when the extension is loaded. The class specified in the Manifest implements the IDTExtensibility2 interface, which allows it to receive notification of Add-In specific events, such as loading and unloading, and bind to IDE events.

Part of the interface is the OnConnection method, which fires when the Add-In is loaded, and gives it a handle to the general Visual Studio root extensibility object:

        public void OnConnection(object application,
            ext_ConnectMode connectMode,
            object addInInst,
            ref Array custom)
        {
            _applicationObject = (DTE2)application;
            _addInInstance = (AddIn)addInInst;
        }

The _applicationObject variable contains the DTE2-typed application reference. The type of this reference changes with versions of Visual Studio, with the object supporting all previous iterations of the DTE interface for backwards compatibility, while implementing potentially new versions to provide for new features. From this object we can get references to the various commands:


        public void OnStartupComplete(ref Array custom)
        {
            // Upon VS successfully starting up we add all the
            // event subscriptions neccessary to make AudioAssist work.
            AddSubscription();
        }

        private CommandEvents _breakpointsEvents;

        public void AddSubscription()
        {
            try
            {

               _breakpointsEvents
                    = _applicationObject.Events.get_CommandEvents(
                        "{5EFC7975-14BC-11CF-9B2B-00AA00573819}",
                        769);
                _breakpointsEvents.BeforeExecute
                    += breakpointsEvents_BeforeExecute;
                // ... and so on
            }
            catch (Exception e)
            {
                System.Windows.Forms.MessageBox.Show(e.Message);
            }
        }

In Visual Studio almost all button presses correspond to a command behind the scenes. These commands are identified by GUID and command ID pairs. To capture these values there’s a trick to enable a popup dialog upon performing a command action. We use a command on the DTE object that gets an instance of the CommandEvents object for that command.

Things get a little odd here. It’s not immediately obvious why we create a private member variable to hold a reference to the CommandEvents object returned from the get_CommandEvents method. The fact of the matter is if we don’t do this we’ll experience some really weird behavior, with button command events never triggering. Because the object returned is a COM-wrapped object, and because no other managed object holds a reference to it, when garbage collection runs it will be disposed of. Objects that have no references, even if they are holding on to unmanaged resources, look abandoned to the garbage collector. To avoid collection we simply hold onto a reference to these objects for the lifetime of our Add-In and life is good.

We attach event handlers to the BeforeExecute events of the commands we’re interested in, implement, and finally play the sound. All of this is pretty mundane, so I’ll defer you to the GitHub repo for all the gory details.

As a final note on this code, I usually wouldn’t recommend catching all exceptions, it’s just bad practice in my opinion to catch an exception that you hadn’t thought about before hand. In this case however I think that it’s justifiable. The code inside the exception try block is pretty simple, only does one thing, and if it fails the Add-In will simply stop working, while displaying a mildly helpful error message to the end user. If this were a production product it would be worth investigating what exceptions could be thrown and handling them appropriately.

Wrapping Things Up

I’ve highlighted the really interesting parts here, everything else is in the GitHub repo. All in all it’s not to create something like Audio Assist, but the documentation is scarce and a little hard to go off of. A lot of the methods I used, especially for debugger hooking, were undocumented and labeled as Microsoft internal use only. That’s a little odd, and makes it difficult to use them effectively, but things generally seemed to make sense.


Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images