When a macro or add-in is not what you are looking for, you can always use the automation object model from a normal Windows Forms application.
The EnvDTE assembly can be referenced by C# and VB.NET applications to directly reference the Visual Studio IDE. Using this assembly, you can modify windows, add custom toolbox items, and much more. Working with this assembly has a number of pitfalls though, and this hack shows ways to get around them.
When using the DTE object from an application. you can perform any of the same actions that you would normally do from a macro or add-in.
|
After adding a reference to the EnvDTE assembly in your project, the first thing you normally will do is create an instance of the DTE object, using a line of code like this:
DTE env = new DTE( );
This will work fine if you have only Visual Studio .NET 2002 installed. If you also have another version of Visual Studio installed, then no matter what assembly you reference, this call will return the DTE object for Visual Studio .NET 2002 (unless you don't have it installed).
This is because both assemblies are the exact same COM wrapper that accesses different versions of Visual Studio. To get the correct version of this class, you will need to use the ProgID of the class. This can be done using the following piece of code:
Type latestDTE = Type.GetTypeFromProgID("VisualStudio.DTE.7.1"); EnvDTE.DTE env = Activator.CreateInstance(latestDTE) as EnvDTE.DTE;
This piece of code will retrieve the object to work with Visual Studio .NET 2003. You could change the ProgID to get the object for 2002 or 2005 by simply changing the version number at the end of the ID (7.0 and 8.0, respectively).
It is a good idea to always use this method when accessing EnvDTEwhile you may have only one version of Visual Studio installed on your machine, other users may have any number of versions installed.
While working with EnvDTE, you will most likely come across random occurrences of "Call was rejected by callee" errors. The key to avoiding this error lies in using an OLE message filter. The filter will watch all calls, and when one fails, it will wait and try again instead of throwing this error. The following OLE message filter will watch for any calls and then retry them instead of throwing an exception:
using System; using System.Runtime.InteropServices; class MessageFilter : IOleMessageFilter { // // Public API public static void Register( ) { IOleMessageFilter newfilter = new MessageFilter( ); IOleMessageFilter oldfilter = null; CoRegisterMessageFilter(newfilter, out oldfilter); } public static void Revoke( ) { IOleMessageFilter oldfilter = null; CoRegisterMessageFilter(null, out oldfilter); } // // IOleMessageFilter impl int IOleMessageFilter.HandleInComingCall(int dwCallType, System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo) { System.Diagnostics.Debug.WriteLine ("IOleMessageFilter::HandleInComingCall"); return 0; //SERVERCALL_ISHANDLED } int IOleMessageFilter.RetryRejectedCall( System.IntPtr hTaskCallee, int dwTickCount, int dwRejectType) { System.Diagnostics.Debug.WriteLine ("IOleMessageFilter::RetryRejectedCall"); if (dwRejectType = = 2 ) //SERVERCALL_RETRYLATER { System.Diagnostics.Debug.WriteLine("Retry call later"); return 99; //retry immediately if return >=0 & <100 } return -1; //cancel call } int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, int dwTickCount, int dwPendingType) { System.Diagnostics.Debug.WriteLine ("IOleMessageFilter::MessagePending"); return 2; //PENDINGMSG_WAITDEFPROCESS } // // Implementation [DllImport("Ole32.dll")] private static extern int CoRegisterMessageFilter( IOleMessageFilter newfilter, out IOleMessageFilter oldfilter); } [ComImport( ), Guid("00000016-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] interface IOleMessageFilter // deliberately renamed to avoid confusion // w/ System.Windows.Forms.IMessageFilter { [PreserveSig] int HandleInComingCall( int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); [PreserveSig] int RetryRejectedCall( IntPtr hTaskCallee, int dwTickCount, int dwRejectType); [PreserveSig] int MessagePending( IntPtr hTaskCallee, int dwTickCount, int dwPendingType); } }
C# and VB.NET versions of this class can be downloaded from the book's web site (see http://www.oreilly.com/catalog/visualstudiohks).
To use this messaging class, you need to call the Register() method before starting to work with EnvDTE, and then call the Revoke() method when you are done working with the object. The following code demonstrates this usage:
// Register the OLE message filter MessageFilter.Register( ); Type latestDTE = Type.GetTypeFromProgID("VisualStudio.DTE.7.1"); EnvDTE.DTE env = Activator.CreateInstance(latestDTE) as EnvDTE.DTE; // Work with the DTE object here // Unplug the message filter MessageFilter.Revoke( );
This message filter should prevent any "Call was rejected by callee" exceptions from being thrown while working with EnvDTE. Thanks to Shawn A. Van Ness for posting this method and code on his web log, which can be found at http://windojitsu.com/blog.