Last night I gave a talk on Visual Studio Extensibility to the BaltoMSDN User Group in Hunt Valley Maryland. Great group, had a lot of fun. If you attended or would like to check out the slides and demos, here is a link:
Just to actually put a little technical content on my blog for a change, I thought I would summarize a few key points from the talk.
You have several options for adding functionality to Visual Studio. The first and simplest are Macros. Visual Studio Macros are code that you can either generate with the Macro recorder (similar to recording macros in Office applications) or write yourself through the Macros IDE, a scaled down version of Visual Studio for macro development that you access through the Tools > Macros menu. Macros are a good way to automate simple repetitive tasks to save yourself keystrokes and mouse clicks that you do over and over for a given development task.
The next step up the pyramid are Visual Studio Add-ins, which were the focus of my talk. Add-ins allow you to write .NET components that plug into Visual Studio and can run in the background and handle events fired by VS as the developer interacts with it, and/or invoke custom code based on user keystrokes or command bar/menu button clicks. You can add custom UIs to Visual studio as dialogs, docking windows, or options panes. There are a bunch of good examples available from MSDN at:
To get really rich functionality like you may have experienced from retail developer tools that integrate into Visual Studio, you will need to tap into the Visual Studio Integration Partner (VSIP) SDK. You can download and use the SDK for free, but if you ship any products to customers that you developed with VSIP, licensing fees and restrictions can apply.
I was just made aware of another option that I have not yet explored in depth, but that looks very promising and intriguing. Developer Express, who produce CodeRush and soon Refactor! for Visual Studio .NET, have packaged their core framework for developing add-ins as a reusable class library that you can use to develop extensions to Visual Studio. Basically they hide all the arcane details of the VS object model and the COM interop layers you have to go through for programming straight add-ins and VSIP code, and instead give you a cleaner, managed object model to program against. It is called DXCore and you can get it here:
I am a huge and vocal fan of CodeRush and now Refactor!, which not only save me a lot of time writing code, they make great motivational demos of the kinds of things you can achieve with extensibility in VS.NET. The fact that DevExpress has made the engine that they build these tools on for other to use is just another great example of the .NET community and I thank them for doing so. I’m looking forward to playing with these capabilities.
Some things to be aware of when building Add-ins (not using DXCore)…
– When you create an Add-in project through the wizard, make sure you compile right away before doing anything. This is because the project wizard adds the registry settings to try and load your Add-in the next time VS starts up. If you don’t build your project, those reg keys are pointing to a non-existent DLL, and you will get an error message that if you say Yes to, will kill all the reg settings you need to build/debug your Add-in. You can get them back by building and running the setup project that is created by the wizard, but it is a better practice to just do a quick build to get something there for VS to refer to after completing the wizard.
– If you move your project to another machine, those reg keys will not be there to allow you to debug the add-in. The easiest way to get them in place is to build the Add-in project, build the setup project that is part of the solution, run the install, and then rebuild the Add-in project. Running the install will install a copy of the original DLL to the path you specify (typically under Program Files), will reg the DLL for COM interop, and will create the necessary reg settings. Rebuilding the Add-in project after that re-does the COM interop registration, pointing the CLSID in the reg back to your debug version of the DLL, allowing you to proceed with further development and debug.
– When you create an Add-in project it sets up the debugger to launch another instance of Visual Studio in the debugger so that you can try out your add in and hit breakpoints and do debugging in the original instance you ran the debug session from. If you are working out bugs and your add-in is failing to catch all exceptions or is not unsubscribing from events, you can get some weird behaviors because your object may remain in memory in the live VS instance. If you get anything unexpected, shut down all instances of VS and re-open your Add-in solution to make sure all objects get killed and you are working with an uncorrupted instance of VS.
– The code injected by the project wizard when you say you want a Tools menu item only creates that item one time, the first time your add-in is loaded. If the tools menu item is removed or exceptions get thrown in loading your add-in, you will never get your tools menu item loaded. This is because the code that creates in the OnConnection method is contained in a conditional block that checks for the enumerated value extConnectMode.extcmUISetup. If you want to make sure your tools menu item is recreated if it fails the first time, change this to be an || (Or) of the values extcmStartup and extcm_AfterStartup. The downside of doing this is that there will be a minor perf hit in loading your add in because an exception will be thrown and caught when it tries to create the command if it already exists, so you may want to revert to the original check for production. But in development environments this will ensure your command gets created and is present repeatably in the face of errors and weird development startup paths.
– The objects and collections in the automation object model are COM components that you are accessing through interop. In many cases to get to the underlying object and get back a valid .NET reference, you have to use the .Object property for objects and the .Item property to access items in a collection. For example, there is an ActiveWindow and ActiveDocument property on the DTE root object of the model that let you get quickly to these items in the environment. However, to get a Window reference or a Document reference variable back from these (respectively), you will need to access DTE.ActiveWindow.Object, not just DTE.ActiveWindow. Likewise, to get into a collection, you will need to index into Item with a 1-based index, such as DTE.Solution.Projects.Item(1).Object to get to the Project object for the first project in the current solution.