Sunday, October 25, 2009

Hack 87. Access Visual Studio from Standalone Applications











 < Day Day Up > 





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.



















     < Day Day Up > 



    No comments: