Microsoft Visual Studio is a great IDE (integrated development environment). It looks good from both the functional and design perspective, even though it is restricted by the need to maintain legacy aspects. It is so good that you will want to use it and extend it to meet your requirements.
There is a long history to having an extensible IDE, probably originating from Alan Kay’s SmallTalk design. There is a range of reasons for wanting to add features: You might be tempted to create a custom color scheme, a tool to automate a mundane task or for moving files around. You might even need a complete support for a new programming language!
When you are creating a Visual Studio plugin, you will most likely come across several frameworks such as native Visual Studio API, DTE automation object and Managed Extensibility Framework. You might even need to resort to using all of them at once to solve a more knotty problem.
Some features of the IDE are not accessible from these frameworks, but you can always use them if you fully understand the caveats. I’ll mention these later in this article. To use these features, you first may need to take a look on how the creators of the IDE write their code or maybe even call their internal methods! Some of them are written in C++ but the most interesting bits are in .NET so you won’t need to resort to anything more complex than disassembling .NET assemblies.
I do this disassembly work quite regularly while working on RevDeBug or while helping other developers on the ExtendVS Gitter.im channel. I became convinced, after seeing questions and problems repeatedly reappearing on this channel, that I should write an article about how to tackle such issues. To illustrate how to do this in practice, I’ll use two of the example questions that keep cropping up on the Channel.
The first issue was how to gain access to the Visual Studio Notifications. I couldn’t find any information about it on the MSDN pages or on other sites so I have put my software archeologist hat on and started a deep dig inside the internals of Visual Studio. It is a pretty new feature so I assumed that it would be fully implemented in .NET. I thought that checking how the “Notifications” tool window is created would be a good place to start my search. For that I’ve used Snoop, a WPF spy utility. With it I was able to track down the class of the window, which was
`Microsoft.VisualStudio.Services.UserNotifications.UserNotificationsWindow`. Using Snoop’s PowerShell tab I’ve also extracted the path to the containing assembly file.
Knowing the type and the path to the assembly, I was able to view the code using the .NET Reflector disassembler. Because this window was responsible for showing the user notifications, I hoped that, by using Reflector’s Analyze tool, I would be able to find further leads or even a real source of notifications. And I was right – in the “Depends On” section there was a reference to a separate assembly, that sounded just right: Microsoft.Internal.VisualStudio.UserNotifications
This was exactly, what I needed. Inside, I found the interfaces of notifications and a `SVsUserNotificationService` class for extracting the service that manages them.
I then started to prepare the sample code I needed to check whether it would actually work. I had to use the .NET Reflector Analyze function few more times to learn how those interfaces are actually used before I was confident enough to be able to come up with the working code listed below.
It requires a reference to `Microsoft.Internal.VisualStudio.UserNotifications.dll` to run.
1 2 3 4 |
var notificationService = this.ServiceProvider.GetService(typeof(SVsUserNotificationService)) as IVsUserNotificationService; var manager = notificationService.GetNotificationManagerAsync().Result; // ExtensionManager provider guid. var notifications = manager.GetNotifications(new Guid("2B7364A1-1482-4b25-9339-3A6BD7A8A86D")); |
All notifications have their own provider with a unique identifier: If we then want to extract existing notifications from the notification manager, we have to provide a valid GUID. In this example I have used ExtensionManager’s GUID which I extracted from `ExtensionManangerUpdateNotificationProvider` in `Microsoft.VisualStudio.ExtensionManager.Implementation.dll`
The second case was a bit more tricky – how can we programmatically trigger the print code function from an opened editor. Although there is a method within the DTE automation that provides this functionality (https://msdn.microsoft.com/en-us/library/envdte.document.printout.aspx), it doesn’t seem to work in newer versions of Visual Studio. Because the printing feature works just fine via the good old Ctrl-P shortcut, I wanted to give it a try and check whether it is possible to run it directly from code (and obviously not by emitting Ctrl+P keystroke).
This time around I didn’t have a good starting point within Visual Studio, so I just loaded all VS private assemblies and looked for anything containing `print` in it.
It didn’t take me long to find `IPrintingService` interface with a very promising method called `PrintTextBuffer`. Now I just had to find a way to call it from code, so I once more used the Analyze tool of .NET Reflector to see where it is used. At first I got no results, but after adding libraries from CommonExtensions/Microsft/Editor I found this little nugget:
The PrintingClient class was responsible for providing everything that is needed to print a single code view represented by an IWpfTextView interface. With this knowledge, I could then try to write my own code and create an instance of this type. I could also check how exactly it is used within the Visual Studio.
Another look at the `Analyze` tool via the `Instantiated By` option drove me to `SimpleTextViewWindow`. This class initializes the PrintingClient when it receives a proper command like “Print” or “PageSetup”. from a user in the InnerExec method, This couldn’t be better. If only this object was available for each code file that can be printed in the Visual Studio, I would be able to use reflection to invoke methods on the `PrintingClient`. When I rechecked the class declaration, I noticed that this class implements the `IVsTextView`. I decided to take a gamble that all printable text views which are returned from IVsTextManager.GetActiveView() are actually objects of the `SimpleTextViewWindow` type (https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.textmanager.interop.ivstextmanager.aspx).
It was worthwhile to give it a shot, especially because I hadn’t found any other class that would use the PrintingClient. I also assumed, that if the `IVsTextView` isn’t an instance of a `SimpleTextViewWindow` type, then we wouldn’t be able to use its print functionality anyway.
Based on the knowledge I‘d gathered, I could then start preparing an exploratory method you can see below. As the PrintClient property that we need to access is not visible publicly, I resorted to obtaining its reference with the use of Reflection API. To run the code, you will need a reference to the `Microsoft.VisualStudio.Editor.Implementation.dll` library, because it uses the `Assembly.GetType` call for extracting types via reflection. It might be easily replaced with a proper assembly-qualified name for the needed types, but it was a quick hack just to get it running.
1 2 3 4 5 6 7 8 9 10 |
var textManager = ServiceProvider.GetService(typeof(VsTextManagerClass)) as IVsTextManager; IVsTextView vTextView = null; textManager.GetActiveView(0, null, out vTextView); var simpleTextViewWindowType = typeof(Microsoft.VisualStudio.Editor.Implementation.AnnotationTagger).Assembly.GetType("Microsoft.VisualStudio.Editor.Implementation.SimpleTextViewWindow"); var printingClientType = typeof(Microsoft.VisualStudio.Editor.Implementation.AnnotationTagger).Assembly.GetType("Microsoft.VisualStudio.Editor.Implementation.PrintingClient"); var wpfTextView = simpleTextViewWindowType.GetProperty("WpfTextView").GetValue(vTextView); var printingClient = simpleTextViewWindowType.GetProperty("PrintClient", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(vTextView); printingClientType.GetMethod("TryPrint").Invoke(printingClient, new object[3] { wpfTextView, false, filePath }); |
When run from a Visual Studio plugin, the code above will trigger the printing facilities of Visual Studio, which was what I was striving for.
Before concluding this article I should probably warn you that, if you want to resort to using internal APIs of Visual Studio, you must keep in mind that they might be rapidly changed by Microsoft and will differ from release to release. This approach is prone to bugs and could affect the stability of the IDE, so make your code dependent on exact VS versions and keep track of what the VS team will add in next release: They might introduce an API that will provide exactly what you wished for 🙂
I hope that these two cases have encouraged you to start hacking Visual Studio. Most of the assemblies that you might want to look at are held inside “<Visual Studio installation location>\Common7\IDE\” directory. Great places to check are:
- PublicAssemblies – public and in almost all cases well documented interfaces. If possible, rely on those.
- PrivateAssemblies – Those change between Visual Studio versions and they lack documentation. Many of the public methods execute code from it.
- CommonExtensions/Microsoft – Microsoft extensions for VS. This place is always worth looking into so you can learn from the masters 😉
Happy hacking!
Load comments