SlideShare a Scribd company logo
Visual	Studio	Automation	Object	Model.	
EnvDTE	interfaces.
Author: Paul Eremeev
Date: 18.10.2012
Abstract
This article contains an overview of Visual Studio Automation Object Model. Model's overall structure
and the means of obtaining access to its interfaces through DTE/DTE2 top level objects are examined.
Several examples of utilizing elements of the model are provided. Also discussed are the issues of using
model's interfaces within multithreaded applications; an example of implementing such mechanism for
multithreaded interaction with COM interfaces in managed code is provided as well.
Introduction
Visual Studio development environment is built upon the principles of automation and extensibility,
providing the developers using it with the ability of integrating almost any custom element into the IDE
and allowing for an easy interaction with its default and user-created components. As the means of
implementing these tasks, Visual Studio users are provided with several cross-complementing toolsets,
the most basic and versatile among these is the Visual Studio Automation Object Model.
Automation Object Model is represented by a series of libraries containing a vast and well-structured
API set which covers all aspects of IDE automation and the majority of its extensibility capabilities.
Although, in comparison to other IDE extensibility tools, this model does not provide access to some
portions of Visual Studio (this applies mostly to the extension of some IDE's features), it is nonetheless
the most flexible and versatile among them.
The majority of the model's interfaces are accessible from within every type of IDE extension module,
which allows interacting with the environment even from an external independent process. Moreover,
the model itself could be extended along with the extension of Visual Studio IDE, providing other third-
party developers with an access to user-created custom components.
Automation Object Model structure
Visual Studio automation model is composed of several interconnected functional object groups
covering all aspects of the development environment; it also provides capabilities for controlling and
extending these groups. Accessing any of them is possible through the top-level global DTE interface
(Development Tools Environment). Figure 1 shows the overall structure of the automation model and
how it is divided among functionality groups.
Figure 1 — Visual Studio Automation Object Model (click the picture to zoom in)
The model itself could be extended by user in one of the following groups:
• project models (implementing new project types, support for new languages);
• document models (implementing new document types and document editors)
• code editor level models (support for specific language constructs)
• project build-level models
Automation model could be extended from plug-ins of VSPackage type only.
Despite the model's versatility, not every group belonging to the model could be equally utilized from all
the types of IDE extensions. For instance, some of the model's capabilities are inaccessible to external
processes; these capabilities are tied to specific extension types, such as Add-In or VSPackage.
Therefore, when selecting the type for the extension to be developed, it is important to consider the
functionality that this extension will require.
Obtaining references to DTE/DTE2 objects.
In order to create a Visual Studio automation application it is necessary to obtain access to the
automation objects themselves in the first place. To accomplish this, first of all it is necessary to hook up
the correct versions of libraries containing the required managed API wrappers in the EnvDTE
namespace. Secondly, the reference to the automation model top-level object, that is the DTE2
interface, should be obtained.
In the course of Visual Studio evolution, several of its automation objects had been modified or received
some additional functionality. So, to maintain a backward compatibility with existing extension
packages, new EnvDTE80, EnvDTE90, EnvDTE100 etc. namespaces were created instead of updating the
interfaces from the original EnvDTE namespace. The majority of such updated interfaces from these
new namespaces do maintain the same names as in the original ones, but with addition of an ordinal
number at the end of the name, for example Solution and Solution2. It is advised that these updated
interfaces should be utilized when creating a new project, as they do contain the most recent
functionality. It's worth noting that properties and methods of DTE2 interface usually return object
references with types corresponding to the original DTE, i.e. accessing dte2.Solution will return Solution
and not the Solution2 as it would seem.
Although these new EnvDTE80, EnvDTE90, EnvDTE100 namespaces do contain some of the updated
functionality as mentioned above, still it is the EnvDTE interface that contains the majority of
automation objects. Therefore, in order to possess access to all of the existing interfaces, it is necessary
to link all versions of the managed COM wrapper libraries to the project, as well as to obtain the
references to DTE and also to DTE2.
The way of obtaining top-level EnvDTE object reference is dependent upon the type of IDE extension
being developed. Let's examine 3 of such extension types: Add-In, VSPackage and an MSVS-independent
external process.
Add-In extension
In the case of an Add-In extension, access to the DTE interface can be obtained inside the OnConnection
method which should be implemented for the IDTExtensibility interface that provides access to the
extension-environment interaction events. The OnConnection method is called at the moment when the
module is loaded by the IDE; it can happen either when the environment is being loaded itself or after
the extension was called for the first time in the IDE session. The example of obtaining the reference
follows:
public void OnConnection(object application,
ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_dte2 = (DTE2)application;
...
}
An Add-In module can be initialized either at the moment of IDE start-up, or when it is called for the first
time in current IDE session. So, the connectMode can be used to correctly determine the moment of
initialization inside the OnConnection method.
switch(connectMode)
{
case ext_ConnectMode.ext_cm_UISetup:
...
break;
case ext_ConnectMode.ext_cm_Startup:
...
break;
case ext_ConnectMode.ext_cm_AfterStartup:
...
break;
case ext_ConnectMode.ext_cm_CommandLine:
...
break;
}
As in the example above, add-In could be loaded either simultaneously with the IDE itself (if the startup
option in the Add-In manager is checked), when it is called the first time or when it is called through the
command line. The ext_ConnectMode.ext_cm_UISetup option is invoked only for a single time in the
plug-in's overall lifetime, which is during its first initialization. This case should be used for initializing
user UI elements which are to be integrated into the environment (more on this later on).
If an Add-In is being loaded during Visual Studio start-up (ext_ConnectMode.ext_cm_Startup), then at
the moment OnConnect method receives control for the first time, it is possible that the IDE still is not
fully initialized itself. In such a case, it is advised to postpone the acquisition of the DTE reference until
the environment is fully loaded. The OnStartupComplete handler provided by the IDTExtensibility can be
used for this.
public void OnStartupComplete(ref Array custom)
{
...
}
VSPackage extension
For VSPackage type of extension, the DTE could be obtained through the global Visual Studio service
with the help of GetService method of a Package subclass:
DTE dte = MyPackage.GetService(typeof(DTE)) as DTE;
Please note that the GetService method could potentially return null in case Visual Studio is not fully
loaded or initialized at the moment of such access, i.e. it is in the so called "zombie" state. To correctly
handle this situation, it is advised that the acquisition of DTE reference should be postponed until this
interface is inquired. But in case the DTE reference is required inside the Initialize method itself, the
IVsShellPropertyEvents interface can be utilized (also by deriving our Package subclass from it) and then
the reference could be safely obtained inside the OnShellPropertyChange handler.
DTE dte;
uint cookie;
protected override void Initialize()
{
base.Initialize();
IVsShell shellService = GetService(typeof(SVsShell)) as IVsShell;
if (shellService != null)
ErrorHandler.ThrowOnFailure(
shellService.AdviseShellPropertyChanges(this,out cookie));
...
}
public int OnShellPropertyChange(int propid, object var)
{
// when zombie state changes to false, finish package initialization
if ((int)__VSSPROPID.VSSPROPID_Zombie == propid)
{
if ((bool)var == false)
{
this.dte = GetService(typeof(SDTE)) as DTE;
IVsShell shellService = GetService(typeof(SVsShell)) as IVsShell;
if (shellService != null)
ErrorHandler.ThrowOnFailure(
shellService.UnadviseShellPropertyChanges(this.cookie) );
this.cookie = 0;
}
}
return VSConstants.S_OK;
}
It should be noted that the process of VSPackage module initialization at IDE startup could vary for
different Visual Studio versions. For instance, in case of VS2005 and VS2008, an attempt at accessing
DTE during IDE startup will almost always result in null being returned, owning to the relative fast
loading times of these versions. But, one does not simply obtain access into DTE. In Visual Studio 2010
case, it mistakenly appears that one could simply obtain an access to the DTE from inside the Initialize()
method. In fact, this impression is a false one, as such method of DTE acquisition could potentially cause
the occasional appearance of "floating" errors which are hard to identify and debug, and even the DTE
itself may be still uninitialized when the reference is acquired. Because of these disparities, the
aforementioned acquisition method for handling IDE loading states should not be ignored on any
version of Visual Studio.
Independent external process
The DTE interface is a top-level abstraction for Visual Studio environment in the automation model. In
order to acquire a reference to this interface from an external application, its ProgID COM identifier
could be utilized; for instance, it will be "VisualStudio.DTE.10.0" for Visual Studio 2010. Consider this
example of initializing a new IDE instance and when obtaining a reference to the DTE interface.
// Get the ProgID for DTE 8.0.
System.Type t = System.Type.GetTypeFromProgID(
"VisualStudio.DTE.10.0", true);
// Create a new instance of the IDE.
object obj = System.Activator.CreateInstance(t, true);
// Cast the instance to DTE2 and assign to variable dte.
EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)obj;
// Show IDE Main Window
dte.MainWindow.Activate();
In the example above we've actually created a new DTE object, starting deven.exe process by the
CreateInstance method. But at the same time, the GUI window of the environment will be displayed
only after the Activate method is called.
Next, let's review a simple example of obtaining the DTE reference from an already running Visual
Studio Instance:
EnvDTE80.DTE2 dte2;
dte2 = (EnvDTE80.DTE2)
System.Runtime.InteropServices.Marshal.GetActiveObject(
"VisualStudio.DTE.10.0");
However, in case several instances of the Visual Studio are executing at the moment of our inquiry, the
GetActiveObject method will return a reference to the IDE instance that was started the earliest. Let's
examine a possible way of obtaining the reference to DTE from a running Visual Studio instance by the
PID of its process.
using EnvDTE80;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
[DllImport("ole32.dll")]
private static extern void CreateBindCtx(int reserved,
out IBindCtx ppbc);
[DllImport("ole32.dll")]
private static extern void GetRunningObjectTable(int reserved,
out IRunningObjectTable prot);
public static DTE2 GetByID(int ID)
{
//rot entry for visual studio running under current process.
string rotEntry = String.Format("!VisualStudio.DTE.10.0:{0}", ID);
IRunningObjectTable rot;
GetRunningObjectTable(0, out rot);
IEnumMoniker enumMoniker;
rot.EnumRunning(out enumMoniker);
enumMoniker.Reset();
IntPtr fetched = IntPtr.Zero;
IMoniker[] moniker = new IMoniker[1];
while (enumMoniker.Next(1, moniker, fetched) == 0)
{
IBindCtx bindCtx;
CreateBindCtx(0, out bindCtx);
string displayName;
moniker[0].GetDisplayName(bindCtx, null, out displayName);
if (displayName == rotEntry)
{
object comObject;
rot.GetObject(moniker[0], out comObject);
return (EnvDTE80.DTE2)comObject;
}
}
return null;
}
Here we've acquired the DTE interface by identifying the required instance of the IDE in the table of
running COM objects (ROT, Running Object Table) by its process identifier. Now we can access the DTE
for every of the executing instances of Visual Studio, for example:
Process Devenv;
...
//Get DTE by Process ID
EnvDTE80.DTE2 dte2 = GetByID(Devenv.Id);
Additionally, to acquire any project-specific interface (including custom model extensions), for example
the CSharpProjects model, through a valid DTE interface, the GetObject method should be utilized:
Projects projects = (Projects)dte.GetObject("CSharpProjects");
The GetObject method will return a Projects collection of regular Project objects, and each one of them
will contain a reference to our project-specific properties, among other regular ones.
Visual Studio text editor documents
Automation model represents Visual Studio text documents through the TextDocument interface. For
example, C/C++ source code files are opened by the environment as text documents. TextDocument is
based upon the common automation model document interface (the Document interface), which
represents file of any type opened in Visual Studio editor or designer. A reference to the text document
object can be obtained through the 'Object' field of the Document object. Let's acquire a text document
for the currently active (i.e. the one possessing focus) document from IDE's text editor.
EnvDTE.TextDocument objTextDoc =
(TextDocument)PVSStudio.DTE.ActiveDocument.Object("TextDocument");
Modifying documents
The TextSelection document allows controlling text selection or to modify it. The methods of this
interface represent the functionality of Visual Studio text editor, i.e. they allow the interaction with the
text as it presented directly by the UI.
EnvDTE.TextDocument Doc =
(TextDocument)PVSStudio.DTE.ActiveDocument.Object(string.Empty);
Doc.Selection.SelectLine();
TextSelection Sel = Doc.Selection;
int CurLine = Sel.TopPoint.Line;
String Text = Sel.Text;
Sel.Insert("testrn");
In this example we selected a text line under the cursor, read the selected text and replaced it with a
'test' string.
TextDocument interface also allows text modification through the EditPoint interface. This interface is
somewhat similar to the TextSelection, but instead of operating with the text through the editor UI, it
directly manipulates text buffer data. The difference between them is that the text buffer is not
influenced by such editor-specific notions as WordWrap and Virtual Spaces. It should be noted that both
of these editing methods are not able to modify read-only text blocks.
Let's examine the example of modifying text with EditPoint by placing additional lines at the end of
current line with a cursor.
objEditPt = objTextDoc.StartPoint.CreateEditPoint();
int lineNumber = objTextDoc.Selection.CurrentLine;
objEditPt.LineDown(lineNumber - 1);
EditPoint objEditPt2 = objTextDoc.StartPoint.CreateEditPoint();
objEditPt2.LineDown(lineNumber - 1);
objEditPt2.CharRight(objEditPt2.LineLength);
String line = objEditPt.GetText(objEditPt.LineLength);
String newLine = line + "test";
objEditPt.ReplaceText(objEditPt2, newLine,
(int)vsEPReplaceTextOptions.vsEPReplaceTextKeepMarkers);
Navigating the documents
VSPackage modules are able to obtain access to a series of global services which could be used for
opening and handling environment documents. These services could be acquired by the
Package.GetGlobalService() method. It should be noted that the services described here are not part of
the DTE model and are accessible only from a Package-type extension, and therefore they could not be
utilized in other types of Visual Studio extensions. Nonetheless, they can be quite useful for handling IDE
documents when they are utilized in addition to the Documents interface described earlier. Next, we'll
examine these services in more detail.
The IVsUIShellOpenDocument interface controls the state of documents opened in the environment.
Following is the example that uses this interface to open a document through path to a file which this
document will represent.
String path = "C:Testtest.cpp";
IVsUIShellOpenDocument openDoc =
Package.GetGlobalService(typeof(IVsUIShellOpenDocument))
as IVsUIShellOpenDocument;
IVsWindowFrame frame;
Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp;
IVsUIHierarchy hier;
uint itemid;
Guid logicalView = VSConstants.LOGVIEWID_Code;
if (ErrorHandler.Failed(
openDoc.OpenDocumentViaProject(path, ref logicalView, out sp,
out hier, out itemid, out frame))
|| frame == null)
{
return;
}
object docData;
frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out docData);
The file will be opened in a new editor or will receive focus in case it already has been opened earlier.
Next, let's read a VsTextBuffer text buffer from this document we opened:
// Get the VsTextBuffer
VsTextBuffer buffer = docData as VsTextBuffer;
if (buffer == null)
{
IVsTextBufferProvider bufferProvider = docData as
IVsTextBufferProvider;
if (bufferProvider != null)
{
IVsTextLines lines;
ErrorHandler.ThrowOnFailure(bufferProvider.GetTextBuffer(
out lines));
buffer = lines as VsTextBuffer;
Debug.Assert(buffer != null,
"IVsTextLines does not implement IVsTextBuffer");
if (buffer == null)
{
return;
}
}
}
The IVsTextManager interface controls all of the active text buffers in the environment. For example we
can navigate a text document using the NavigateToLineAndColumn method of this manager on a buffer
we've acquired earlier:
IVsTextManager mgr =
Package.GetGlobalService(typeof(VsTextManagerClass))
as IVsTextManager;
mgr.NavigateToLineAndColumn(buffer, ref logicalView, line,
column, line, column);
Subscribing and handling events
Automation objects events are represented by the DTE.Events property. This element references all of
the common IDE events (such as CommandEvents, SolutionEvents), as well as the events of separate
environment components (project types, editors, tools etc.), also including the ones designed by third-
party developers. To acquire a reference for this automation object, the GetObject method could be
utilized.
When subscribing to the DTE events one should remember that this interface could be still unavailable
at the moment of extension being initialized. So it is always important to consider the sequence of your
extension initialization process if the access to DTE.Events is required in the Initialize() method of your
extension package. The correct handling of initialization sequence will vary for different extension types,
as it was described earlier.
Let's acquire a reference for an events object of Visual C++ project model defined by the
VCProjectEngineEvents interface and assign a handler for the removal of an element from the Solution
Explorer tree:
VCProjectEngineEvents m_ProjectItemsEvents =
PVSStudio.DTE.Events.GetObject("VCProjectEngineEventsObject")
as VCProjectEngineEvents;
m_ProjectItemsEvents.ItemRemoved +=
new _dispVCProjectEngineEvents_ItemRemovedEventHandler(
m_ProjectItemsEvents_ItemRemoved);
MDI windows events
The Events.WindowEvents property could be utilized to handle regular events of an environment MDI
window. This interface permits the assignment of a separate handler for a single window (defined
through the EnvDTE.Window interface) or the assignment of a common handler for all of the
environment's windows. Following example contains the assignment of a handler for the event of
switching between IDE windows:
WindowEvents WE = PVSStudio.DTE.Events.WindowEvents;
WE.WindowActivated +=
new _dispWindowEvents_WindowActivatedEventHandler(
Package.WE_WindowActivated);
Next example is the assignment of a handler for window switching to the currently active MDI window
through WindowEvents indexer:
WindowEvents WE =
m_dte.Events.WindowEvents[MyPackage.DTE.ActiveWindow];
WE.WindowActivated += new
_dispWindowEvents_WindowActivatedEventHandler(
MyPackage.WE_WindowActivated);
IDE commands events
The actual handling of environment's commands and their extension through the automation model is
covered in a separate article of this series. In this section we will examine the handling of the events
related to these commands (and not of the execution of the commands themselves). Assigning the
handlers to these events is possible through the Events.CommandEvents interface. The CommandEvents
property, as in the case of MDI windows events, also permits the assignment of a handler either for all
of the commands or for a single one through the indexer.
Let's examine the assignment of a handler for the event of a command execution being complete (i.e.
when the command finishes its execution):
CommandEvents CEvents = DTE.Events.CommandEvents;
CEvents.AfterExecute += new
_dispCommandEvents_AfterExecuteEventHandler(C_AfterExecute);
But in order to assign such a handler for an individual command, it is necessary to identify this command
in the first place. Each command of the environment is identified by a pair of GUID:ID, and in case of a
user-created commands these values are specified directly by the developer during their integration, for
example through the VSCT table. Visual Studio possesses a special debug mode which allows identifying
any of the environment's comamnds. To activate this mode, it is required that the following key is to be
added to the system registry (an example for Visual Studio 2010):
[HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio10.0General]
"EnableVSIPLogging"=dword:00000001
Now, after restarting the IDE, hovering your mouse over menu or toolbar elements with CTRL+SHIFT
being simultaneously pressed (though sometime it will not work until you left-click it) will display a
dialog window containing all of the command's internal identifiers. We are interested in the values of
Guid and CmdID. Let's examine the handling of events for the File.NewFile command:
CommandEvents CEvents = DTE.Events.CommandEvents[
"{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 221];
CEvents.AfterExecute += new
_dispCommandEvents_AfterExecuteEventHandler(C_AfterExecute);
The handler obtained in this way will receive control only after the command execution is finished.
void C_AfterExecute(string Guid, int ID, object CustomIn,
object CustomOut)
{
...
}
This handler should not be confused with an immediate handler for the execution of the command itself
which could be assigned during this command's initialization (from an extension package and in case the
command is user-created). Handling the IDE commands is described in a separate article that is entirely
devoted to IDE commands.
In conclusion to this section it should be mentioned that in the process of developing our own
VSPackage extension, we've encountered the necessity to store the references to interface objects
containing our handler delegates (such as CommandEvents, WindowEvents etc.) on the top-level fields
of our main Package subclass. The reason for this is that in case of the handler being assigned through a
function-level local variable, it is lost immediately after leaving the method. Such behavior could
probably be attributed to the .NET garbage collector, although we've obtained these references from
the DTE interface which definitely exists during the entire lifetime of our extension package.
Interacting with DTE2 COM interfaces from within a multithreaded
application
Initially PVS-Studio extension package had not contained any specific thread-safety mechanisms for its
interaction with Visual Studio APIs. At the same time, we had been attempting to confine the
interactions with this APIs within a single background thread which was created and owned by our plug-
in. And such approach functioned flawlessly for quite a long period. However, several bug reports from
our users, each one containing a similar ComExeption error, prompted us to examine this issue in more
detail and to implement a threading safety mechanism for our COM Interop.
Although Visual Studio automation model is not a thread-safe one, it still provides a way for interacting
with multi-threaded applications. Visual Studio application is a COM (Component Object Mode) server.
For the task of handling calls from COM clients (in our case, this will be our extension package) to
thread-unsafe servers, COM provides a mechanism known as STA (single-threaded apartment) model. In
the terms of COM, an Apartment represents a logical container inside a process in which objects and
threads share the same thread access rules. STA can hold only a single thread, but an unlimited number
of objects, inside such container. Calls from other threads to such thread-unsafe objects inside STA are
converted into messages and posted to a message queue. Messages are retrieved from the message
queue and converted back into method calls one at a time by the thread running in the STA, so it
becomes possible for only a single thread to access these unsafe objects on the server.
Utilizing Apartment mechanism inside managed code
The .NET Framework does not utilize COM Apartment mechanics directly. Therefore, when a managed
application calls a COM object in the COM interoperation scenarios, CLR (Common Language Runtime)
creates and initializes apartment container. A managed thread is able to create and enter either an MTA
(multi-threaded apartment, a container that, contrary to STA, can host several threads at the same
time), or an STA, though a thread will be started as an MTA by default. The type of the apartment could
be specified before thread is launched:
Thread t = new Thread(ThreadProc);
t.SetApartmentState(ApartmentState.STA);
...
t.Start();
As an apartment type could not be changed once thread had been started, the STAThread attribute
should be used to specify the main thread of a managed application as an STA:
[STAThread]
static void Main(string[] args)
{...}
Implementing message filter for COM interoperation errors in a managed environment
As STA serializes all of calls to the COM server, one of the calling clients could potentially be blocked or
even rejected when the server is busy, processing different calls or another thread is already inside the
apartment container. In case COM server rejects its client, .NET COM interop will generate a
System.Runtime.InteropServices.COMException ("The message filter indicated that the application is
busy").
When working on a Visual Studio module (add-in, vspackage) or a macro, the execution control usually
passes into the module from the environment's main STA UI thread (such as in case of handling events
or environment state changes, etc.). Calling automation COM interfaces from this main IDE thread is
safe. But if other background threads are planned to be utilized and EnvDTE COM interfaces are to be
called from these background threads (as in case of long calculations that could potentially hang the
IDE's interface, if these are performed on the main UI thread), then it is advised to implement a
mechanism for handling calls rejected by a server.
While working on PVS-Studio plug-in we've often encountered these kinds of COM exceptions in
situations when other third-party extensions were active inside the IDE simultaneously with PVS-Studio
plug-in. Heavy user interaction with the UI also was the usual cause for such issues. It is quite logical that
these situations often resulted in simultaneous parallel calls to COM objects inside STA and
consequently to the rejection of some of them.
To selectively handle incoming and outgoing calls, COM provides the IMessageFilter interface. If it is
implemented by the server, all of the calls are passed to the HandleIncomingCall method, and the client
is informed on the rejected calls through the RetryRejectedCall method. This in turn allows the rejected
calls to be repeated, or at least to correctly present this rejection to a user (for example, by displaying a
dialog with a 'server is busy' message). Following is the example of implementing the rejected call
handling for a managed application.
[ComImport()]
[Guid("00000016-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface 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);
}
class MessageFilter : MarshalByRefObject, IDisposable, IMessageFilter
{
[DllImport("ole32.dll")]
[PreserveSig]
private static extern int CoRegisterMessageFilter(
IMessageFilter lpMessageFilter,
out IMessageFilter lplpMessageFilter);
private IMessageFilter oldFilter;
private const int SERVERCALL_ISHANDLED = 0;
private const int PENDINGMSG_WAITNOPROCESS = 2;
private const int SERVERCALL_RETRYLATER = 2;
public MessageFilter()
{
//Starting IMessageFilter for COM objects
int hr =
MessageFilter.CoRegisterMessageFilter(
(IMessageFilter)this,
out this.oldFilter);
System.Diagnostics.Debug.Assert(hr >= 0,
"Registering COM IMessageFilter failed!");
}
public void Dispose()
{
//disabling IMessageFilter
IMessageFilter dummy;
int hr = MessageFilter.CoRegisterMessageFilter(this.oldFilter,
out dummy);
System.Diagnostics.Debug.Assert(hr >= 0,
"De-Registering COM IMessageFilter failed!")
System.GC.SuppressFinalize(this);
}
int IMessageFilter.HandleInComingCall(int dwCallType,
IntPtr threadIdCaller, int dwTickCount, IntPtr lpInterfaceInfo)
{
// Return the ole default (don't let the call through).
return MessageFilter.SERVERCALL_ISHANDLED;
}
int IMessageFilter.RetryRejectedCall(IntPtr threadIDCallee,
int dwTickCount, int dwRejectType)
{
if (dwRejectType == MessageFilter.SERVERCALL_RETRYLATER)
{
// Retry the thread call immediately if return >=0 &
// <100.
return 150; //waiting 150 mseconds until retry
}
// Too busy; cancel call. SERVERCALL_REJECTED
return -1;
//Call was rejected by callee.
//(Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED))
}
int IMessageFilter.MessagePending(
IntPtr threadIDCallee, int dwTickCount, int dwPendingType)
{
// Perform default processing.
return MessageFilter.PENDINGMSG_WAITNOPROCESS;
}
}
Now we can utilize our MessageFilter while calling COM interfaces from a background thread:
using (new MessageFilter())
{
//COM-interface dependent code
...
}
References
1. MSDN. Referencing Automation Assemblies and the DTE2 Object.
2. MSDN. Functional Automation Groups.
3. MZ-Tools. HOWTO: Use correctly the OnConnection method of a Visual Studio add-in.
4. The Code Project. Understanding The COM Single-Threaded Apartment.
5. MZ-Tools. HOWTO: Add an event handler from a Visual Studio add-in.
6. Dr. eX's Blog. Using EnableVSIPLogging to identify menus and commands with VS 2005 + SP1.
Other articles in this series
0. Introduction.
1. Creating, debugging and deploying extension packages for Microsoft Visual Studio
2005/2008/2010/2012.
2. Visual Studio Automation Object Model. EnvDTE interfaces.
3. Visual Studio commands.
4. Visual Studio tool windows.
5. Integrating into Visual Studio settings.
6. Visual C++ project model.

More Related Content

What's hot

Java Swing
Java SwingJava Swing
Java Swing
Arkadeep Dey
 
Di code steps
Di code stepsDi code steps
Di code steps
Brian Kiptoo
 
java swing programming
java swing programming java swing programming
java swing programming
Ankit Desai
 
Introdu.awt
Introdu.awtIntrodu.awt
Introdu.awt
myrajendra
 
Jdbc
JdbcJdbc
Java session01
Java session01Java session01
Java session01
Niit Care
 
Java Swing
Java SwingJava Swing
Java Swing
Shraddha
 
Type Annotations in Java 8
Type Annotations in Java 8 Type Annotations in Java 8
Type Annotations in Java 8
FinLingua, Inc.
 
Java session11
Java session11Java session11
Java session11
Niit Care
 
Module 1: Introduction to .NET Framework 3.5 (Material)
Module 1: Introduction to .NET Framework 3.5 (Material)Module 1: Introduction to .NET Framework 3.5 (Material)
Module 1: Introduction to .NET Framework 3.5 (Material)
Mohamed Saleh
 
Basic of Applet
Basic of AppletBasic of Applet
Basic of Applet
suraj pandey
 
Annotations
AnnotationsAnnotations
Annotations
Knoldus Inc.
 
Java session08
Java session08Java session08
Java session08
Niit Care
 
Java session02
Java session02Java session02
Java session02
Niit Care
 
An Introduction To Unit Testing and TDD
An Introduction To Unit Testing and TDDAn Introduction To Unit Testing and TDD
An Introduction To Unit Testing and TDD
Ahmed Ehab AbdulAziz
 
06 iec t1_s1_oo_ps_session_08
06 iec t1_s1_oo_ps_session_0806 iec t1_s1_oo_ps_session_08
06 iec t1_s1_oo_ps_session_08
Niit Care
 
Guice tutorial
Guice tutorialGuice tutorial
Guice tutorial
Anh Quân
 
01 iec t1_s1_oo_ps_session_01
01 iec t1_s1_oo_ps_session_0101 iec t1_s1_oo_ps_session_01
01 iec t1_s1_oo_ps_session_01
Niit Care
 
Java Annotation
Java AnnotationJava Annotation
Java Annotation
karthik.tech123
 
Identifing Listeners and Filters
Identifing Listeners and FiltersIdentifing Listeners and Filters
Identifing Listeners and Filters
People Strategists
 

What's hot (20)

Java Swing
Java SwingJava Swing
Java Swing
 
Di code steps
Di code stepsDi code steps
Di code steps
 
java swing programming
java swing programming java swing programming
java swing programming
 
Introdu.awt
Introdu.awtIntrodu.awt
Introdu.awt
 
Jdbc
JdbcJdbc
Jdbc
 
Java session01
Java session01Java session01
Java session01
 
Java Swing
Java SwingJava Swing
Java Swing
 
Type Annotations in Java 8
Type Annotations in Java 8 Type Annotations in Java 8
Type Annotations in Java 8
 
Java session11
Java session11Java session11
Java session11
 
Module 1: Introduction to .NET Framework 3.5 (Material)
Module 1: Introduction to .NET Framework 3.5 (Material)Module 1: Introduction to .NET Framework 3.5 (Material)
Module 1: Introduction to .NET Framework 3.5 (Material)
 
Basic of Applet
Basic of AppletBasic of Applet
Basic of Applet
 
Annotations
AnnotationsAnnotations
Annotations
 
Java session08
Java session08Java session08
Java session08
 
Java session02
Java session02Java session02
Java session02
 
An Introduction To Unit Testing and TDD
An Introduction To Unit Testing and TDDAn Introduction To Unit Testing and TDD
An Introduction To Unit Testing and TDD
 
06 iec t1_s1_oo_ps_session_08
06 iec t1_s1_oo_ps_session_0806 iec t1_s1_oo_ps_session_08
06 iec t1_s1_oo_ps_session_08
 
Guice tutorial
Guice tutorialGuice tutorial
Guice tutorial
 
01 iec t1_s1_oo_ps_session_01
01 iec t1_s1_oo_ps_session_0101 iec t1_s1_oo_ps_session_01
01 iec t1_s1_oo_ps_session_01
 
Java Annotation
Java AnnotationJava Annotation
Java Annotation
 
Identifing Listeners and Filters
Identifing Listeners and FiltersIdentifing Listeners and Filters
Identifing Listeners and Filters
 

Viewers also liked

Checking OpenCV with PVS-Studio
Checking OpenCV with PVS-StudioChecking OpenCV with PVS-Studio
Checking OpenCV with PVS-Studio
PVS-Studio
 
How we test the code analyzer
How we test the code analyzerHow we test the code analyzer
How we test the code analyzer
PVS-Studio
 
How to make fewer errors at the stage of code writing. Part N4.
How to make fewer errors at the stage of code writing. Part N4.How to make fewer errors at the stage of code writing. Part N4.
How to make fewer errors at the stage of code writing. Part N4.
PVS-Studio
 
The compiler is to blame for everything
The compiler is to blame for everythingThe compiler is to blame for everything
The compiler is to blame for everything
PVS-Studio
 
Why Windows 8 drivers are buggy
Why Windows 8 drivers are buggyWhy Windows 8 drivers are buggy
Why Windows 8 drivers are buggy
PVS-Studio
 
Comparing the general static analysis in Visual Studio 2010 and PVS-Studio by...
Comparing the general static analysis in Visual Studio 2010 and PVS-Studio by...Comparing the general static analysis in Visual Studio 2010 and PVS-Studio by...
Comparing the general static analysis in Visual Studio 2010 and PVS-Studio by...
PVS-Studio
 
Cppcheck and PVS-Studio compared
Cppcheck and PVS-Studio comparedCppcheck and PVS-Studio compared
Cppcheck and PVS-Studio compared
PVS-Studio
 
100 bugs in Open Source C/C++ projects
100 bugs in Open Source C/C++ projects100 bugs in Open Source C/C++ projects
100 bugs in Open Source C/C++ projects
PVS-Studio
 
PVS-Studio vs Chromium
PVS-Studio vs ChromiumPVS-Studio vs Chromium
PVS-Studio vs Chromium
PVS-Studio
 
Errors detected in the Visual C++ 2012 libraries
Errors detected in the Visual C++ 2012 librariesErrors detected in the Visual C++ 2012 libraries
Errors detected in the Visual C++ 2012 libraries
PVS-Studio
 
R&D on PVS-Studio
R&D on PVS-StudioR&D on PVS-Studio
R&D on PVS-Studio
PVS-Studio
 
Analysis of the Trans-Proteomic Pipeline (TPP) project
Analysis of the Trans-Proteomic Pipeline (TPP) projectAnalysis of the Trans-Proteomic Pipeline (TPP) project
Analysis of the Trans-Proteomic Pipeline (TPP) project
PVS-Studio
 
Monitoring a program that monitors computer networks
Monitoring a program that monitors computer networksMonitoring a program that monitors computer networks
Monitoring a program that monitors computer networks
PVS-Studio
 
64-bit
64-bit64-bit
64-bit
PVS-Studio
 
A few words about OpenSSL
A few words about OpenSSLA few words about OpenSSL
A few words about OpenSSL
PVS-Studio
 
Integrating into Visual Studio settings
Integrating into Visual Studio settingsIntegrating into Visual Studio settings
Integrating into Visual Studio settings
PVS-Studio
 
PVS-Studio: analyzing ReactOS's code
PVS-Studio: analyzing ReactOS's codePVS-Studio: analyzing ReactOS's code
PVS-Studio: analyzing ReactOS's code
PVS-Studio
 
Regular use of static code analysis in team development
Regular use of static code analysis in team developmentRegular use of static code analysis in team development
Regular use of static code analysis in team development
PVS-Studio
 
How to make fewer errors at the stage of code writing. Part N3.
How to make fewer errors at the stage of code writing. Part N3.How to make fewer errors at the stage of code writing. Part N3.
How to make fewer errors at the stage of code writing. Part N3.
PVS-Studio
 
Visual Studio commands
Visual Studio commandsVisual Studio commands
Visual Studio commands
PVS-Studio
 

Viewers also liked (20)

Checking OpenCV with PVS-Studio
Checking OpenCV with PVS-StudioChecking OpenCV with PVS-Studio
Checking OpenCV with PVS-Studio
 
How we test the code analyzer
How we test the code analyzerHow we test the code analyzer
How we test the code analyzer
 
How to make fewer errors at the stage of code writing. Part N4.
How to make fewer errors at the stage of code writing. Part N4.How to make fewer errors at the stage of code writing. Part N4.
How to make fewer errors at the stage of code writing. Part N4.
 
The compiler is to blame for everything
The compiler is to blame for everythingThe compiler is to blame for everything
The compiler is to blame for everything
 
Why Windows 8 drivers are buggy
Why Windows 8 drivers are buggyWhy Windows 8 drivers are buggy
Why Windows 8 drivers are buggy
 
Comparing the general static analysis in Visual Studio 2010 and PVS-Studio by...
Comparing the general static analysis in Visual Studio 2010 and PVS-Studio by...Comparing the general static analysis in Visual Studio 2010 and PVS-Studio by...
Comparing the general static analysis in Visual Studio 2010 and PVS-Studio by...
 
Cppcheck and PVS-Studio compared
Cppcheck and PVS-Studio comparedCppcheck and PVS-Studio compared
Cppcheck and PVS-Studio compared
 
100 bugs in Open Source C/C++ projects
100 bugs in Open Source C/C++ projects100 bugs in Open Source C/C++ projects
100 bugs in Open Source C/C++ projects
 
PVS-Studio vs Chromium
PVS-Studio vs ChromiumPVS-Studio vs Chromium
PVS-Studio vs Chromium
 
Errors detected in the Visual C++ 2012 libraries
Errors detected in the Visual C++ 2012 librariesErrors detected in the Visual C++ 2012 libraries
Errors detected in the Visual C++ 2012 libraries
 
R&D on PVS-Studio
R&D on PVS-StudioR&D on PVS-Studio
R&D on PVS-Studio
 
Analysis of the Trans-Proteomic Pipeline (TPP) project
Analysis of the Trans-Proteomic Pipeline (TPP) projectAnalysis of the Trans-Proteomic Pipeline (TPP) project
Analysis of the Trans-Proteomic Pipeline (TPP) project
 
Monitoring a program that monitors computer networks
Monitoring a program that monitors computer networksMonitoring a program that monitors computer networks
Monitoring a program that monitors computer networks
 
64-bit
64-bit64-bit
64-bit
 
A few words about OpenSSL
A few words about OpenSSLA few words about OpenSSL
A few words about OpenSSL
 
Integrating into Visual Studio settings
Integrating into Visual Studio settingsIntegrating into Visual Studio settings
Integrating into Visual Studio settings
 
PVS-Studio: analyzing ReactOS's code
PVS-Studio: analyzing ReactOS's codePVS-Studio: analyzing ReactOS's code
PVS-Studio: analyzing ReactOS's code
 
Regular use of static code analysis in team development
Regular use of static code analysis in team developmentRegular use of static code analysis in team development
Regular use of static code analysis in team development
 
How to make fewer errors at the stage of code writing. Part N3.
How to make fewer errors at the stage of code writing. Part N3.How to make fewer errors at the stage of code writing. Part N3.
How to make fewer errors at the stage of code writing. Part N3.
 
Visual Studio commands
Visual Studio commandsVisual Studio commands
Visual Studio commands
 

Similar to Visual Studio Automation Object Model. EnvDTE interfaces

Ide overview
Ide overviewIde overview
Ide overview
Elhabib Atiea
 
Software development effort reduction with Co-op
Software development effort reduction with Co-opSoftware development effort reduction with Co-op
Software development effort reduction with Co-op
lbergmans
 
Extend Eclipse p2 framework capabilities: Add your custom installation steps
Extend Eclipse p2 framework capabilities: Add your custom installation stepsExtend Eclipse p2 framework capabilities: Add your custom installation steps
Extend Eclipse p2 framework capabilities: Add your custom installation steps
Dragos_Mihailescu
 
Use Eclipse technologies to build a modern embedded IDE
Use Eclipse technologies to build a modern embedded IDEUse Eclipse technologies to build a modern embedded IDE
Use Eclipse technologies to build a modern embedded IDE
Benjamin Cabé
 
Dot net interview_questions
Dot net interview_questionsDot net interview_questions
Dot net interview_questions
nehadhamecha
 
Dot net interview_questions
Dot net interview_questionsDot net interview_questions
Dot net interview_questions
Jayesh Kheradia
 
Visual Studio tool windows
Visual Studio tool windowsVisual Studio tool windows
Visual Studio tool windows
PVS-Studio
 
Ef Poco And Unit Testing
Ef Poco And Unit TestingEf Poco And Unit Testing
Ef Poco And Unit Testing
James Phillips
 
Dot net interview_questions
Dot net interview_questionsDot net interview_questions
Dot net interview_questions
9292929292
 
Plug yourself in and your app will never be the same (1 hr edition)
Plug yourself in and your app will never be the same (1 hr edition)Plug yourself in and your app will never be the same (1 hr edition)
Plug yourself in and your app will never be the same (1 hr edition)
Mikkel Flindt Heisterberg
 
Android Tutorial
Android TutorialAndroid Tutorial
Android Tutorial
Fun2Do Labs
 
Bridging the gap – plugin for unity and i os
Bridging the gap – plugin for unity and i osBridging the gap – plugin for unity and i os
Bridging the gap – plugin for unity and i os
Zuaib
 
Spring boot
Spring bootSpring boot
Object-oriented programming (OOP) with Complete understanding modules
Object-oriented programming (OOP) with Complete understanding modulesObject-oriented programming (OOP) with Complete understanding modules
Object-oriented programming (OOP) with Complete understanding modules
Durgesh Singh
 
Repository Pattern in MVC3 Application with Entity Framework
Repository Pattern in MVC3 Application with Entity FrameworkRepository Pattern in MVC3 Application with Entity Framework
Repository Pattern in MVC3 Application with Entity Framework
Akhil Mittal
 
Generic Repository Pattern in MVC3 Application with Entity Framework
Generic Repository Pattern in MVC3 Application with Entity FrameworkGeneric Repository Pattern in MVC3 Application with Entity Framework
Generic Repository Pattern in MVC3 Application with Entity Framework
Akhil Mittal
 
Patterns (contd)Software Development ProcessDesign patte.docx
Patterns (contd)Software Development ProcessDesign patte.docxPatterns (contd)Software Development ProcessDesign patte.docx
Patterns (contd)Software Development ProcessDesign patte.docx
danhaley45372
 
Readme
ReadmeReadme
Readme
rec2006
 
Visual C++ project model
Visual C++ project modelVisual C++ project model
Visual C++ project model
PVS-Studio
 
MODEL DRIVEN ARCHITECTURE, CONTROL SYSTEMS AND ECLIPSE
MODEL DRIVEN ARCHITECTURE, CONTROL SYSTEMS AND ECLIPSEMODEL DRIVEN ARCHITECTURE, CONTROL SYSTEMS AND ECLIPSE
MODEL DRIVEN ARCHITECTURE, CONTROL SYSTEMS AND ECLIPSE
Anže Vodovnik
 

Similar to Visual Studio Automation Object Model. EnvDTE interfaces (20)

Ide overview
Ide overviewIde overview
Ide overview
 
Software development effort reduction with Co-op
Software development effort reduction with Co-opSoftware development effort reduction with Co-op
Software development effort reduction with Co-op
 
Extend Eclipse p2 framework capabilities: Add your custom installation steps
Extend Eclipse p2 framework capabilities: Add your custom installation stepsExtend Eclipse p2 framework capabilities: Add your custom installation steps
Extend Eclipse p2 framework capabilities: Add your custom installation steps
 
Use Eclipse technologies to build a modern embedded IDE
Use Eclipse technologies to build a modern embedded IDEUse Eclipse technologies to build a modern embedded IDE
Use Eclipse technologies to build a modern embedded IDE
 
Dot net interview_questions
Dot net interview_questionsDot net interview_questions
Dot net interview_questions
 
Dot net interview_questions
Dot net interview_questionsDot net interview_questions
Dot net interview_questions
 
Visual Studio tool windows
Visual Studio tool windowsVisual Studio tool windows
Visual Studio tool windows
 
Ef Poco And Unit Testing
Ef Poco And Unit TestingEf Poco And Unit Testing
Ef Poco And Unit Testing
 
Dot net interview_questions
Dot net interview_questionsDot net interview_questions
Dot net interview_questions
 
Plug yourself in and your app will never be the same (1 hr edition)
Plug yourself in and your app will never be the same (1 hr edition)Plug yourself in and your app will never be the same (1 hr edition)
Plug yourself in and your app will never be the same (1 hr edition)
 
Android Tutorial
Android TutorialAndroid Tutorial
Android Tutorial
 
Bridging the gap – plugin for unity and i os
Bridging the gap – plugin for unity and i osBridging the gap – plugin for unity and i os
Bridging the gap – plugin for unity and i os
 
Spring boot
Spring bootSpring boot
Spring boot
 
Object-oriented programming (OOP) with Complete understanding modules
Object-oriented programming (OOP) with Complete understanding modulesObject-oriented programming (OOP) with Complete understanding modules
Object-oriented programming (OOP) with Complete understanding modules
 
Repository Pattern in MVC3 Application with Entity Framework
Repository Pattern in MVC3 Application with Entity FrameworkRepository Pattern in MVC3 Application with Entity Framework
Repository Pattern in MVC3 Application with Entity Framework
 
Generic Repository Pattern in MVC3 Application with Entity Framework
Generic Repository Pattern in MVC3 Application with Entity FrameworkGeneric Repository Pattern in MVC3 Application with Entity Framework
Generic Repository Pattern in MVC3 Application with Entity Framework
 
Patterns (contd)Software Development ProcessDesign patte.docx
Patterns (contd)Software Development ProcessDesign patte.docxPatterns (contd)Software Development ProcessDesign patte.docx
Patterns (contd)Software Development ProcessDesign patte.docx
 
Readme
ReadmeReadme
Readme
 
Visual C++ project model
Visual C++ project modelVisual C++ project model
Visual C++ project model
 
MODEL DRIVEN ARCHITECTURE, CONTROL SYSTEMS AND ECLIPSE
MODEL DRIVEN ARCHITECTURE, CONTROL SYSTEMS AND ECLIPSEMODEL DRIVEN ARCHITECTURE, CONTROL SYSTEMS AND ECLIPSE
MODEL DRIVEN ARCHITECTURE, CONTROL SYSTEMS AND ECLIPSE
 

Recently uploaded

JavaLand 2024: Application Development Green Masterplan
JavaLand 2024: Application Development Green MasterplanJavaLand 2024: Application Development Green Masterplan
JavaLand 2024: Application Development Green Masterplan
Miro Wengner
 
AWS Certified Solutions Architect Associate (SAA-C03)
AWS Certified Solutions Architect Associate (SAA-C03)AWS Certified Solutions Architect Associate (SAA-C03)
AWS Certified Solutions Architect Associate (SAA-C03)
HarpalGohil4
 
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectorsConnector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
DianaGray10
 
Session 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdfSession 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdf
UiPathCommunity
 
What is an RPA CoE? Session 2 – CoE Roles
What is an RPA CoE?  Session 2 – CoE RolesWhat is an RPA CoE?  Session 2 – CoE Roles
What is an RPA CoE? Session 2 – CoE Roles
DianaGray10
 
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance PanelsNorthern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving
 
Christine's Supplier Sourcing Presentaion.pptx
Christine's Supplier Sourcing Presentaion.pptxChristine's Supplier Sourcing Presentaion.pptx
Christine's Supplier Sourcing Presentaion.pptx
christinelarrosa
 
Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |
AstuteBusiness
 
Lee Barnes - Path to Becoming an Effective Test Automation Engineer.pdf
Lee Barnes - Path to Becoming an Effective Test Automation Engineer.pdfLee Barnes - Path to Becoming an Effective Test Automation Engineer.pdf
Lee Barnes - Path to Becoming an Effective Test Automation Engineer.pdf
leebarnesutopia
 
What is an RPA CoE? Session 1 – CoE Vision
What is an RPA CoE?  Session 1 – CoE VisionWhat is an RPA CoE?  Session 1 – CoE Vision
What is an RPA CoE? Session 1 – CoE Vision
DianaGray10
 
Call Girls Chandigarh🔥7023059433🔥Agency Profile Escorts in Chandigarh Availab...
Call Girls Chandigarh🔥7023059433🔥Agency Profile Escorts in Chandigarh Availab...Call Girls Chandigarh🔥7023059433🔥Agency Profile Escorts in Chandigarh Availab...
Call Girls Chandigarh🔥7023059433🔥Agency Profile Escorts in Chandigarh Availab...
manji sharman06
 
GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)
Javier Junquera
 
A Deep Dive into ScyllaDB's Architecture
A Deep Dive into ScyllaDB's ArchitectureA Deep Dive into ScyllaDB's Architecture
A Deep Dive into ScyllaDB's Architecture
ScyllaDB
 
Introducing BoxLang : A new JVM language for productivity and modularity!
Introducing BoxLang : A new JVM language for productivity and modularity!Introducing BoxLang : A new JVM language for productivity and modularity!
Introducing BoxLang : A new JVM language for productivity and modularity!
Ortus Solutions, Corp
 
"$10 thousand per minute of downtime: architecture, queues, streaming and fin...
"$10 thousand per minute of downtime: architecture, queues, streaming and fin..."$10 thousand per minute of downtime: architecture, queues, streaming and fin...
"$10 thousand per minute of downtime: architecture, queues, streaming and fin...
Fwdays
 
Day 2 - Intro to UiPath Studio Fundamentals
Day 2 - Intro to UiPath Studio FundamentalsDay 2 - Intro to UiPath Studio Fundamentals
Day 2 - Intro to UiPath Studio Fundamentals
UiPathCommunity
 
QR Secure: A Hybrid Approach Using Machine Learning and Security Validation F...
QR Secure: A Hybrid Approach Using Machine Learning and Security Validation F...QR Secure: A Hybrid Approach Using Machine Learning and Security Validation F...
QR Secure: A Hybrid Approach Using Machine Learning and Security Validation F...
AlexanderRichford
 
AI in the Workplace Reskilling, Upskilling, and Future Work.pptx
AI in the Workplace Reskilling, Upskilling, and Future Work.pptxAI in the Workplace Reskilling, Upskilling, and Future Work.pptx
AI in the Workplace Reskilling, Upskilling, and Future Work.pptx
Sunil Jagani
 
AppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSFAppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSF
Ajin Abraham
 
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
Jason Yip
 

Recently uploaded (20)

JavaLand 2024: Application Development Green Masterplan
JavaLand 2024: Application Development Green MasterplanJavaLand 2024: Application Development Green Masterplan
JavaLand 2024: Application Development Green Masterplan
 
AWS Certified Solutions Architect Associate (SAA-C03)
AWS Certified Solutions Architect Associate (SAA-C03)AWS Certified Solutions Architect Associate (SAA-C03)
AWS Certified Solutions Architect Associate (SAA-C03)
 
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectorsConnector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
 
Session 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdfSession 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdf
 
What is an RPA CoE? Session 2 – CoE Roles
What is an RPA CoE?  Session 2 – CoE RolesWhat is an RPA CoE?  Session 2 – CoE Roles
What is an RPA CoE? Session 2 – CoE Roles
 
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance PanelsNorthern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
Northern Engraving | Modern Metal Trim, Nameplates and Appliance Panels
 
Christine's Supplier Sourcing Presentaion.pptx
Christine's Supplier Sourcing Presentaion.pptxChristine's Supplier Sourcing Presentaion.pptx
Christine's Supplier Sourcing Presentaion.pptx
 
Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |
 
Lee Barnes - Path to Becoming an Effective Test Automation Engineer.pdf
Lee Barnes - Path to Becoming an Effective Test Automation Engineer.pdfLee Barnes - Path to Becoming an Effective Test Automation Engineer.pdf
Lee Barnes - Path to Becoming an Effective Test Automation Engineer.pdf
 
What is an RPA CoE? Session 1 – CoE Vision
What is an RPA CoE?  Session 1 – CoE VisionWhat is an RPA CoE?  Session 1 – CoE Vision
What is an RPA CoE? Session 1 – CoE Vision
 
Call Girls Chandigarh🔥7023059433🔥Agency Profile Escorts in Chandigarh Availab...
Call Girls Chandigarh🔥7023059433🔥Agency Profile Escorts in Chandigarh Availab...Call Girls Chandigarh🔥7023059433🔥Agency Profile Escorts in Chandigarh Availab...
Call Girls Chandigarh🔥7023059433🔥Agency Profile Escorts in Chandigarh Availab...
 
GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)
 
A Deep Dive into ScyllaDB's Architecture
A Deep Dive into ScyllaDB's ArchitectureA Deep Dive into ScyllaDB's Architecture
A Deep Dive into ScyllaDB's Architecture
 
Introducing BoxLang : A new JVM language for productivity and modularity!
Introducing BoxLang : A new JVM language for productivity and modularity!Introducing BoxLang : A new JVM language for productivity and modularity!
Introducing BoxLang : A new JVM language for productivity and modularity!
 
"$10 thousand per minute of downtime: architecture, queues, streaming and fin...
"$10 thousand per minute of downtime: architecture, queues, streaming and fin..."$10 thousand per minute of downtime: architecture, queues, streaming and fin...
"$10 thousand per minute of downtime: architecture, queues, streaming and fin...
 
Day 2 - Intro to UiPath Studio Fundamentals
Day 2 - Intro to UiPath Studio FundamentalsDay 2 - Intro to UiPath Studio Fundamentals
Day 2 - Intro to UiPath Studio Fundamentals
 
QR Secure: A Hybrid Approach Using Machine Learning and Security Validation F...
QR Secure: A Hybrid Approach Using Machine Learning and Security Validation F...QR Secure: A Hybrid Approach Using Machine Learning and Security Validation F...
QR Secure: A Hybrid Approach Using Machine Learning and Security Validation F...
 
AI in the Workplace Reskilling, Upskilling, and Future Work.pptx
AI in the Workplace Reskilling, Upskilling, and Future Work.pptxAI in the Workplace Reskilling, Upskilling, and Future Work.pptx
AI in the Workplace Reskilling, Upskilling, and Future Work.pptx
 
AppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSFAppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSF
 
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
[OReilly Superstream] Occupy the Space: A grassroots guide to engineering (an...
 

Visual Studio Automation Object Model. EnvDTE interfaces

  • 1. Visual Studio Automation Object Model. EnvDTE interfaces. Author: Paul Eremeev Date: 18.10.2012 Abstract This article contains an overview of Visual Studio Automation Object Model. Model's overall structure and the means of obtaining access to its interfaces through DTE/DTE2 top level objects are examined. Several examples of utilizing elements of the model are provided. Also discussed are the issues of using model's interfaces within multithreaded applications; an example of implementing such mechanism for multithreaded interaction with COM interfaces in managed code is provided as well. Introduction Visual Studio development environment is built upon the principles of automation and extensibility, providing the developers using it with the ability of integrating almost any custom element into the IDE and allowing for an easy interaction with its default and user-created components. As the means of implementing these tasks, Visual Studio users are provided with several cross-complementing toolsets, the most basic and versatile among these is the Visual Studio Automation Object Model. Automation Object Model is represented by a series of libraries containing a vast and well-structured API set which covers all aspects of IDE automation and the majority of its extensibility capabilities. Although, in comparison to other IDE extensibility tools, this model does not provide access to some portions of Visual Studio (this applies mostly to the extension of some IDE's features), it is nonetheless the most flexible and versatile among them. The majority of the model's interfaces are accessible from within every type of IDE extension module, which allows interacting with the environment even from an external independent process. Moreover, the model itself could be extended along with the extension of Visual Studio IDE, providing other third- party developers with an access to user-created custom components. Automation Object Model structure Visual Studio automation model is composed of several interconnected functional object groups covering all aspects of the development environment; it also provides capabilities for controlling and extending these groups. Accessing any of them is possible through the top-level global DTE interface (Development Tools Environment). Figure 1 shows the overall structure of the automation model and how it is divided among functionality groups.
  • 2. Figure 1 — Visual Studio Automation Object Model (click the picture to zoom in)
  • 3. The model itself could be extended by user in one of the following groups: • project models (implementing new project types, support for new languages); • document models (implementing new document types and document editors) • code editor level models (support for specific language constructs) • project build-level models Automation model could be extended from plug-ins of VSPackage type only. Despite the model's versatility, not every group belonging to the model could be equally utilized from all the types of IDE extensions. For instance, some of the model's capabilities are inaccessible to external processes; these capabilities are tied to specific extension types, such as Add-In or VSPackage. Therefore, when selecting the type for the extension to be developed, it is important to consider the functionality that this extension will require. Obtaining references to DTE/DTE2 objects. In order to create a Visual Studio automation application it is necessary to obtain access to the automation objects themselves in the first place. To accomplish this, first of all it is necessary to hook up the correct versions of libraries containing the required managed API wrappers in the EnvDTE namespace. Secondly, the reference to the automation model top-level object, that is the DTE2 interface, should be obtained. In the course of Visual Studio evolution, several of its automation objects had been modified or received some additional functionality. So, to maintain a backward compatibility with existing extension packages, new EnvDTE80, EnvDTE90, EnvDTE100 etc. namespaces were created instead of updating the interfaces from the original EnvDTE namespace. The majority of such updated interfaces from these new namespaces do maintain the same names as in the original ones, but with addition of an ordinal number at the end of the name, for example Solution and Solution2. It is advised that these updated interfaces should be utilized when creating a new project, as they do contain the most recent functionality. It's worth noting that properties and methods of DTE2 interface usually return object references with types corresponding to the original DTE, i.e. accessing dte2.Solution will return Solution and not the Solution2 as it would seem. Although these new EnvDTE80, EnvDTE90, EnvDTE100 namespaces do contain some of the updated functionality as mentioned above, still it is the EnvDTE interface that contains the majority of automation objects. Therefore, in order to possess access to all of the existing interfaces, it is necessary to link all versions of the managed COM wrapper libraries to the project, as well as to obtain the references to DTE and also to DTE2. The way of obtaining top-level EnvDTE object reference is dependent upon the type of IDE extension being developed. Let's examine 3 of such extension types: Add-In, VSPackage and an MSVS-independent external process. Add-In extension In the case of an Add-In extension, access to the DTE interface can be obtained inside the OnConnection method which should be implemented for the IDTExtensibility interface that provides access to the extension-environment interaction events. The OnConnection method is called at the moment when the module is loaded by the IDE; it can happen either when the environment is being loaded itself or after
  • 4. the extension was called for the first time in the IDE session. The example of obtaining the reference follows: public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom) { _dte2 = (DTE2)application; ... } An Add-In module can be initialized either at the moment of IDE start-up, or when it is called for the first time in current IDE session. So, the connectMode can be used to correctly determine the moment of initialization inside the OnConnection method. switch(connectMode) { case ext_ConnectMode.ext_cm_UISetup: ... break; case ext_ConnectMode.ext_cm_Startup: ... break; case ext_ConnectMode.ext_cm_AfterStartup: ... break; case ext_ConnectMode.ext_cm_CommandLine: ... break; } As in the example above, add-In could be loaded either simultaneously with the IDE itself (if the startup option in the Add-In manager is checked), when it is called the first time or when it is called through the command line. The ext_ConnectMode.ext_cm_UISetup option is invoked only for a single time in the plug-in's overall lifetime, which is during its first initialization. This case should be used for initializing user UI elements which are to be integrated into the environment (more on this later on). If an Add-In is being loaded during Visual Studio start-up (ext_ConnectMode.ext_cm_Startup), then at the moment OnConnect method receives control for the first time, it is possible that the IDE still is not fully initialized itself. In such a case, it is advised to postpone the acquisition of the DTE reference until the environment is fully loaded. The OnStartupComplete handler provided by the IDTExtensibility can be used for this. public void OnStartupComplete(ref Array custom) { ... }
  • 5. VSPackage extension For VSPackage type of extension, the DTE could be obtained through the global Visual Studio service with the help of GetService method of a Package subclass: DTE dte = MyPackage.GetService(typeof(DTE)) as DTE; Please note that the GetService method could potentially return null in case Visual Studio is not fully loaded or initialized at the moment of such access, i.e. it is in the so called "zombie" state. To correctly handle this situation, it is advised that the acquisition of DTE reference should be postponed until this interface is inquired. But in case the DTE reference is required inside the Initialize method itself, the IVsShellPropertyEvents interface can be utilized (also by deriving our Package subclass from it) and then the reference could be safely obtained inside the OnShellPropertyChange handler. DTE dte; uint cookie; protected override void Initialize() { base.Initialize(); IVsShell shellService = GetService(typeof(SVsShell)) as IVsShell; if (shellService != null) ErrorHandler.ThrowOnFailure( shellService.AdviseShellPropertyChanges(this,out cookie)); ... } public int OnShellPropertyChange(int propid, object var) { // when zombie state changes to false, finish package initialization if ((int)__VSSPROPID.VSSPROPID_Zombie == propid) { if ((bool)var == false) { this.dte = GetService(typeof(SDTE)) as DTE; IVsShell shellService = GetService(typeof(SVsShell)) as IVsShell; if (shellService != null) ErrorHandler.ThrowOnFailure( shellService.UnadviseShellPropertyChanges(this.cookie) ); this.cookie = 0; } } return VSConstants.S_OK; } It should be noted that the process of VSPackage module initialization at IDE startup could vary for different Visual Studio versions. For instance, in case of VS2005 and VS2008, an attempt at accessing DTE during IDE startup will almost always result in null being returned, owning to the relative fast loading times of these versions. But, one does not simply obtain access into DTE. In Visual Studio 2010 case, it mistakenly appears that one could simply obtain an access to the DTE from inside the Initialize()
  • 6. method. In fact, this impression is a false one, as such method of DTE acquisition could potentially cause the occasional appearance of "floating" errors which are hard to identify and debug, and even the DTE itself may be still uninitialized when the reference is acquired. Because of these disparities, the aforementioned acquisition method for handling IDE loading states should not be ignored on any version of Visual Studio. Independent external process The DTE interface is a top-level abstraction for Visual Studio environment in the automation model. In order to acquire a reference to this interface from an external application, its ProgID COM identifier could be utilized; for instance, it will be "VisualStudio.DTE.10.0" for Visual Studio 2010. Consider this example of initializing a new IDE instance and when obtaining a reference to the DTE interface. // Get the ProgID for DTE 8.0. System.Type t = System.Type.GetTypeFromProgID( "VisualStudio.DTE.10.0", true); // Create a new instance of the IDE. object obj = System.Activator.CreateInstance(t, true); // Cast the instance to DTE2 and assign to variable dte. EnvDTE80.DTE2 dte = (EnvDTE80.DTE2)obj; // Show IDE Main Window dte.MainWindow.Activate(); In the example above we've actually created a new DTE object, starting deven.exe process by the CreateInstance method. But at the same time, the GUI window of the environment will be displayed only after the Activate method is called. Next, let's review a simple example of obtaining the DTE reference from an already running Visual Studio Instance: EnvDTE80.DTE2 dte2; dte2 = (EnvDTE80.DTE2) System.Runtime.InteropServices.Marshal.GetActiveObject( "VisualStudio.DTE.10.0"); However, in case several instances of the Visual Studio are executing at the moment of our inquiry, the GetActiveObject method will return a reference to the IDE instance that was started the earliest. Let's examine a possible way of obtaining the reference to DTE from a running Visual Studio instance by the PID of its process. using EnvDTE80; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; [DllImport("ole32.dll")] private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc); [DllImport("ole32.dll")] private static extern void GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
  • 7. public static DTE2 GetByID(int ID) { //rot entry for visual studio running under current process. string rotEntry = String.Format("!VisualStudio.DTE.10.0:{0}", ID); IRunningObjectTable rot; GetRunningObjectTable(0, out rot); IEnumMoniker enumMoniker; rot.EnumRunning(out enumMoniker); enumMoniker.Reset(); IntPtr fetched = IntPtr.Zero; IMoniker[] moniker = new IMoniker[1]; while (enumMoniker.Next(1, moniker, fetched) == 0) { IBindCtx bindCtx; CreateBindCtx(0, out bindCtx); string displayName; moniker[0].GetDisplayName(bindCtx, null, out displayName); if (displayName == rotEntry) { object comObject; rot.GetObject(moniker[0], out comObject); return (EnvDTE80.DTE2)comObject; } } return null; } Here we've acquired the DTE interface by identifying the required instance of the IDE in the table of running COM objects (ROT, Running Object Table) by its process identifier. Now we can access the DTE for every of the executing instances of Visual Studio, for example: Process Devenv; ... //Get DTE by Process ID EnvDTE80.DTE2 dte2 = GetByID(Devenv.Id); Additionally, to acquire any project-specific interface (including custom model extensions), for example the CSharpProjects model, through a valid DTE interface, the GetObject method should be utilized: Projects projects = (Projects)dte.GetObject("CSharpProjects"); The GetObject method will return a Projects collection of regular Project objects, and each one of them will contain a reference to our project-specific properties, among other regular ones. Visual Studio text editor documents Automation model represents Visual Studio text documents through the TextDocument interface. For example, C/C++ source code files are opened by the environment as text documents. TextDocument is based upon the common automation model document interface (the Document interface), which represents file of any type opened in Visual Studio editor or designer. A reference to the text document
  • 8. object can be obtained through the 'Object' field of the Document object. Let's acquire a text document for the currently active (i.e. the one possessing focus) document from IDE's text editor. EnvDTE.TextDocument objTextDoc = (TextDocument)PVSStudio.DTE.ActiveDocument.Object("TextDocument"); Modifying documents The TextSelection document allows controlling text selection or to modify it. The methods of this interface represent the functionality of Visual Studio text editor, i.e. they allow the interaction with the text as it presented directly by the UI. EnvDTE.TextDocument Doc = (TextDocument)PVSStudio.DTE.ActiveDocument.Object(string.Empty); Doc.Selection.SelectLine(); TextSelection Sel = Doc.Selection; int CurLine = Sel.TopPoint.Line; String Text = Sel.Text; Sel.Insert("testrn"); In this example we selected a text line under the cursor, read the selected text and replaced it with a 'test' string. TextDocument interface also allows text modification through the EditPoint interface. This interface is somewhat similar to the TextSelection, but instead of operating with the text through the editor UI, it directly manipulates text buffer data. The difference between them is that the text buffer is not influenced by such editor-specific notions as WordWrap and Virtual Spaces. It should be noted that both of these editing methods are not able to modify read-only text blocks. Let's examine the example of modifying text with EditPoint by placing additional lines at the end of current line with a cursor. objEditPt = objTextDoc.StartPoint.CreateEditPoint(); int lineNumber = objTextDoc.Selection.CurrentLine; objEditPt.LineDown(lineNumber - 1); EditPoint objEditPt2 = objTextDoc.StartPoint.CreateEditPoint(); objEditPt2.LineDown(lineNumber - 1); objEditPt2.CharRight(objEditPt2.LineLength); String line = objEditPt.GetText(objEditPt.LineLength); String newLine = line + "test"; objEditPt.ReplaceText(objEditPt2, newLine, (int)vsEPReplaceTextOptions.vsEPReplaceTextKeepMarkers); Navigating the documents VSPackage modules are able to obtain access to a series of global services which could be used for opening and handling environment documents. These services could be acquired by the Package.GetGlobalService() method. It should be noted that the services described here are not part of the DTE model and are accessible only from a Package-type extension, and therefore they could not be utilized in other types of Visual Studio extensions. Nonetheless, they can be quite useful for handling IDE
  • 9. documents when they are utilized in addition to the Documents interface described earlier. Next, we'll examine these services in more detail. The IVsUIShellOpenDocument interface controls the state of documents opened in the environment. Following is the example that uses this interface to open a document through path to a file which this document will represent. String path = "C:Testtest.cpp"; IVsUIShellOpenDocument openDoc = Package.GetGlobalService(typeof(IVsUIShellOpenDocument)) as IVsUIShellOpenDocument; IVsWindowFrame frame; Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp; IVsUIHierarchy hier; uint itemid; Guid logicalView = VSConstants.LOGVIEWID_Code; if (ErrorHandler.Failed( openDoc.OpenDocumentViaProject(path, ref logicalView, out sp, out hier, out itemid, out frame)) || frame == null) { return; } object docData; frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out docData); The file will be opened in a new editor or will receive focus in case it already has been opened earlier. Next, let's read a VsTextBuffer text buffer from this document we opened: // Get the VsTextBuffer VsTextBuffer buffer = docData as VsTextBuffer; if (buffer == null) { IVsTextBufferProvider bufferProvider = docData as IVsTextBufferProvider; if (bufferProvider != null) { IVsTextLines lines; ErrorHandler.ThrowOnFailure(bufferProvider.GetTextBuffer( out lines)); buffer = lines as VsTextBuffer; Debug.Assert(buffer != null, "IVsTextLines does not implement IVsTextBuffer"); if (buffer == null) { return; } } }
  • 10. The IVsTextManager interface controls all of the active text buffers in the environment. For example we can navigate a text document using the NavigateToLineAndColumn method of this manager on a buffer we've acquired earlier: IVsTextManager mgr = Package.GetGlobalService(typeof(VsTextManagerClass)) as IVsTextManager; mgr.NavigateToLineAndColumn(buffer, ref logicalView, line, column, line, column); Subscribing and handling events Automation objects events are represented by the DTE.Events property. This element references all of the common IDE events (such as CommandEvents, SolutionEvents), as well as the events of separate environment components (project types, editors, tools etc.), also including the ones designed by third- party developers. To acquire a reference for this automation object, the GetObject method could be utilized. When subscribing to the DTE events one should remember that this interface could be still unavailable at the moment of extension being initialized. So it is always important to consider the sequence of your extension initialization process if the access to DTE.Events is required in the Initialize() method of your extension package. The correct handling of initialization sequence will vary for different extension types, as it was described earlier. Let's acquire a reference for an events object of Visual C++ project model defined by the VCProjectEngineEvents interface and assign a handler for the removal of an element from the Solution Explorer tree: VCProjectEngineEvents m_ProjectItemsEvents = PVSStudio.DTE.Events.GetObject("VCProjectEngineEventsObject") as VCProjectEngineEvents; m_ProjectItemsEvents.ItemRemoved += new _dispVCProjectEngineEvents_ItemRemovedEventHandler( m_ProjectItemsEvents_ItemRemoved); MDI windows events The Events.WindowEvents property could be utilized to handle regular events of an environment MDI window. This interface permits the assignment of a separate handler for a single window (defined through the EnvDTE.Window interface) or the assignment of a common handler for all of the environment's windows. Following example contains the assignment of a handler for the event of switching between IDE windows: WindowEvents WE = PVSStudio.DTE.Events.WindowEvents; WE.WindowActivated += new _dispWindowEvents_WindowActivatedEventHandler( Package.WE_WindowActivated); Next example is the assignment of a handler for window switching to the currently active MDI window through WindowEvents indexer:
  • 11. WindowEvents WE = m_dte.Events.WindowEvents[MyPackage.DTE.ActiveWindow]; WE.WindowActivated += new _dispWindowEvents_WindowActivatedEventHandler( MyPackage.WE_WindowActivated); IDE commands events The actual handling of environment's commands and their extension through the automation model is covered in a separate article of this series. In this section we will examine the handling of the events related to these commands (and not of the execution of the commands themselves). Assigning the handlers to these events is possible through the Events.CommandEvents interface. The CommandEvents property, as in the case of MDI windows events, also permits the assignment of a handler either for all of the commands or for a single one through the indexer. Let's examine the assignment of a handler for the event of a command execution being complete (i.e. when the command finishes its execution): CommandEvents CEvents = DTE.Events.CommandEvents; CEvents.AfterExecute += new _dispCommandEvents_AfterExecuteEventHandler(C_AfterExecute); But in order to assign such a handler for an individual command, it is necessary to identify this command in the first place. Each command of the environment is identified by a pair of GUID:ID, and in case of a user-created commands these values are specified directly by the developer during their integration, for example through the VSCT table. Visual Studio possesses a special debug mode which allows identifying any of the environment's comamnds. To activate this mode, it is required that the following key is to be added to the system registry (an example for Visual Studio 2010): [HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio10.0General] "EnableVSIPLogging"=dword:00000001 Now, after restarting the IDE, hovering your mouse over menu or toolbar elements with CTRL+SHIFT being simultaneously pressed (though sometime it will not work until you left-click it) will display a dialog window containing all of the command's internal identifiers. We are interested in the values of Guid and CmdID. Let's examine the handling of events for the File.NewFile command: CommandEvents CEvents = DTE.Events.CommandEvents[ "{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 221]; CEvents.AfterExecute += new _dispCommandEvents_AfterExecuteEventHandler(C_AfterExecute); The handler obtained in this way will receive control only after the command execution is finished. void C_AfterExecute(string Guid, int ID, object CustomIn, object CustomOut) { ... }
  • 12. This handler should not be confused with an immediate handler for the execution of the command itself which could be assigned during this command's initialization (from an extension package and in case the command is user-created). Handling the IDE commands is described in a separate article that is entirely devoted to IDE commands. In conclusion to this section it should be mentioned that in the process of developing our own VSPackage extension, we've encountered the necessity to store the references to interface objects containing our handler delegates (such as CommandEvents, WindowEvents etc.) on the top-level fields of our main Package subclass. The reason for this is that in case of the handler being assigned through a function-level local variable, it is lost immediately after leaving the method. Such behavior could probably be attributed to the .NET garbage collector, although we've obtained these references from the DTE interface which definitely exists during the entire lifetime of our extension package. Interacting with DTE2 COM interfaces from within a multithreaded application Initially PVS-Studio extension package had not contained any specific thread-safety mechanisms for its interaction with Visual Studio APIs. At the same time, we had been attempting to confine the interactions with this APIs within a single background thread which was created and owned by our plug- in. And such approach functioned flawlessly for quite a long period. However, several bug reports from our users, each one containing a similar ComExeption error, prompted us to examine this issue in more detail and to implement a threading safety mechanism for our COM Interop. Although Visual Studio automation model is not a thread-safe one, it still provides a way for interacting with multi-threaded applications. Visual Studio application is a COM (Component Object Mode) server. For the task of handling calls from COM clients (in our case, this will be our extension package) to thread-unsafe servers, COM provides a mechanism known as STA (single-threaded apartment) model. In the terms of COM, an Apartment represents a logical container inside a process in which objects and threads share the same thread access rules. STA can hold only a single thread, but an unlimited number of objects, inside such container. Calls from other threads to such thread-unsafe objects inside STA are converted into messages and posted to a message queue. Messages are retrieved from the message queue and converted back into method calls one at a time by the thread running in the STA, so it becomes possible for only a single thread to access these unsafe objects on the server. Utilizing Apartment mechanism inside managed code The .NET Framework does not utilize COM Apartment mechanics directly. Therefore, when a managed application calls a COM object in the COM interoperation scenarios, CLR (Common Language Runtime) creates and initializes apartment container. A managed thread is able to create and enter either an MTA (multi-threaded apartment, a container that, contrary to STA, can host several threads at the same time), or an STA, though a thread will be started as an MTA by default. The type of the apartment could be specified before thread is launched: Thread t = new Thread(ThreadProc); t.SetApartmentState(ApartmentState.STA); ... t.Start();
  • 13. As an apartment type could not be changed once thread had been started, the STAThread attribute should be used to specify the main thread of a managed application as an STA: [STAThread] static void Main(string[] args) {...} Implementing message filter for COM interoperation errors in a managed environment As STA serializes all of calls to the COM server, one of the calling clients could potentially be blocked or even rejected when the server is busy, processing different calls or another thread is already inside the apartment container. In case COM server rejects its client, .NET COM interop will generate a System.Runtime.InteropServices.COMException ("The message filter indicated that the application is busy"). When working on a Visual Studio module (add-in, vspackage) or a macro, the execution control usually passes into the module from the environment's main STA UI thread (such as in case of handling events or environment state changes, etc.). Calling automation COM interfaces from this main IDE thread is safe. But if other background threads are planned to be utilized and EnvDTE COM interfaces are to be called from these background threads (as in case of long calculations that could potentially hang the IDE's interface, if these are performed on the main UI thread), then it is advised to implement a mechanism for handling calls rejected by a server. While working on PVS-Studio plug-in we've often encountered these kinds of COM exceptions in situations when other third-party extensions were active inside the IDE simultaneously with PVS-Studio plug-in. Heavy user interaction with the UI also was the usual cause for such issues. It is quite logical that these situations often resulted in simultaneous parallel calls to COM objects inside STA and consequently to the rejection of some of them. To selectively handle incoming and outgoing calls, COM provides the IMessageFilter interface. If it is implemented by the server, all of the calls are passed to the HandleIncomingCall method, and the client is informed on the rejected calls through the RetryRejectedCall method. This in turn allows the rejected calls to be repeated, or at least to correctly present this rejection to a user (for example, by displaying a dialog with a 'server is busy' message). Following is the example of implementing the rejected call handling for a managed application. [ComImport()] [Guid("00000016-0000-0000-C000-000000000046")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IMessageFilter { [PreserveSig] int HandleInComingCall( int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); [PreserveSig] int RetryRejectedCall(
  • 14. IntPtr hTaskCallee, int dwTickCount, int dwRejectType); [PreserveSig] int MessagePending( IntPtr hTaskCallee, int dwTickCount, int dwPendingType); } class MessageFilter : MarshalByRefObject, IDisposable, IMessageFilter { [DllImport("ole32.dll")] [PreserveSig] private static extern int CoRegisterMessageFilter( IMessageFilter lpMessageFilter, out IMessageFilter lplpMessageFilter); private IMessageFilter oldFilter; private const int SERVERCALL_ISHANDLED = 0; private const int PENDINGMSG_WAITNOPROCESS = 2; private const int SERVERCALL_RETRYLATER = 2; public MessageFilter() { //Starting IMessageFilter for COM objects int hr = MessageFilter.CoRegisterMessageFilter( (IMessageFilter)this, out this.oldFilter); System.Diagnostics.Debug.Assert(hr >= 0, "Registering COM IMessageFilter failed!"); } public void Dispose() { //disabling IMessageFilter IMessageFilter dummy; int hr = MessageFilter.CoRegisterMessageFilter(this.oldFilter, out dummy); System.Diagnostics.Debug.Assert(hr >= 0, "De-Registering COM IMessageFilter failed!") System.GC.SuppressFinalize(this); } int IMessageFilter.HandleInComingCall(int dwCallType, IntPtr threadIdCaller, int dwTickCount, IntPtr lpInterfaceInfo) {
  • 15. // Return the ole default (don't let the call through). return MessageFilter.SERVERCALL_ISHANDLED; } int IMessageFilter.RetryRejectedCall(IntPtr threadIDCallee, int dwTickCount, int dwRejectType) { if (dwRejectType == MessageFilter.SERVERCALL_RETRYLATER) { // Retry the thread call immediately if return >=0 & // <100. return 150; //waiting 150 mseconds until retry } // Too busy; cancel call. SERVERCALL_REJECTED return -1; //Call was rejected by callee. //(Exception from HRESULT: 0x80010001 (RPC_E_CALL_REJECTED)) } int IMessageFilter.MessagePending( IntPtr threadIDCallee, int dwTickCount, int dwPendingType) { // Perform default processing. return MessageFilter.PENDINGMSG_WAITNOPROCESS; } } Now we can utilize our MessageFilter while calling COM interfaces from a background thread: using (new MessageFilter()) { //COM-interface dependent code ... } References 1. MSDN. Referencing Automation Assemblies and the DTE2 Object. 2. MSDN. Functional Automation Groups. 3. MZ-Tools. HOWTO: Use correctly the OnConnection method of a Visual Studio add-in. 4. The Code Project. Understanding The COM Single-Threaded Apartment. 5. MZ-Tools. HOWTO: Add an event handler from a Visual Studio add-in. 6. Dr. eX's Blog. Using EnableVSIPLogging to identify menus and commands with VS 2005 + SP1. Other articles in this series 0. Introduction. 1. Creating, debugging and deploying extension packages for Microsoft Visual Studio 2005/2008/2010/2012. 2. Visual Studio Automation Object Model. EnvDTE interfaces.
  • 16. 3. Visual Studio commands. 4. Visual Studio tool windows. 5. Integrating into Visual Studio settings. 6. Visual C++ project model.