Hack 87. Access Visual Studio from Standalone Applications
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.
|
You can get a reference to the currently executing instance of Visual
Studio using this line of code:
EnvDTE.DTE dte =
(DTE)Marshal.GetActiveObject("VisualStudio.DTE.
7.1");
This will return a reference to the currently executing instance of
Visual Studio as opposed to a reference to a new instance of Visual
Studio.
|
|
12.3.1. Version Issues
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
EnvDTE�while you may have only one version
of Visual Studio installed on your machine, other users may have any
number of versions installed.
12.3.2. The Notorious "Call Was Rejected by Callee" Error
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.
|
No comments:
Post a Comment