SlideShare une entreprise Scribd logo
1  sur  54
Télécharger pour lire hors ligne
Developing extension packages for Visual
Studio 2005/2008/2010/2012 using C#
with real-life samples
Author: Paul Eremeev

Date: 29.10.2012


Introduction
Creating extension packages (plug-ins) for Microsoft Visual Studio IDE appears as quite an easy task at
the first sight. There exist an excellent MSDN documentation, as well as various articles, examples and a
lot of other additional sources on this topic. But, at the same time, it could also appear as a difficult task
when an unexpected behavior is encountered along the way. Although it can be said that such issues are
quite common to any programming task, the subject of IDE plug-in development is still not thoroughly
covered at this moment.

We develop PVS-Studio static code analyzer. Although the tool itself is intended for C++ developers,
quite a large fragment of it is written in C#. When we just had been starting the development of our
plug-in, Visual Studio 2005 had been considered as modern state-of-the-art IDE. Although, at this
moment of Visual Studio 2012 release, some could say that Visual Studio 2005 is not relevant anymore,
we still provide support for this version in our tool. During our time supporting various Visual Studio
versions and exploring capabilities of the environment, we've accumulated a large practical experience
on how to correctly (and even more so incorrectly!) develop IDE plug-ins. As holding all of this
knowledge inside us was becoming unbearable, we've decided to publish it here. Some of our solutions
that seem quite obvious right now were discovered in the course of several years. And the same issues
could still haunt other plug-in developers.

By publishing this collection of small articles we wish to contribute to this area of software
development. We hope that developers interested in this topic will discover something new for
themselves or at least will use the articles as a collection of how-to's and recipes for Visual Studio
extension development.


Creating, debugging and deploying extension packages for Microsoft
Visual Studio 2005/2008/2010/2012
Abstract
This article contains the overview of several different methods for extending Visual Studio IDE. The
creation, debugging, registration and end-user deployment of Visual Studio extension packages will be
explained in detail.
Introduction
The following series of articles is dedicated to the development of the extension package for Visual
Studio 2005/2008/2010/2012 IDEs utilizing .NET framework and C# programming language. The
following topics will be covered:

       basic information on creating and debugging of MSVS plug-ins and maintaining these
        extensibility projects for several versions of Visual Studio inside a common source code base;
       overview of Automation Object Model and various Managed Package Framework (MPF) classes
       extending interface of the IDE though the automation object model's API (EnvDTE) and MPF
        (Managed Package Framework) classes with custom menus, toolbars, windows and options
        pages;
       utilizing Visual C++ project automation model for gathering data needed to operate an external
        preprocessor/compiler, such as compilation arguments and settings for different platforms and
        configurations;

The content of these articles is based on our experience in developing an MSVS extension package plug-
in for PVS-Studio static analyzer. A more detailed in-depth references for the topics covered here are
available at the end of each article through the links to MSDN library and several other external
resources.

The articles will cover the extension development only for Visual Studio 2005 and later versions. This
limitation reflects that PVS-Studio also supports integration to Visual Studio starting only from version 8
(Visual Studio 2005). The main reason behind this is that a new extensibility API model was introduced
for Visual Studio 2005, and this new version is not backward-compatible with previous IDE APIs.

Creating and debugging Visual Studio VSPackage extension modules
There exists a number of ways to extend Microsoft Visual Studio features. On the most basic level it's
possible to automate simple routine user actions using macros. An add-In plug-in module can be used
for obtaining an access to environment's UI objects, such as menu commands, windows etc. Extension
of IDE's internal editors is possible through MEF (Managed Extensibility Framework) components
(starting with MSVS 2010). Finally, a plug-in of the Extension Package type (known as VSPackage) is best
suited for integrating large independent components into Visual Studio. VSPackage allows combining
the environment automation through Automation Object Model with usage of Managed Package
Framework classes (such as Package). It also provides the means of extending the automation model
itself by registering user-defined custom automation objects within it. Such user automation objects, in
turn, will become available through the same automation model from other user-created extensibility
packages, providing these packages with access to your custom components.

In its' earlier versions, PVS-Studio plug-in (versions 1.xx and 2.xx to be precise, when it was still known as
Viva64) existed as an Add-In package. Starting with PVS-Studio 3.0 it was redesigned as VSPackage
because the functionality Add-in was able to provide became insufficient for the tasks at hand and also
the debugging process was quite inconvenient. After all, we wanted to have our own logo on Visual
Studio splash screen!

Projects for VSPackage plug-in modules. Creating the extension package.
Contrary to Add-In plug-ins, developing VS extension packages requires the installation of Microsoft
Visual Studio SDK for a targeted version of IDE, i.e. a separate SDK should be installed with every version
of Visual Studio for which an extension is being developed. We will be examining the 2005, 2008, 2009
and 2012 versions of Visual Studio. Installation of Visual Studio SDK adds a standard project template for
Visual Studio Package (on the 'Other Project Types -> Extensibility' page) to VS template manager. If
selected, this template will generate a basic MSBuild project for an extension package, allowing several
parameters to be specified beforehand, such as a programming language to be used and the automatic
generation of several stub components for generic UI elements, such as menu item, an editor, user
toolwindow etc.

We will be using a C# VSPackage project (csproj), which is a project for managed dynamic-link library
(dll). The corresponding csproj MSBuild project for this managed assembly will also contain several XML
nodes specific to a Visual Studio package, such as VSCT compiler and IncludeinVSIX (in later IDE
versions).

The main class of an extension package should be inherited from the
Microsoft.VisualStudio.Shell.Package. This base class provides managed wrappers for IDE interaction
APIs, implementation of which is required from a fully-functional Visual Studio extension package.

public sealed class MyPackage: Package
{
  public MyPackage ()
  {}
  ...
}


The Package class allows overriding of its base Initialize method. This method receives execution control
at the moment of package initialization in the current session of IDE.

protected override void Initialize()
{
  base.Initialize();

    ...
}

The initialization of the module will occur when it is invoked for the first time, but it also could be
triggered automatically, for example after IDE is started or when user enters a predefined environment
UI context state.

Being aware of the package's initialization and shutdown timings is crucial. It's quite possible that the
developer would be requesting some of Visual Studio functionality at the moment when it is still
unavailable to the package. During PVS-Studio development we've encountered several such situations
when the environment "punished us" for not understanding this, for instance, we are not allowed to
"straightforwardly" display message boxes after Visual Studio enters a shutdown process.

Debugging extension packages. Experimental Instance.
The task of debugging a plug-in module or extension intended for an integrated development
environment is not quite a typical one. Quite often such environment itself is utilized for plug-in's
development and debugging. Hooking up an unstable module to this IDE can lead to instability of the
environment itself. The necessity to uninstall a module under development from the IDE before every
debugging session, which in turn often requires restarting it, is also a major inconvenience (IDE could
block the dll that needs to be replaced by a newer version for debugging).
It should be noted that a VSPackage debugging process in this aspect is substantially easier than that of
an Add-In package. This was one of the main reasons for changing the project type of PVS-Studio plug-
in.

VSPackage solves the aforementioned development and debugging issues by utilizing Visual Studio
Experimental Instance mechanism. Such an experimental instance could be easily started by passing a
special command line argument:

"C:Program Files (x86)Microsoft Visual Studio 10.0

   Common7IDEdevenv.exe" /RootSuffix Exp

An experimental instance of the environment utilizes a separate independent Windows registry hive
(called experimental hive) for storing all of its settings and component registration data. As such, any
modifications in the IDE's settings or changes in its component registration data, which were made
inside the experimental hive, will not affect the instance which is employed for the development of the
module (that is your main regular instance which is used by default).

Visual Studio SDK provides a special tool for creating or resetting such experimental instances —
CreateExpInstance. To create a new experimental hive, it should be executed with these arguments:

CreateExpInstance.exe /Reset /VSInstance=10.0 /RootSuffix=PVSExp

Executing this command will create a new experimental registry hive with a PVSExp suffix in its name for
the 10th version of IDE (Visual Studio 2010), also resetting all of its settings to their default values in
advance. The registry path for this new instance will look as follows:

HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio10.0PVSExp

While the Exp suffix is utilized by default for package debugging inside VSPackage template project,
other experimental hives with unique names could also be created by the developer at will. To start an
instance of the environment for the hive we've created earlier (containing PVSExp in its name), these
arguments should be used:

"C:Program Files (x86)Microsoft Visual Studio 10.0

   Common7IDEdevenv.exe" /RootSuffix PVSExp

A capacity for creating several different experimental hives on a single local workstation could be quite
useful, as, for example, to provide a simultaneous and isolated development of several extension
packages.

After installing the SDK package, a link is created in the Visual Studio program's menu group for
resetting the default Experimental Instance for this version of the IDE (for instance, "Reset the Microsoft
Visual Studio 2010 Experimental Instance").

In the end, the faster you'll figure out how the debugging environment works, the fewer issues you'll
encounter in understanding how plug-in initialization works during development.

Registering and deploying Visual Studio extension packages
Registering a VS extension package requires registering a package itself, as well as registering all of the
components it integrates into the IDE (for example, menu items, option pages, user windows etc.). The
registration is accomplished by creating records corresponding to these components inside the main
system registry hive of Visual Studio.

All the information required for registration is placed, after building your VSPackage, inside a special
pkgdef file, according to several special attributes of the main class of your package (which itself should
be a subclass of the MPF 'Package' class). The pkgdef can also be created manually using the
CreatePkgDef utility. This tool collects all of the required module registration information from these
special attributes by the means of .NET reflection. Let's study these registration attributes in detail.

The PackageRegistration attribute tells the registration tool that this class is indeed a Visual Studio
extension package. Only if this attribute is discovered will the tool perform its search for additional ones.

 [PackageRegistration(UseManagedResourcesOnly = true)]

The Guid attribute specifies a unique package module identifier, which will be used for creating a
registry sub-key for this module in Visual Studio hive.

 [Guid("a0fcf0f3-577e-4c47-9847-5f152c16c02c")]

The InstalledProductRegistration attribute adds information to 'Visual Studio Help -> About' dialog and
the loading splash screen.

 [InstalledProductRegistration("#110", "#112", "1.0",

   IconResourceID = 400)]

The ProvideAutoLoad attribute links automatic module initialization with the activation of a specified
environment UI context. When a user enters this context, the package will be automatically loaded and
initialized. This is an example of setting module initialization to the opening of a solution file:

 [ProvideAutoLoad("D2567162-F94F-4091-8798-A096E61B8B50")]

The GUID values for different IDE UI contexts can be found in the
Microsoft.VisualStudio.VSConstants.UICONTEXT class.

The ProvideMenuResource attribute specifies an ID of resource that contains user created menus and
commands for their registration inside IDE.

 [ProvideMenuResource("Menus.ctmenu", 1)]

The DefaultRegistryRoot attribute specifies a path to be used for writing registration data to the system
registry. Starting with Visual Studio 2010 this attribute can be dropped as the corresponding data will be
present in manifest file of a VSIX container. An example of registering a package for Visual Studio 2008:

[DefaultRegistryRoot("SoftwareMicrosoftVisualStudio9.0")]

Registration of user-created components, such as toolwidows, editors, option pages etc. also requires
the inclusion of their corresponding attributes for the user's Package subclass. We will examine these
attributes separately when we will be examining corresponding components individually.

It's also possible to write any user-defined registry keys (and values to already existing keys) during
package registration through custom user registration attributes. Such attributes can be created by
inheriting the RegistrationAttribute abstract class.
[AttributeUsage(AttributeTargets.Class, Inherited = true,
   AllowMultiple = false)]
      public class CustomRegistrationAttribute : RegistrationAttribute
      {
      }
The RegistrationAttribute-derived attribute must override its Register and Unregister methods, which
are used to modify registration information in the system registry.

The RegPkg tool can be used for writing registration data to Windows registry. It will add all of the keys
from pkgdef file passed to it into the registry hive specified by the /root argument. For instance, the
RegPkg is utilized by default in Visual Studio VSPackage project template for registering the module in
the Visual Studio experimental hive, providing convenient seamless debugging of the package being
developed. After all of the registration information have been added to the registry, Visual Studio
(devenv.exe) should be started with '/setup' switch to complete registration for new components inside
the IDE.

Deploying plug-ins for developers and end-users. Package Load Key.
Before proceeding to describe the deployment process itself, one particular rule should be stressed:

Each time after a new version of the distribution containing your plug-in is created, this new
distribution should be tested on a system without Visual Studio SDK installed, as to make sure that it
will be registered correctly on the end-user system.

Today, as the releases of early versions of PVS-Studio are past us, we do not experience these kinds of
issues, but several of these early first versions were prone to them.

Deploying a package for Visual Studio 2005/2008 will require launching of regpkg tool for a pkgdef file
and passing the path to Visual Studio main registry hive into it. Alternately, all keys from a pkgdef can be
written to Windows registry manually. Here is the example of automatically writing all the registration
data from a pkgdef file by regpkg tool (in a single line):

RegPkg.exe /root:SoftwareMicrosoftVisualStudio9.0Exp

   "/pkgdeffile:objDebugPVS-Studio-vs2008.pkgdef"

   "C:MyPackageMyPackage.dll"

After adding the registration information to the system registry, it is necessary to start Visual Studio
with a /setup switch to complete the component registration. It is usually the last step in the installation
procedure of a new plug-in.

Devenv.exe /setup

Starting the environment with this switch instructs Visual Studio to absorb resource metadata for user-
created components from all available extension packages, so that these components will be correctly
displayed by IDE's interface. Starting devenv with this key will not open its' main GUI window.

We do not employ RepPkg utility as part of PVS-Studio deployment, instead manually writing required
data to the registry by using our stand-alone installer. We chose this method because we have no desire
of being dependent on some external third-party tools and we want full control over the installation
process. Still, we do use RegPkg during plug-in development for convenient debugging.
VSIX packages
Beginning from Visual Studio 2010, VSPackage deployment process can be significantly simplified
through the usage of VSIX packages. VSIX package itself is a common (Open Packaging Conventions)
archive containing plug-in's binary files and all of the other auxiliary files which are necessary for plug-
in's deployment. By passing such archive to the standard VSIXInstaller.exe utility, its contents will be
automatically registered in the IDE:

VSIXInstaller.exe MyPackage.vsix

VSIX installer could also be used with /uninstall switch to remove the previously installed package from
a system. A unique GUID of the extension package should be used to identify such package:

VSIXInstaller.exe /uninstall: 009084B1-6271-4621-A893-6D72F2B67A4D

Contents of a VSIX container are defined through the special vsixmanifest file, which should be added to
plug-in's project. Vsixmanifest file permits the following properties to be defined for an extension:

       targeted Visual Studio versions and editions, which will be supported by the plug-in;
       a unique GUID identifier;
       a list of components to be registered (VSPackage, MEF components, toolbox control etc.);
       general information about the plug-in to be installed (description, license, version, etc.);

To include additional files into a VSIX container, the IncludeInVSIX node should be added to their
declarations inside your MSBuild project (alternately, they could also be marked as included into VSIX
from their respective property windows, by opening it from Visual Studio Solution Explorer).

<Content Include="MyPackage.pdb">
    <IncludeInVSIX>true</IncludeInVSIX>
</Content>
In fact, the VSIX file could be viewed as an almost full-fledged installer for extension packages on the
latest versions of Visual Studio (2010 and 2012), allowing the extensions to be deployed by a "one-click"
method. Publishing your VSIX container in the official Visual Studio Gallery for extensions allows end-
users to install such package through the Tools -> Extension Manager IDE dialog.

This new VSIX installation procedure in Visual Studio 2010 does substantially alleviate package
deployment for end-users (as well as for developers themselves). Some developers even had decided to
support only VS2010 IDE and versions above it, if only not to get involved with the development of a
package and installer for earlier IDE versions.

Unfortunately, several issues can be encountered when using VSIX installer together with Visual Studio
2010 extension manager interface. For instance, sometimes the extension's binary files are not removed
correctly after uninstall, which in turn blocks the VSIX installer from installing/reinstalling the same
extension. As such, we advise you not to depend upon the VSIX installer entirely and to provide some
backup, for example by directly removing your files from a previous plug-in installation before
proceeding with a new one.

Package Load Key
Each VSPackage module loaded into Visual Studio must possess a unique Package Load Key (PLK). PLK
key is specified through the ProvideLoadKey attribute for the Package subclass in 2005/2008 versions of
the IDE.
[ProvideLoadKey("Standard", "9.99", "MyPackage", "My Company", 100)]

Starting with Visual Studio 2010, the presence of a PLK, as well as of the ProvideLoadKey attribute
respectively, in a package is not required, but it can still be specified in case the module under
development is targeting several versions of MSVS. The PLK can be obtained by registering at the Visual
Studio Industry Partner portal, meaning it guarantees that the development environment can load only
packages certified by Microsoft.

However, systems containing Visual Studio SDK installed are exceptions to this, as Developer License Key
is installed together with the SDK. It allows the corresponding IDE to load any extension package,
regardless of validity of its PLK.

Considering the aforementioned, one more time it is necessary to stress the importance of testing the
distribution on a system without Visual Studio SDK present, because the extension package will operate
properly on developer's workstation regardless of its PLK correctness.

Extension registration specifics in the context of supporting several different versions of
Visual Studio IDE
By default, VSPackage project template will generate an extensibility project for the version of Visual
Studio that is used for the development. This is not a mandatory requirement though, so it is possible to
develop an extension for a particular version of IDE using a different one. It also should be noted that
after automatically upgrading a project file to a newer version through devenv /Upgrade switch, the
targeted version of the IDE and its' corresponding managed API libraries will remain unchanged, i.e.
from a previous version of Visual Studio.

To change the target of the extension to another version of Visual Studio (or to register an extension
into this version to be more precise), you should alter values passed to the DefaultRegistryRoot attribute
(only for 2005/2008 IDE versions, as starting from Visual Studio 2010 this attribute is no longer required)
or change the target version in the VSIX manifest file (for versions above 2008).

VSIX support appears only starting from Visual Studio 2010, so building and debugging the plug-in
targeted for the earlier IDE version from within Visual Studio 2010 (and later) requires setting up all the
aforementioned registration steps manually, without VSIX manifest. While changing target IDE version
one should also not forget to switch referenced managed assemblies, which contain COM interface
wrappers utilized by the plug-in, to the corresponding versions as well.

Altering the IDE target version of the plug-in affects the following Package subclass attributes:

        the InstalledProductRegistration attribute does not support overloading of its constructor with a
         (Boolean, String, String, String) signature, starting from Visual Studio 2010;
        the presence of DefaultRegistryRoot and ProvideLoadKey attributes is not mandatory starting
         from Visual Studio 2010, as similar values are now specified inside VSIX manifest;

References
    1.   MSDN. Experimental Build.
    2.   MSDN. How to: Register a VSPackage.
    3.   MSDN. VSIX Deployment.
    4.   MSDN. How to: Obtain a PLK for a VSPackage.
    5.   MZ-Tools. Resources about Visual Studio .NET extensibility.
    6.   MSDN. Creating Add-ins and Wizards.
7. MSDN. Using a Custom Registration Attribute to Register an Extension.




Visual Studio Automation Object Model. EnvDTE interfaces.
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.


Visual Studio commands

Abstract
This article deals with creation, utilization and handling of Visual Studio commands in its extension
modules through automation object model APIs and IDE services. The relations between IDE commands
and environment UI elements, such as user menus and toolbars, will also be examined.
Introduction
Visual Studio commands provide a way for direct interaction with development environment through
the keyboard input. Almost all capabilities of different dialog and tool windows, toolbars and user
menus are represented by the environment's commands. In fact, main menu items and toolbar buttons
are practically commands themselves. Although it is possible for a command not to possess a direct
representation in the development environment's UI, as commands are not the UI elements per se, they
can be represented by such UI elements as menu items and toolbar buttons.

PVS-Studio IDE extension package integrates several subgroups of its commands into Visual Studio main
menu, and these commands serve as one of the plug-in's main UI components (with another one being
its MDI toolwindow), allowing a user to control all of the aspects of static code analysis either from the
environment's UI or by invoking the commands directly through command line.

Using IDE commands
Any IDE command, regardless of its UI representation in the IDE (or of the lack of it), could be executed
directly through the Command or Immediate windows, as well as by starting devenv.exe with the
'/command' argument.

The full name of a command is formed according to its affiliation with a functional group, as for example
the commands of the 'File' main menu item. Command's full name could be examined in the 'Keyboard,
Environment' Options page. Also, the 'Tools -> Customize -> Commands' dialog allows inspecting all of
the commands which are currently registered within the environment. This dialog sorts the commands
by their respective functional groups and UI presentation types (menus, toolbars), also allowing to
modify, add or delete them.

Commands can receive additional arguments which should be separated from the command's name by
a space. Let's examine a call to a standard system command of the main menu, 'File -> New -> File' for
example, with a passing of additional parameters to it through the Command Window:

>File.NewFile Mytext /t:"GeneralText File"
  /e:"Source Code (text) Editor"

A command's syntax generally complies with the following rules:

       command's name and arguments are separated by a space
       arguments containing spaces are wrapped by double quotes
       The caret (^) is used as an escape character
       One-character abridgments for command names can be combined, as for example, /case(/c)
        and /word(/w) could be presented as /cw

When using the 'command' command-line switch, name of a command with all of its arguments should
be wrapped by double quotes:

devenv.exe /command "MyGroup.MyCommandName arg1 arg2"

For the sake of convenience, a command could be associated with an alias:

>alias MyAlias File.NewFile MyFile

Commands integrated into IDE by PVS-Studio extension can be utilized through the /command switch as
well. For example, this mode could be used for the integration of our static analysis into the automated
build process. Our analyzer itself (PVS-Studio.exe) is a native command-line application, which operates
quite similar to the compiler, i.e. it takes a path to the file containing source code and its compilation
arguments and then it outputs analysis results to stdout/stderr streams. It's quite obvious that the
analyzer could easily be integrated directly into the build system (for instance, into a system which is
based on MSBuild, NMake or even GNU Make) at the same level where C/C++ compiler is being called.
Of course, such integration already provides us, by its own definition, with complete enumeration of all
of the source files being built, with all of their compilation parameters. In turn, this allows for a
substitution (or supplementation) of a compiler call by call to the analyzer. Although the described
scenario is fully supported by PVS-Studio.exe analyzer, it still requires a complete understanding of build
system's internals as well as an opportunity to modify a system in the first place, which could be
problematic or even impossible at times.

Therefore, the integration of the analyzer into the build process can be performed in a more convenient
way, on a higher level (i.e. at the level of Continuous Integration Server), by utilizing Visual Studio
extension commands through the /command switch, for example, by using the PVS-
Studio.CheckSolution command to perform analysis on MSVS solution. Of course, such use case is only
possible when building Visual C++ native project types (vcproj/vcxproj).

In case Visual Studio is started form a command line, the /command switch will be executed
immediately after the environment is fully loaded. In this case, the IDE will be started as a regular GUI
application, without redirecting its standard I/O streams to the console that was used to launch the
environment. It should be noted that, in general, Visual Studio is a UI based development environment
and so it is not intended for command line operations. It is recommended to employ Microsoft MSBuild
utility for building inside build automation systems, as this tool supports all of native Visual Studio
project types.

Caution should be applied when using Visual Studio /command switch together with non-interactive
desktop mode (for example when calling IDE from a Windows service). We've encountered several
interesting issues ourselves when we were evaluating the possibility of integrating PVS-Studio static
analysis into Microsoft Team Foundation build process, as Team Foundation operates as a Windows
service by default. At that moment, our plug-in had not been tested for non-interactive desktop sessions
and was incorrectly handling its child windows and dialogs, which in turn lead to exceptions and crashes.
But Visual Studio itself experienced none of such issues, almost none to be more precise. The case is,
Visual Studio displays a particular dialog for every user when it is started for a first time after an
installation, and this dialog offers the user to select a default UI configuration. And it was this dialog that
Visual Studio displayed for a LocalSystem account, the account which actually owns the Team
Foundation service. It turns out that the same dialog is 'displayed' even in the non-interactive desktop
mode, and it subsequently blocks the execution of the /command switch. As this user doesn't have an
interactive desktop, he is also unable to close this dialog normally by manually starting the IDE himself.
But, in the end, we were able to close the dialog manually by launching Visual Studio for LocalSystem
account in the interactive mode through psexec tool from PSTools utilities.

Creating and handling commands in VSPackage. Vsct files.
VSPackage extension utilizes Visual Studio command table (*.vsct) file for creating and managing
commands that it integrates into the IDE. Command tables are text files in XML format which can be
compiled by VSCT compiler into binary CTO files (command table output). CTO files are then included as
a resources into final builds of IDE extension packages. With the help of VSCT, commands can be
associated with menus or toolbar buttons. Support for VSCT is available starting from Visual Studio
2005. Earlier IDE versions utilized CTC (command table compiler) files handling their commands, but
they will not be covered in this article.

In a VSCT file each command is assigned a unique ID — CommandID, a name, a group and a quick access
hotkey combination, while its representation in the interface (if any) is specified by special flags.

Let's examine a basic structure of VSCT file. The root element of file is 'CommandTable' node that
contains the 'Commands' sub-node, which defines all of the user's commands, groups, menu items,
toolbars etc. Value of the "Package" attribute of the "Commands" node must correspond with the ID of
your extension. The "Symbols" sub-node should contain definitions for all identifiers used throughout
this VSCT file. The 'KeyBindings' sub-node contains default quick access hotkey combinations for the
commands.

<CommandTable"http://schemas.microsoft.com/VisualStudio/2005-10-
18/CommandTable">

    <Extern href="stdidcmd.h"/>
    <Extern href="vsshlids.h"/>
  <Commands>
    <Groups>
    ...
    </Groups>
    <Bitmaps>
    ...
    </Bitmaps>
  </Commands>
  <Commands package="guidMyPackage">
    <Menus>
    ...
    </Menus>
    <Buttons>
    ...
    </Buttons>
  </Commands>

  <KeyBindings>
    <KeyBinding guid="guidMyPackage" id="cmdidMyCommand1"
 editor="guidVSStd97" key1="221" mod1="Alt" />
  </KeyBindings>
  <Symbols>
    <GuidSymbol name="guidMyPackage" value="{B837A59E-5BF0-4190-B8FC-
FDC35BE5C342}" />
    <GuidSymbol name="guidMyPackageCmdSet" value="{CC8B1E36-FE6B-48C1-
B9A9-2CC0EAB4E71F}">
      <IDSymbol name="cmdidMyCommand1" value="0x0101" />
    </GuidSymbol>
  </Symbols>
</CommandTable>
The 'Buttons' node defines the commands themselves by specifying their UI representation style and
binding them to various command groups.

<Button guid="guidMyPackageCmdSet" id="cmdidMyCommand1"
priority="0x0102" type="Button">
  <Parent guid="guidMyPackageCmdSet" id="MyTopLevelMenuGroup" />
  <Icon guid="guidMyPackageCmdSet" id="bmpMyCommand1" />
  <CommandFlag>Pict</CommandFlag>
  <CommandFlag>TextOnly</CommandFlag>
  <CommandFlag>IconAndText</CommandFlag>
  <CommandFlag>DefaultDisabled</CommandFlag>
  <Strings>
    <ButtonText>My &amp;Command 1</ButtonText>
  </Strings>
</Button>


The 'Menus' node defines the structure of UI elements (such as menus and toolbars), also binding them
to command groups in the 'Groups' node. A group of commands bound with a 'Menu' element will be
displayed by the UI as a menu or a toolbar.

<Menu guid=" guidMyPackageCmdSet" id="SubMenu1" priority="0x0000"
type="Menu">
  <Parent guid="guidMyPackageCmdSet" id="MyTopLevelMenuGroup"/>
  <Strings>
    <ButtonText>Sub Menu 1</ButtonText>
  </Strings>
</Menu>
<Menu guid=" guidMyPackageCmdSet" id="MyToolBar1" priority="0x0010"
type="Toolbar">
</Menu>


And finally, the 'Groups' element organizes user's IDE command groups.

<Group guid="guidMyPackageCmdSet" id="MySubGroup1" priority="0x0020">
  <Parent guid="guidMyPackageCmdSet" id="MyGroup1" />
</Group>

To include vsct file into MSBuild-based VSPackage project, it is necessary to insert the following node
used for calling VSCT compiler into your csproj project file (note, that in the auto-generated project
created from an SDK template, a vsct file will be already included in a project):

<ItemGroup>
  <VSCTCompile Include="TopLevelMenu.vsct">
    <ResourceName>Menus.ctmenu</ResourceName>
  </VSCTCompile>
</ItemGroup>

Next, the ProvideMenuResource attribute of your Package-derived class should point to this node that
you've inserted into your project earlier:
[ProvideMenuResource("Menus.ctmenu", 1)]
...
public sealed class MyPackage : Package


Assigning handlers to the commands defined in a VSCT file is possible through a service that is available
through the IMenuCommandService. A reference for it can be obtained by the GetService method of
your Package subclass:

OleMenuCommandService MCS = GetService(typeof(IMenuCommandService)) as
  OleMenuCommandService;

Let's examine an example in which we assign a handler to a menu command (this command should be
declared in a vsct file beforehand):

EventHandler eh = new EventHandler(CMDHandler);
CommandID menuCommandID = new CommandID(guidCommand1CmdSet, id);
//ID and GUID should be the same as in the VCST file
OleMenuCommand menuItem = new OleMenuCommand(eh, menuCommandID);
menuItem.ParametersDescription = "$";
MCS.AddCommand(menuItem);

To obtain command's arguments while handling its invocation, the EventArgs object should be casted
into OleMenuCmdEventArgs:

void CMDHandler(object sender, EventArgs e)
{
  OleMenuCmdEventArgs eventArgs = (OleMenuCmdEventArgs)e;
                   if (eventArgs.InValue != null)
                       param = eventArgs.InValue.ToString();
  ...
}

Handling commands through EnvDTE.DTE interfaces.
The EnvDTE.DTE automation object allows for a direct manipulation (creation, modification and
execution) of commands through the dte.Commands interface and dte.ExecuteCommand method.
Utilizing the Automation Object Model for invoking, modifying and creating IDE commands, as opposed
to using VSCT mechanism available only for VSPackage, allows the interaction with IDE commands from
within Add-In extension packages as well.

The DTE automation object allows a direct creation, modification and invocation of commands through
the DTE.Commands interface. A command can be directly added to the IDE by
Commands.AddNamedCommand method (but only for an Add-In extension):

dte.Commands.AddNamedCommand(add_in, "MyCommand", "My Command",
  "My Tooltip", true);

The command added in this way will be preserved by the IDE — it will reappear in the menu after IDE
restart, even if the extension which created the command is not loaded itself. That's why this method
should only be utilized during the first initialization of an Add-In module, after its installation (this is
described in the article dedicated to Visual Studio Automation Object Model). The OnConnection
method of an Add-In contains a special initialization mode which is invoked only for a single time in the
module's entire lifetime. This method can be used to integrate UI elements into the IDE:

public void OnConnection(object application,
                         ext_ConnectMode connectMode,
                         object addInInst, ref Array custom)
{
  switch(connectMode)
  {
      case ext_ConnectMode.ext_cm_UISetup:
          ...
          break;

         ...
    }
}

The EnvDTE.Command interface represents a single IDE command. This interface can be used to modify
a command which it references. It permits managing IDE commands from either a VSPackage, or an
Add-In module. Let's obtain a reference to the EnvDTE.Command object for our custom command
'MyCommand1' and utilize this interface to assign a 'hot-key' to it for a quick access:

EnvDTE.Command MyCommand1 =
MyPackage.DTE.Commands.Item("MyGroup.MyCommand1", -1);
MyCommand1.Bindings = new object[1] { "Global::Alt+1" };

The quick-access combination assigned to MyGroup.MyCommand1 will now be available through
'Keyboard, Environment' environment settings dialog.

As was mentioned before, Visual Studio command is not a UI element by itself. The
Commands.AddCommandBar method allows the creation of such UI elements, as main menu items,
toolbars, context menus and the association of these elements with user-created commands.

CommandBar MyToolbar = dte.Commands.AddCommandBar("MyToolbar1",
  vsCommandBarType.vsCommandBarTypeToolbar) as CommandBar;
CommandBar MyMenu = dte.Commands.AddCommandBar("MyMenu1",
  vsCommandBarType.vsCommandBarTypeMenu) as CommandBar;
CommandBarButton MyButton1 = MyCommand1.AddControl(MyToolbar) as
  CommandBarButton;
MyButton1.Caption = "My Command 1";

The 'Delete' method of Command/ CommandBar objects could be utilized to remove a command or
toolbar from IDE.

MyCommand1.Delete();

In general, it is not recommended creating commands each time an Add-In plug-in is loaded and
removing them each time it is un-loaded, as such behavior could slow-down the initialization of IDE
itself. Even more, in case the OnDisconnect method is somehow interrupted in the process, it is possible
that the user commands will not be completely deleted from the IDE. That is why it is advised that the
integration, and subsequent removal, of IDE commands should be handled at the times of module's
installation/uninstallation, as for example, by obtaining DTE interface reference from a stand-alone
installer application. The initialization of Add-In modules and acquisition of DTE references is thoroughly
described in the article devoted to EnvDTE Automation Object Model.

Any IDE command (either custom or default one) could be called by the ExecuteComand method. Here
is the example of invoking our custom MyCommand1 command:

MyPackage.DTE.ExecuteCommand("MyGroup.MyCommand1", args);

To handle command execution, an Add-In extension should be derived from the IDTCommandTarget
interface and it should also implement the Exec method:

public void Exec(string commandName,
  vsCommandExecOption executeOption, ref object varIn,
  ref object varOut, ref bool handled)
{
  handled = false;
  if(executeOption ==
    vsCommandExecOption.vsCommandExecOptionDoDefault)
  {
    if(commandName == "MyAddin1.Connect.MyCommand1")
    {
      ...
      handled = true;
      return;
    }
  }
}

References
    1. MSDN. Visual Studio Commands and Switches.
    2. MSDN. Visual Studio Command Table (.Vsct) Files.
    3. MSDN. Designing XML Command Table (.Vsct) Files.
    4. MSDN. Walkthrough: Adding a Toolbar to the IDE.
    5. MSDN. How VSPackages Add User Interface Elements to the IDE.
    6. MZ-Tools. HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an
       add-in.
    7. MSDN. How to: Create Toolbars for Tool Windows.


Visual Studio tool windows

Abstract
This article covers the extension of Visual Studio IDE through integration of a custom user toolwindow
into the environment. Discussed are the issues of window registration and initialization in VSPackage
and Add-In plug-in modules, hosting of user components and handling of window's events and states.

Introduction
Tool windows are child windows of Visual Studio MDI (Multiple Document Interface) interface and they
are responsible for presenting various pieces of information to the user. Solution Explorer and Error List
are the examples of tool windows. Usually tool windows' contents are not associated with any files and
do not contain any editors, as separate document windows are reserved for such tasks.

For instance, PVS-Studio extension package integrates several tool windows into the IDE, with Output
Window being the primary one. All other of its tool windows can be opened from this main window, as,
for example, a search window for the grid. PVS-Studio Output Window itself can be opened from Visual
Studio main menu (PVS-Studio -> Show PVS-Studio Output Window), but it also will be invoked
automatically each time the analysis starts.

In most cases IDE creates and utilizes just a single instance for each one of its toolwindows, and this
instance will be preserved until IDE itself needs to shut down. Therefore, pressing the 'close' button on a
tool window does actually hide it, and when this window is invoked for the second time, it becomes
visible again, thus preserving any data that it contained before being 'closed'. But still, is it possible to
crate Multi-Instance toolwindows in the IDE, which are the windows that can exist in several instances
at once. A toolwindow can also be associated with a certain UI context (as the so called dynamic
window), and such window will be automatically displayed when the user enters this context.

Integration of a tool window into the IDE is supported by VSPackage and Add-In extensions (although
the methods for it are different); it requires the specification of the window's initial settings and its
registration in the system registry.

Registering and initializing user tool windows
A VSPackage project template that is installed together with Visual Studio SDK allows you to create a
sample tool window in the extension project which this template generates. Such a project should
already contain all of the basic components which will be described below, so it could be conveniently
used as a sample for experimenting with Visual Studio toolwindow integration process for VSPackage
plug-ins.

Registering, initializing and invoking a tool window in VSPackage
Registering a custom user window in the environment requires writing of the data that defines this
window into a special section of Visual Studio registry hive. This process can be automated by
generating a pkgdef file that can contain all of the required window registration information. The
contents of this pkgdef files can be specified through special registration attributes of your Package
subclass.

The immediate registration of a user-created tool window into VSPackage extension is handled by
ProvideToolWindow attribute of Package subclass:

 [ProvideToolWindow(typeof(MyWindowPane), Orientation =
ToolWindowOrientation.Right, Style = VsDockStyle.Tabbed, Window =
Microsoft.VisualStudio.Shell.Interop.ToolWindowGuids.Outputwindow,
MultiInstances = false, Transient = true, Width = 500, Height = 250,
PositionX = 300, PositionY = 300)]

Let's examine several parameters of this attribute. The 'Typeof' parameter points to user
implementation of the window's client area (a subclass of ToolWindowPane). The 'MultiInstances'
parameter enables the Multi-Instance mode for a window, in which multiple instances of the window
can be opened simultaneously. The Orientation, Size and Style parameters specify the initial position of
a window when it is opened for the first time by the user. It should be noted that the position specified
by these parameters will only be used once, when a tool window is displayed for the first time; at all of
the subsequent iterations of opening this window, the IDE will be restoring its screen position from the
previous one, that is the position before a window was closed. The 'Transient' parameter indicates
whether the window will be automatically opened after Visual Studio environment is loaded in case it
already have been opened during the previous session of the IDE.

It should also be remembered that the initialization of a user window by VSPackage (the initialization
itself will be covered later) does not necessarily occur at the same moment as the initialization of a
Package subclass for which we provided this registration attribute. For example, after implementing a
tool window for PVS-Studio plug-in, we've encountered an issue in which our custom window was
automatically opened (but not focused/displayed) and placed among other window tabs at the bottom
of the main window, and it was done immediately after Visual Studio started up, even though we've
passed the 'Transient=true' parameter to the ProvideToolWindow attribute. Although the plug-in itself is
always initialized at IDE start-up, the window had not been fully initialized until after a first call to it,
which was evident by the corrupted icon on aforementioned tab.

A dynamic visibility context can be specified for a window by the ProvideToolWindowVisibility attribute:

  [ProvideToolWindowVisibility(typeof(MyWindowPane),
/*UICONTEXT_SolutionExists*/"f1536ef8-92ec-443c-9ed7-fdadf150da82")]
In this example, the window is set to be automatically displayed when the user enters the "Solution
Exists" UI context. Take a note that each one of user's toolwindow requires a separate attribute and a
window's type should be passed as a first argument to it.

The FindToolWindow method of a Package subclass can be utilized to create and display a toolwindow
from a VSPackage extension. This method returns a reference to the specified toolwindow object,
creating it if necessary (for instance, in case a single-instance window is called for a first time). Following
is the example of invoking a single-instance toolwindow:

private void ShowMyWindow(object sender, EventArgs e)
{
  ToolWindowPane MyWindow = this.FindToolWindow(typeof(MyToolWindow),
    0, true);
  if ((null == MyWindow) || (null == MyWindow.Frame))
  {
    throw new NotSupportedException(Resources.CanNotCreateWindow);
  }
  IVsWindowFrame windowFrame = (IVsWindowFrame) MyWindow.Frame;
  ErrorHandler.ThrowOnFailure(windowFrame.Show());
}

In this example, the window will be created in case it is called for the first time, or the window will be
made visible in case it had been created before and then hidden. The FindToolWindow 's third argument
of the bool type specifies whether a new instance of a window should be created if the method was
unable to find an already existing one.

To create a Multi-Instance tool window, the CreateToolWindow method can be used. It allows the
creation of a window with a pre-defined identifier. An example of invoking such window:

private void CreateMyWindow(object sender, EventArgs e)
{
  for (int i = 0; ; i++)
{
        // Find existing windows.
        var currentWindow =
          this.FindToolWindow(typeof(MyToolWindow), i, false);
        if (currentWindow == null)
        {
          // Create the window with the first free ID.
          var window =
           (ToolWindowPane)this.CreateToolWindow(typeof(MyToolWindow), i);

            if ((null == window) || (null == window.Frame))
            {
              throw new
                NotSupportedException(Resources.CanNotCreateWindow);
            }
            IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;

            ErrorHandler.ThrowOnFailure(windowFrame.Show());
            break;
        }
    }
}

Note that in this example the FindToolWindow method receives 'false' value as its third argument, i.e.
we are searching for an unoccupied index before initializing a new window instance.

As was mentioned above, the environment will preserve position of a window after it is closed. But if,
for whatever reason, it is necessary to specify the size and position of a window, it could be achieved
through the SetFramePos method of the IVsWindowFrame interface:

Guid gd = Guid.Empty;
windowFrame.SetFramePos(VSSETFRAMEPOS.SFP_fDockBottom, ref gd, 20, 20,
  200, 200);

A call to the SetFramePos() should always be made only after the Show() method is executed.

Creating and invoking a window from Add-In extension
A user tool window can be initialized from an Add-In extension with the help of the EnvDTE Window2
interface:

public void OnConnection(object application,
ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
  _applicationObject = (DTE2)application;
  _addInInstance = (AddIn)addInInst;
  EnvDTE80.Windows2 window;
  AddIn add_in;
  object ctlobj = null;
  Window myWindow;

    // Get the window object
add_in = _applicationObject.AddIns.Item(1);
    window = (Windows2)_applicationObject.Windows;

    // This section specifies the path and class name of the windows
    // control that you want to host in the new tool window, as well as
    // its caption and a unique GUID.
    string assemblypath = "C:MyToolwindowMyToolWindowControl.dll";
    string classname = " MyToolWindowControl.MyUserControl";
    string guidpos = "{E87F0FC8-5330-442C-AF56-4F42B5F1AD11}";
    string caption = "My Window";

    // Creates the new tool window and inserts the user control into it.
    myWindow = window.CreateToolWindow2(add_in, assemblypath,
    classname, caption, guidpos, ref ctlobj);
    myWindow.Visible = true;
}

In the example above, a user toolwindow was created using the MyToolWindowControl.MyUserControl
as a client area control. The MyToolWindowControl.MyUserControl class could either be located in the
same assembly as the add-in that initializes it, or it could be provided by a stand-alone assembly with a
full COM visibility (though the 'Register for COM Interop' option in project settings). The regular
composite UserControl subclass could be utilized as MyUserControl.

Implementing a user toolwindow in a VSPackage module
Tool window consists of a frame border and a client area. A frame is provided by the environment and is
responsible for performing docking with other interface objects of the environment, as well as for size
and position of the window itself. A client area is a pane, controlled by a user, which houses the
contents of a window. Tool windows can host user-created WinForms and WPF components and are
capable of handling regular events, such as OnShow , OnMove, etc.

A user toolwindow, or its client area to be more precise, can be implemented by inheriting the class
representing a standard empty IDE window — ToolWindowPane.

 [Guid("870ab1d8-b434-4e86-a479-e49b3c6797f0")]
public class MyToolWindow : ToolWindowPane
{

    public MyToolWindow():base(null)
    {
      this.Caption = Resources.ToolWindowTitle;
      this.BitmapResourceID = 301;
      this.BitmapIndex = 1;
      ...

    }
}

The Guid attribute is used to uniquely identify each custom user window. In case a plug-in module
creates several windows of different types, each one of them should be identified by its own unique
Guid. A ToolWIndowPane subclass can be subsequently modified and host user-controlled components.
Hosting user components
A base ToolWindowPane class implements an empty tool window of the environment. Inheriting form
this class allows hosting user-created WinForms or WPF components.

Up until Visual Studio 2008 version, toolwindows only provided a native supported for WinForms user
components, although it still was possible to host WPF components through the WPF Interoperability
ElementHost object. Starting from Visual Studio 2010, toolwindows themselves are based on WPF
technology, although they still provide a backward compatibility for hosting of WinForms components.

To host a user-created WinForms component inside a user toolwindow, the Window property of the
ToolWindowPane base class should be overridden:

public MyUserControl control;

public MyToolWindow():base(null)
{
  this.Caption = Resources.ToolWindowTitle;
  this.BitmapResourceID = 301;
  this.BitmapIndex = 1;
  this.control = new MyUserControl();
}

public override IWin32Window Window
{
  get { return (IWin32Window)control; }
}

In the example above, the 'MyUserControl' object is a regular composite component of the
System.Windows.Forms.UserControl type and it can host any other user component inside itself.
UserControl can also host WPF components by using WPF ElementHost object.

Starting from Visual Studio 2010, WPF components can be hosted by tool windows natively. To do this, a
reference to the WPF component should be passed to the 'Content' property of a base class:

public MyToolWindow():base(null)
{
  this.Caption = Resources.ToolWindowTitle;
  this.BitmapResourceID = 301;
  this.BitmapIndex = 1;
  base.Content = new MyWPFUserControl();
}


Please note that using the two methods described above simultaneously is not possible. When a
reference to WPF component is assigned to the base.Content property, an overridden Window property
is ignored.

The main PVS-Studio 'Output' window of our extension plug-in hosts a virtual grid based on SourceGrid
open-source project. This window provides an interface for handling the results of static analysis. The
grid itself is bound to a regular ADO.NET table of the System.Data.Datatable type, which is utilized for
storing analysis results. Until 4.00 version of PVS-Studio extension, it utilized a regular IDE 'Error List'
window, but as the analyzer evolved, the capabilities of this default window became insufficient. Apart
from being un-extendable with such specific static analysis UI elements as, for example, false positive
suppression and filtering mechanisms, the Error List is itself basically a 'real' grid, as it stores all of the
displayed elements inside itself. Therefore, this grid only permits an adequate handling of 1-2k
messages at a time, performance wise, as a greater number of messages already can cause quite a
noticeable lag to the environment's UI. On the other hand, our own practice of using static analysis on
relatively large projects, such as Chromium or LLVM, demonstrated that a total number of diagnostic
messages (taking into account all of the marked false alarms and low-lever user diagnostics as well)
could easily reach tens of thousands or even more.

Therefore, by implementing a custom output window, based on virtual grid that is connected to a DB
table, PVS-Studio is able to display and provide convenient handling for hundreds of thousands of
diagnostic messages at once. Also, the ability for a convenient and flexible filtering of the analysis results
is quite an important aspect of handling a static analyzer, as the manual examination even of only such a
"tiny" amount of messages as 1-2k is nearly impossible for a single user. The storage of analysis results
in a Datatable object by itself provides quite a convenient filtering mechanism based on a simple SQL
queries, even more so because the results of such queries become visible immediately inside the bound
virtual grid.

Handling tool windows events
 A client area of a tool window (represented by our ToolWindowPane subclass) can process the regular
events of user-interface interactions. The IVsWindowFrameNotify3 interface can be used for subscribing
to window events. Let's provide an example of implementing this interface:

public sealed class WindowStatus: IVsWindowFrameNotify3
{
  // Private fields to keep track of the last known state
  private int x = 0;
  private int y = 0;
  private int width = 0;
  private int height = 0;
  private bool dockable = false;

   #region Public properties

   // Return the current horizontal position of the window
   public int X
   {
     get { return x; }
   }

   // Return the current vertical position of the window
   public int Y
   {
     get { return y; }
   }

   // Return the current width of the window
   public int Width
   {
get { return width; }
}

// Return the current height of the window
public int Height
{
  get { return height; }
}

// Is the window dockable
public bool IsDockable
{
  get { return dockable; }
}

#endregion

public WindowStatus()
{}

#region IVsWindowFrameNotify3 Members
// This is called when the window is being closed
public int OnClose(ref uint pgrfSaveOptions)
{
  return Microsoft.VisualStudio.VSConstants.S_OK;
}

// This is called when a window "dock state" changes.
public int OnDockableChange(int fDockable, int x, int y, int w,
int h)
{
  this.x = x;
  this.y = y;
  this.width = w;
  this.height = h;
  this.dockable = (fDockable != 0);

    return Microsoft.VisualStudio.VSConstants.S_OK;
}

// This is called when the window is moved
public int OnMove(int x, int y, int w, int h)
{
  this.x = x;
  this.y = y;
  this.width = w;
  this.height = h;

    return Microsoft.VisualStudio.VSConstants.S_OK;
}
// This is called when the window is shown or hidden
    public int OnShow(int fShow)
    {
      return Microsoft.VisualStudio.VSConstants.S_OK;
    }

    /// This is called when the window is resized
    public int OnSize(int x, int y, int w, int h)
    {
      this.x = x;
      this.y = y;
      this.width = w;
      this.height = h;
      return Microsoft.VisualStudio.VSConstants.S_OK;
    }

    #endregion

}

As evident by this sample code above, the WindowsStatus class implementing the interface is able to
process such window state changes, as the alterations in window's size, position, visibility properties
and so on. Now, let's subscribe our window for handling these events. It requires the
OnToolWindowCreated method to be overridden in our ToolWindowPane subclass:

public class MyToolWindow: ToolWindowPane
{
  public override void OnToolWindowCreated()
  {
    base.OnToolWindowCreated();

      // Register to the window events
      WindowStatus windowFrameEventsHandler = new WindowStatus();

ErrorHandler.ThrowOnFailure(((IVsWindowFrame)this.Frame).SetProperty(
  (int)__VSFPROPID.VSFPROPID_ViewHelper,
  (IVsWindowFrameNotify3)windowFrameEventsHandler));
  }

    ...
}


Controlling window state
A window state can be controlled through event handlers of our IVsWindowFrameNotify3
implementation.

The 'OnShow' method notifies the extension package about changes in tool window's visibility state,
allowing to track the appearance of the window to a user, when, for example, user switches windows by
clicking on window tabs. Current visibility state could be obtained by the fShow parameter, which
corresponds to the __FRAMESHOW list.

The 'OnClose' method notifies about the closure of a window frame, allowing to define IDE behavior in
case ofthis event with the pgrfSaveOptions parameter, which controls the default document saving
dialog (__FRAMECLOSE).

The OnDockableChange method informs the package on window's docking status changes. The
fDockable parameter indicates whether a window is docked to another one; other parameters control
window's size and position before and after the docking event.

The parameters of 'OnMove' and 'OnSize' methods provide window's coordinates and size while it is
being dragged of resized.

References
    1.   MSDN. Kinds of Windows.
    2.   MSDN. Tool Windows.
    3.   MSDN. Tool Window Essentials.
    4.   MSDN. Tool Window Walkthroughs.
    5.   MSDN. Arranging and Using Windows in Visual Studio.
    6.   MZ-Tools. HOWTO: Understanding toolwindow states in Visual Studio.


Integrating into Visual Studio settings
Abstract
This article covers the extension of Visual Studio by integrating into its 'Settings' dialog pages. Option
page registration and integration into the IDE for different kinds of extension packages will be examined,
as well as the means to display various standard and user-created components inside a custom settings
page. Also covered are the ways of accessing environment settings through Visual Studio Automation
model and preservation mechanism for option pages.

Introduction
Visual Studio employs a single unified dialog window to provide an access to the settings of its various
components. This window is available through the IDE Tools -> Options main menu item. A basic
element of Visual Studio settings is an Options Page. The Options dialog window arranges its pages in a
tree-like structure according to the membership of the pages in their respective functional groups. Each
one of these pages could be uniquely identified by the name of its group and its own name. For
example, Visual Basic source code editor settings page is "Text Editor, Basic".

Extension packages are able to access and modify the values of various settings from option pages
registered in the IDE. They can also create and register their own custom options pages in the
environment through the automation object model and MPF classes (Managed Package Framework,
available only to VSPackage extensions). Visual Studio contains an embedded mechanism for preserving
the state of its settings objects; it is enabled by default, but can be overridden or disabled.

Creating and registering user options pages
It can be useful for a Visual Studio extension plug-in to be associated with one or several custom options
pages from the Tools->Options dialog window. Such tool for configuring an extension will conform to
the environment's UI paradigm and is actually quite convenient for handling your extension's settings
from within the IDE itself. The methods of implementing and integrating custom user options page into
the IDE can vary, as they depend upon the type of the extension being developed and the technology
being used (either an automation model or MPF).

Integrating settings through an MPF class
Managed Package Framework allows creating custom options pages by inheriting from the DialogPage
class. As the environment loads each of its options pages independently when accessing the
corresponding section of the Tools->Options dialog, each page must be implemented with an
independent object as a result.

The object which implements a custom page should be associated with your VSPackage through the
ProvideOptionPage attribute of the corresponding Package subclass.

 [ProvideOptionPageAttribute(typeof(OptionsPageRegistration),
"MyPackage", "MyOptionsPage", 113, 114, true)]

This attribute designates the names for the options page itself and for group that it belongs to, as it
should be displayed in the IDE options dialog. A separate attribute should be used for every custom page
that is to be integrated by the extension. In fact, this attribute is used to provide a registration for the
page through pkgdef file and it does not directly affect the execution in any other way. For the user
options page to be correctly rendered by the environment, the page should be registered in the
following node of the system registry:

HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<VsVersion>ToolsOptionsPages

Here <VsVersion> is the version number of Visual Studio IDE, 10.0 for example. This record will be
automatically created when ProvideOptionPage attribute is utilized. It should be noted that a correct
uninstallation of an extension package also requires purging all of the records that this extension had
written to the system registry before, including the ones belonging to its options pages. As the versions
of Visual Studio IDE starting from 2010 can utilize VSIX packages to deploy/uninstall VSPackage plug-ins,
the VSIX installer will automatically perform such registry operations according to its pkgdef file. But
earlier versions of IDE may require manual registry cleaning, for instance by a stand-alone installer
application.

The 6th bool-type argument of the attribute's constructor allows the user's custom options page to be
registered as an automation object. This exposes the page to the Automation Object Model, providing
an access to the its settings through the EnvDTE interfaces for other third-party plug-ins. Registering an
automation object requires the creation of several records in the system registry (it is performed
automatically when using the aforementioned attributes) in the following nodes:

HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<VersionPackages
<PackageGUID>Automation

HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<Version>
AutomationProperties

The ProvideProfile attribute allows registering an options page or any other independent object with the
build-in mechanism for setting's state preservation, provided that such user object implements the
IProfileManager interface.
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples
Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples

Contenu connexe

Plus de Andrey Karpov

Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...
Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...
Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...Andrey Karpov
 
Best Bugs from Games: Fellow Programmers' Mistakes
Best Bugs from Games: Fellow Programmers' MistakesBest Bugs from Games: Fellow Programmers' Mistakes
Best Bugs from Games: Fellow Programmers' MistakesAndrey Karpov
 
Does static analysis need machine learning?
Does static analysis need machine learning?Does static analysis need machine learning?
Does static analysis need machine learning?Andrey Karpov
 
Typical errors in code on the example of C++, C#, and Java
Typical errors in code on the example of C++, C#, and JavaTypical errors in code on the example of C++, C#, and Java
Typical errors in code on the example of C++, C#, and JavaAndrey Karpov
 
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)Andrey Karpov
 
Game Engine Code Quality: Is Everything Really That Bad?
Game Engine Code Quality: Is Everything Really That Bad?Game Engine Code Quality: Is Everything Really That Bad?
Game Engine Code Quality: Is Everything Really That Bad?Andrey Karpov
 
C++ Code as Seen by a Hypercritical Reviewer
C++ Code as Seen by a Hypercritical ReviewerC++ Code as Seen by a Hypercritical Reviewer
C++ Code as Seen by a Hypercritical ReviewerAndrey Karpov
 
The Use of Static Code Analysis When Teaching or Developing Open-Source Software
The Use of Static Code Analysis When Teaching or Developing Open-Source SoftwareThe Use of Static Code Analysis When Teaching or Developing Open-Source Software
The Use of Static Code Analysis When Teaching or Developing Open-Source SoftwareAndrey Karpov
 
Static Code Analysis for Projects, Built on Unreal Engine
Static Code Analysis for Projects, Built on Unreal EngineStatic Code Analysis for Projects, Built on Unreal Engine
Static Code Analysis for Projects, Built on Unreal EngineAndrey Karpov
 
Safety on the Max: How to Write Reliable C/C++ Code for Embedded Systems
Safety on the Max: How to Write Reliable C/C++ Code for Embedded SystemsSafety on the Max: How to Write Reliable C/C++ Code for Embedded Systems
Safety on the Max: How to Write Reliable C/C++ Code for Embedded SystemsAndrey Karpov
 
The Great and Mighty C++
The Great and Mighty C++The Great and Mighty C++
The Great and Mighty C++Andrey Karpov
 
Static code analysis: what? how? why?
Static code analysis: what? how? why?Static code analysis: what? how? why?
Static code analysis: what? how? why?Andrey Karpov
 
Zero, one, two, Freddy's coming for you
Zero, one, two, Freddy's coming for youZero, one, two, Freddy's coming for you
Zero, one, two, Freddy's coming for youAndrey Karpov
 
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOps
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOpsPVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOps
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOpsAndrey Karpov
 
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...Andrey Karpov
 
Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using ...
Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using ...Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using ...
Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using ...Andrey Karpov
 
PVS-Studio in the Clouds: CircleCI
PVS-Studio in the Clouds: CircleCIPVS-Studio in the Clouds: CircleCI
PVS-Studio in the Clouds: CircleCIAndrey Karpov
 
PVS-Studio in the Clouds: Azure DevOps
PVS-Studio in the Clouds: Azure DevOpsPVS-Studio in the Clouds: Azure DevOps
PVS-Studio in the Clouds: Azure DevOpsAndrey Karpov
 
Errors that static code analysis does not find because it is not used
Errors that static code analysis does not find because it is not usedErrors that static code analysis does not find because it is not used
Errors that static code analysis does not find because it is not usedAndrey Karpov
 
Machine Learning in Static Analysis of Program Source Code
Machine Learning in Static Analysis of Program Source CodeMachine Learning in Static Analysis of Program Source Code
Machine Learning in Static Analysis of Program Source CodeAndrey Karpov
 

Plus de Andrey Karpov (20)

Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...
Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...
Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...
 
Best Bugs from Games: Fellow Programmers' Mistakes
Best Bugs from Games: Fellow Programmers' MistakesBest Bugs from Games: Fellow Programmers' Mistakes
Best Bugs from Games: Fellow Programmers' Mistakes
 
Does static analysis need machine learning?
Does static analysis need machine learning?Does static analysis need machine learning?
Does static analysis need machine learning?
 
Typical errors in code on the example of C++, C#, and Java
Typical errors in code on the example of C++, C#, and JavaTypical errors in code on the example of C++, C#, and Java
Typical errors in code on the example of C++, C#, and Java
 
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)
 
Game Engine Code Quality: Is Everything Really That Bad?
Game Engine Code Quality: Is Everything Really That Bad?Game Engine Code Quality: Is Everything Really That Bad?
Game Engine Code Quality: Is Everything Really That Bad?
 
C++ Code as Seen by a Hypercritical Reviewer
C++ Code as Seen by a Hypercritical ReviewerC++ Code as Seen by a Hypercritical Reviewer
C++ Code as Seen by a Hypercritical Reviewer
 
The Use of Static Code Analysis When Teaching or Developing Open-Source Software
The Use of Static Code Analysis When Teaching or Developing Open-Source SoftwareThe Use of Static Code Analysis When Teaching or Developing Open-Source Software
The Use of Static Code Analysis When Teaching or Developing Open-Source Software
 
Static Code Analysis for Projects, Built on Unreal Engine
Static Code Analysis for Projects, Built on Unreal EngineStatic Code Analysis for Projects, Built on Unreal Engine
Static Code Analysis for Projects, Built on Unreal Engine
 
Safety on the Max: How to Write Reliable C/C++ Code for Embedded Systems
Safety on the Max: How to Write Reliable C/C++ Code for Embedded SystemsSafety on the Max: How to Write Reliable C/C++ Code for Embedded Systems
Safety on the Max: How to Write Reliable C/C++ Code for Embedded Systems
 
The Great and Mighty C++
The Great and Mighty C++The Great and Mighty C++
The Great and Mighty C++
 
Static code analysis: what? how? why?
Static code analysis: what? how? why?Static code analysis: what? how? why?
Static code analysis: what? how? why?
 
Zero, one, two, Freddy's coming for you
Zero, one, two, Freddy's coming for youZero, one, two, Freddy's coming for you
Zero, one, two, Freddy's coming for you
 
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOps
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOpsPVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOps
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOps
 
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...
 
Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using ...
Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using ...Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using ...
Analysis of commits and pull requests in Travis CI, Buddy and AppVeyor using ...
 
PVS-Studio in the Clouds: CircleCI
PVS-Studio in the Clouds: CircleCIPVS-Studio in the Clouds: CircleCI
PVS-Studio in the Clouds: CircleCI
 
PVS-Studio in the Clouds: Azure DevOps
PVS-Studio in the Clouds: Azure DevOpsPVS-Studio in the Clouds: Azure DevOps
PVS-Studio in the Clouds: Azure DevOps
 
Errors that static code analysis does not find because it is not used
Errors that static code analysis does not find because it is not usedErrors that static code analysis does not find because it is not used
Errors that static code analysis does not find because it is not used
 
Machine Learning in Static Analysis of Program Source Code
Machine Learning in Static Analysis of Program Source CodeMachine Learning in Static Analysis of Program Source Code
Machine Learning in Static Analysis of Program Source Code
 

Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples

  • 1. Developing extension packages for Visual Studio 2005/2008/2010/2012 using C# with real-life samples Author: Paul Eremeev Date: 29.10.2012 Introduction Creating extension packages (plug-ins) for Microsoft Visual Studio IDE appears as quite an easy task at the first sight. There exist an excellent MSDN documentation, as well as various articles, examples and a lot of other additional sources on this topic. But, at the same time, it could also appear as a difficult task when an unexpected behavior is encountered along the way. Although it can be said that such issues are quite common to any programming task, the subject of IDE plug-in development is still not thoroughly covered at this moment. We develop PVS-Studio static code analyzer. Although the tool itself is intended for C++ developers, quite a large fragment of it is written in C#. When we just had been starting the development of our plug-in, Visual Studio 2005 had been considered as modern state-of-the-art IDE. Although, at this moment of Visual Studio 2012 release, some could say that Visual Studio 2005 is not relevant anymore, we still provide support for this version in our tool. During our time supporting various Visual Studio versions and exploring capabilities of the environment, we've accumulated a large practical experience on how to correctly (and even more so incorrectly!) develop IDE plug-ins. As holding all of this knowledge inside us was becoming unbearable, we've decided to publish it here. Some of our solutions that seem quite obvious right now were discovered in the course of several years. And the same issues could still haunt other plug-in developers. By publishing this collection of small articles we wish to contribute to this area of software development. We hope that developers interested in this topic will discover something new for themselves or at least will use the articles as a collection of how-to's and recipes for Visual Studio extension development. Creating, debugging and deploying extension packages for Microsoft Visual Studio 2005/2008/2010/2012 Abstract This article contains the overview of several different methods for extending Visual Studio IDE. The creation, debugging, registration and end-user deployment of Visual Studio extension packages will be explained in detail.
  • 2. Introduction The following series of articles is dedicated to the development of the extension package for Visual Studio 2005/2008/2010/2012 IDEs utilizing .NET framework and C# programming language. The following topics will be covered:  basic information on creating and debugging of MSVS plug-ins and maintaining these extensibility projects for several versions of Visual Studio inside a common source code base;  overview of Automation Object Model and various Managed Package Framework (MPF) classes  extending interface of the IDE though the automation object model's API (EnvDTE) and MPF (Managed Package Framework) classes with custom menus, toolbars, windows and options pages;  utilizing Visual C++ project automation model for gathering data needed to operate an external preprocessor/compiler, such as compilation arguments and settings for different platforms and configurations; The content of these articles is based on our experience in developing an MSVS extension package plug- in for PVS-Studio static analyzer. A more detailed in-depth references for the topics covered here are available at the end of each article through the links to MSDN library and several other external resources. The articles will cover the extension development only for Visual Studio 2005 and later versions. This limitation reflects that PVS-Studio also supports integration to Visual Studio starting only from version 8 (Visual Studio 2005). The main reason behind this is that a new extensibility API model was introduced for Visual Studio 2005, and this new version is not backward-compatible with previous IDE APIs. Creating and debugging Visual Studio VSPackage extension modules There exists a number of ways to extend Microsoft Visual Studio features. On the most basic level it's possible to automate simple routine user actions using macros. An add-In plug-in module can be used for obtaining an access to environment's UI objects, such as menu commands, windows etc. Extension of IDE's internal editors is possible through MEF (Managed Extensibility Framework) components (starting with MSVS 2010). Finally, a plug-in of the Extension Package type (known as VSPackage) is best suited for integrating large independent components into Visual Studio. VSPackage allows combining the environment automation through Automation Object Model with usage of Managed Package Framework classes (such as Package). It also provides the means of extending the automation model itself by registering user-defined custom automation objects within it. Such user automation objects, in turn, will become available through the same automation model from other user-created extensibility packages, providing these packages with access to your custom components. In its' earlier versions, PVS-Studio plug-in (versions 1.xx and 2.xx to be precise, when it was still known as Viva64) existed as an Add-In package. Starting with PVS-Studio 3.0 it was redesigned as VSPackage because the functionality Add-in was able to provide became insufficient for the tasks at hand and also the debugging process was quite inconvenient. After all, we wanted to have our own logo on Visual Studio splash screen! Projects for VSPackage plug-in modules. Creating the extension package. Contrary to Add-In plug-ins, developing VS extension packages requires the installation of Microsoft Visual Studio SDK for a targeted version of IDE, i.e. a separate SDK should be installed with every version of Visual Studio for which an extension is being developed. We will be examining the 2005, 2008, 2009 and 2012 versions of Visual Studio. Installation of Visual Studio SDK adds a standard project template for
  • 3. Visual Studio Package (on the 'Other Project Types -> Extensibility' page) to VS template manager. If selected, this template will generate a basic MSBuild project for an extension package, allowing several parameters to be specified beforehand, such as a programming language to be used and the automatic generation of several stub components for generic UI elements, such as menu item, an editor, user toolwindow etc. We will be using a C# VSPackage project (csproj), which is a project for managed dynamic-link library (dll). The corresponding csproj MSBuild project for this managed assembly will also contain several XML nodes specific to a Visual Studio package, such as VSCT compiler and IncludeinVSIX (in later IDE versions). The main class of an extension package should be inherited from the Microsoft.VisualStudio.Shell.Package. This base class provides managed wrappers for IDE interaction APIs, implementation of which is required from a fully-functional Visual Studio extension package. public sealed class MyPackage: Package { public MyPackage () {} ... } The Package class allows overriding of its base Initialize method. This method receives execution control at the moment of package initialization in the current session of IDE. protected override void Initialize() { base.Initialize(); ... } The initialization of the module will occur when it is invoked for the first time, but it also could be triggered automatically, for example after IDE is started or when user enters a predefined environment UI context state. Being aware of the package's initialization and shutdown timings is crucial. It's quite possible that the developer would be requesting some of Visual Studio functionality at the moment when it is still unavailable to the package. During PVS-Studio development we've encountered several such situations when the environment "punished us" for not understanding this, for instance, we are not allowed to "straightforwardly" display message boxes after Visual Studio enters a shutdown process. Debugging extension packages. Experimental Instance. The task of debugging a plug-in module or extension intended for an integrated development environment is not quite a typical one. Quite often such environment itself is utilized for plug-in's development and debugging. Hooking up an unstable module to this IDE can lead to instability of the environment itself. The necessity to uninstall a module under development from the IDE before every debugging session, which in turn often requires restarting it, is also a major inconvenience (IDE could block the dll that needs to be replaced by a newer version for debugging).
  • 4. It should be noted that a VSPackage debugging process in this aspect is substantially easier than that of an Add-In package. This was one of the main reasons for changing the project type of PVS-Studio plug- in. VSPackage solves the aforementioned development and debugging issues by utilizing Visual Studio Experimental Instance mechanism. Such an experimental instance could be easily started by passing a special command line argument: "C:Program Files (x86)Microsoft Visual Studio 10.0 Common7IDEdevenv.exe" /RootSuffix Exp An experimental instance of the environment utilizes a separate independent Windows registry hive (called experimental hive) for storing all of its settings and component registration data. As such, any modifications in the IDE's settings or changes in its component registration data, which were made inside the experimental hive, will not affect the instance which is employed for the development of the module (that is your main regular instance which is used by default). Visual Studio SDK provides a special tool for creating or resetting such experimental instances — CreateExpInstance. To create a new experimental hive, it should be executed with these arguments: CreateExpInstance.exe /Reset /VSInstance=10.0 /RootSuffix=PVSExp Executing this command will create a new experimental registry hive with a PVSExp suffix in its name for the 10th version of IDE (Visual Studio 2010), also resetting all of its settings to their default values in advance. The registry path for this new instance will look as follows: HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio10.0PVSExp While the Exp suffix is utilized by default for package debugging inside VSPackage template project, other experimental hives with unique names could also be created by the developer at will. To start an instance of the environment for the hive we've created earlier (containing PVSExp in its name), these arguments should be used: "C:Program Files (x86)Microsoft Visual Studio 10.0 Common7IDEdevenv.exe" /RootSuffix PVSExp A capacity for creating several different experimental hives on a single local workstation could be quite useful, as, for example, to provide a simultaneous and isolated development of several extension packages. After installing the SDK package, a link is created in the Visual Studio program's menu group for resetting the default Experimental Instance for this version of the IDE (for instance, "Reset the Microsoft Visual Studio 2010 Experimental Instance"). In the end, the faster you'll figure out how the debugging environment works, the fewer issues you'll encounter in understanding how plug-in initialization works during development. Registering and deploying Visual Studio extension packages Registering a VS extension package requires registering a package itself, as well as registering all of the components it integrates into the IDE (for example, menu items, option pages, user windows etc.). The
  • 5. registration is accomplished by creating records corresponding to these components inside the main system registry hive of Visual Studio. All the information required for registration is placed, after building your VSPackage, inside a special pkgdef file, according to several special attributes of the main class of your package (which itself should be a subclass of the MPF 'Package' class). The pkgdef can also be created manually using the CreatePkgDef utility. This tool collects all of the required module registration information from these special attributes by the means of .NET reflection. Let's study these registration attributes in detail. The PackageRegistration attribute tells the registration tool that this class is indeed a Visual Studio extension package. Only if this attribute is discovered will the tool perform its search for additional ones. [PackageRegistration(UseManagedResourcesOnly = true)] The Guid attribute specifies a unique package module identifier, which will be used for creating a registry sub-key for this module in Visual Studio hive. [Guid("a0fcf0f3-577e-4c47-9847-5f152c16c02c")] The InstalledProductRegistration attribute adds information to 'Visual Studio Help -> About' dialog and the loading splash screen. [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] The ProvideAutoLoad attribute links automatic module initialization with the activation of a specified environment UI context. When a user enters this context, the package will be automatically loaded and initialized. This is an example of setting module initialization to the opening of a solution file: [ProvideAutoLoad("D2567162-F94F-4091-8798-A096E61B8B50")] The GUID values for different IDE UI contexts can be found in the Microsoft.VisualStudio.VSConstants.UICONTEXT class. The ProvideMenuResource attribute specifies an ID of resource that contains user created menus and commands for their registration inside IDE. [ProvideMenuResource("Menus.ctmenu", 1)] The DefaultRegistryRoot attribute specifies a path to be used for writing registration data to the system registry. Starting with Visual Studio 2010 this attribute can be dropped as the corresponding data will be present in manifest file of a VSIX container. An example of registering a package for Visual Studio 2008: [DefaultRegistryRoot("SoftwareMicrosoftVisualStudio9.0")] Registration of user-created components, such as toolwidows, editors, option pages etc. also requires the inclusion of their corresponding attributes for the user's Package subclass. We will examine these attributes separately when we will be examining corresponding components individually. It's also possible to write any user-defined registry keys (and values to already existing keys) during package registration through custom user registration attributes. Such attributes can be created by inheriting the RegistrationAttribute abstract class.
  • 6. [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)] public class CustomRegistrationAttribute : RegistrationAttribute { } The RegistrationAttribute-derived attribute must override its Register and Unregister methods, which are used to modify registration information in the system registry. The RegPkg tool can be used for writing registration data to Windows registry. It will add all of the keys from pkgdef file passed to it into the registry hive specified by the /root argument. For instance, the RegPkg is utilized by default in Visual Studio VSPackage project template for registering the module in the Visual Studio experimental hive, providing convenient seamless debugging of the package being developed. After all of the registration information have been added to the registry, Visual Studio (devenv.exe) should be started with '/setup' switch to complete registration for new components inside the IDE. Deploying plug-ins for developers and end-users. Package Load Key. Before proceeding to describe the deployment process itself, one particular rule should be stressed: Each time after a new version of the distribution containing your plug-in is created, this new distribution should be tested on a system without Visual Studio SDK installed, as to make sure that it will be registered correctly on the end-user system. Today, as the releases of early versions of PVS-Studio are past us, we do not experience these kinds of issues, but several of these early first versions were prone to them. Deploying a package for Visual Studio 2005/2008 will require launching of regpkg tool for a pkgdef file and passing the path to Visual Studio main registry hive into it. Alternately, all keys from a pkgdef can be written to Windows registry manually. Here is the example of automatically writing all the registration data from a pkgdef file by regpkg tool (in a single line): RegPkg.exe /root:SoftwareMicrosoftVisualStudio9.0Exp "/pkgdeffile:objDebugPVS-Studio-vs2008.pkgdef" "C:MyPackageMyPackage.dll" After adding the registration information to the system registry, it is necessary to start Visual Studio with a /setup switch to complete the component registration. It is usually the last step in the installation procedure of a new plug-in. Devenv.exe /setup Starting the environment with this switch instructs Visual Studio to absorb resource metadata for user- created components from all available extension packages, so that these components will be correctly displayed by IDE's interface. Starting devenv with this key will not open its' main GUI window. We do not employ RepPkg utility as part of PVS-Studio deployment, instead manually writing required data to the registry by using our stand-alone installer. We chose this method because we have no desire of being dependent on some external third-party tools and we want full control over the installation process. Still, we do use RegPkg during plug-in development for convenient debugging.
  • 7. VSIX packages Beginning from Visual Studio 2010, VSPackage deployment process can be significantly simplified through the usage of VSIX packages. VSIX package itself is a common (Open Packaging Conventions) archive containing plug-in's binary files and all of the other auxiliary files which are necessary for plug- in's deployment. By passing such archive to the standard VSIXInstaller.exe utility, its contents will be automatically registered in the IDE: VSIXInstaller.exe MyPackage.vsix VSIX installer could also be used with /uninstall switch to remove the previously installed package from a system. A unique GUID of the extension package should be used to identify such package: VSIXInstaller.exe /uninstall: 009084B1-6271-4621-A893-6D72F2B67A4D Contents of a VSIX container are defined through the special vsixmanifest file, which should be added to plug-in's project. Vsixmanifest file permits the following properties to be defined for an extension:  targeted Visual Studio versions and editions, which will be supported by the plug-in;  a unique GUID identifier;  a list of components to be registered (VSPackage, MEF components, toolbox control etc.);  general information about the plug-in to be installed (description, license, version, etc.); To include additional files into a VSIX container, the IncludeInVSIX node should be added to their declarations inside your MSBuild project (alternately, they could also be marked as included into VSIX from their respective property windows, by opening it from Visual Studio Solution Explorer). <Content Include="MyPackage.pdb"> <IncludeInVSIX>true</IncludeInVSIX> </Content> In fact, the VSIX file could be viewed as an almost full-fledged installer for extension packages on the latest versions of Visual Studio (2010 and 2012), allowing the extensions to be deployed by a "one-click" method. Publishing your VSIX container in the official Visual Studio Gallery for extensions allows end- users to install such package through the Tools -> Extension Manager IDE dialog. This new VSIX installation procedure in Visual Studio 2010 does substantially alleviate package deployment for end-users (as well as for developers themselves). Some developers even had decided to support only VS2010 IDE and versions above it, if only not to get involved with the development of a package and installer for earlier IDE versions. Unfortunately, several issues can be encountered when using VSIX installer together with Visual Studio 2010 extension manager interface. For instance, sometimes the extension's binary files are not removed correctly after uninstall, which in turn blocks the VSIX installer from installing/reinstalling the same extension. As such, we advise you not to depend upon the VSIX installer entirely and to provide some backup, for example by directly removing your files from a previous plug-in installation before proceeding with a new one. Package Load Key Each VSPackage module loaded into Visual Studio must possess a unique Package Load Key (PLK). PLK key is specified through the ProvideLoadKey attribute for the Package subclass in 2005/2008 versions of the IDE.
  • 8. [ProvideLoadKey("Standard", "9.99", "MyPackage", "My Company", 100)] Starting with Visual Studio 2010, the presence of a PLK, as well as of the ProvideLoadKey attribute respectively, in a package is not required, but it can still be specified in case the module under development is targeting several versions of MSVS. The PLK can be obtained by registering at the Visual Studio Industry Partner portal, meaning it guarantees that the development environment can load only packages certified by Microsoft. However, systems containing Visual Studio SDK installed are exceptions to this, as Developer License Key is installed together with the SDK. It allows the corresponding IDE to load any extension package, regardless of validity of its PLK. Considering the aforementioned, one more time it is necessary to stress the importance of testing the distribution on a system without Visual Studio SDK present, because the extension package will operate properly on developer's workstation regardless of its PLK correctness. Extension registration specifics in the context of supporting several different versions of Visual Studio IDE By default, VSPackage project template will generate an extensibility project for the version of Visual Studio that is used for the development. This is not a mandatory requirement though, so it is possible to develop an extension for a particular version of IDE using a different one. It also should be noted that after automatically upgrading a project file to a newer version through devenv /Upgrade switch, the targeted version of the IDE and its' corresponding managed API libraries will remain unchanged, i.e. from a previous version of Visual Studio. To change the target of the extension to another version of Visual Studio (or to register an extension into this version to be more precise), you should alter values passed to the DefaultRegistryRoot attribute (only for 2005/2008 IDE versions, as starting from Visual Studio 2010 this attribute is no longer required) or change the target version in the VSIX manifest file (for versions above 2008). VSIX support appears only starting from Visual Studio 2010, so building and debugging the plug-in targeted for the earlier IDE version from within Visual Studio 2010 (and later) requires setting up all the aforementioned registration steps manually, without VSIX manifest. While changing target IDE version one should also not forget to switch referenced managed assemblies, which contain COM interface wrappers utilized by the plug-in, to the corresponding versions as well. Altering the IDE target version of the plug-in affects the following Package subclass attributes:  the InstalledProductRegistration attribute does not support overloading of its constructor with a (Boolean, String, String, String) signature, starting from Visual Studio 2010;  the presence of DefaultRegistryRoot and ProvideLoadKey attributes is not mandatory starting from Visual Studio 2010, as similar values are now specified inside VSIX manifest; References 1. MSDN. Experimental Build. 2. MSDN. How to: Register a VSPackage. 3. MSDN. VSIX Deployment. 4. MSDN. How to: Obtain a PLK for a VSPackage. 5. MZ-Tools. Resources about Visual Studio .NET extensibility. 6. MSDN. Creating Add-ins and Wizards.
  • 9. 7. MSDN. Using a Custom Registration Attribute to Register an Extension. Visual Studio Automation Object Model. EnvDTE interfaces. 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.
  • 10. Figure 1 — Visual Studio Automation Object Model (click the picture to zoom in)
  • 11. 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
  • 12. 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) { ... }
  • 13. 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()
  • 14. 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);
  • 15. 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.
  • 16. 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.
  • 17. 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; } } }
  • 18. 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:
  • 19. 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) { ... }
  • 20. 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:
  • 21. [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);
  • 22. [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; }
  • 23. 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. Visual Studio commands Abstract This article deals with creation, utilization and handling of Visual Studio commands in its extension modules through automation object model APIs and IDE services. The relations between IDE commands and environment UI elements, such as user menus and toolbars, will also be examined.
  • 24. Introduction Visual Studio commands provide a way for direct interaction with development environment through the keyboard input. Almost all capabilities of different dialog and tool windows, toolbars and user menus are represented by the environment's commands. In fact, main menu items and toolbar buttons are practically commands themselves. Although it is possible for a command not to possess a direct representation in the development environment's UI, as commands are not the UI elements per se, they can be represented by such UI elements as menu items and toolbar buttons. PVS-Studio IDE extension package integrates several subgroups of its commands into Visual Studio main menu, and these commands serve as one of the plug-in's main UI components (with another one being its MDI toolwindow), allowing a user to control all of the aspects of static code analysis either from the environment's UI or by invoking the commands directly through command line. Using IDE commands Any IDE command, regardless of its UI representation in the IDE (or of the lack of it), could be executed directly through the Command or Immediate windows, as well as by starting devenv.exe with the '/command' argument. The full name of a command is formed according to its affiliation with a functional group, as for example the commands of the 'File' main menu item. Command's full name could be examined in the 'Keyboard, Environment' Options page. Also, the 'Tools -> Customize -> Commands' dialog allows inspecting all of the commands which are currently registered within the environment. This dialog sorts the commands by their respective functional groups and UI presentation types (menus, toolbars), also allowing to modify, add or delete them. Commands can receive additional arguments which should be separated from the command's name by a space. Let's examine a call to a standard system command of the main menu, 'File -> New -> File' for example, with a passing of additional parameters to it through the Command Window: >File.NewFile Mytext /t:"GeneralText File" /e:"Source Code (text) Editor" A command's syntax generally complies with the following rules:  command's name and arguments are separated by a space  arguments containing spaces are wrapped by double quotes  The caret (^) is used as an escape character  One-character abridgments for command names can be combined, as for example, /case(/c) and /word(/w) could be presented as /cw When using the 'command' command-line switch, name of a command with all of its arguments should be wrapped by double quotes: devenv.exe /command "MyGroup.MyCommandName arg1 arg2" For the sake of convenience, a command could be associated with an alias: >alias MyAlias File.NewFile MyFile Commands integrated into IDE by PVS-Studio extension can be utilized through the /command switch as well. For example, this mode could be used for the integration of our static analysis into the automated
  • 25. build process. Our analyzer itself (PVS-Studio.exe) is a native command-line application, which operates quite similar to the compiler, i.e. it takes a path to the file containing source code and its compilation arguments and then it outputs analysis results to stdout/stderr streams. It's quite obvious that the analyzer could easily be integrated directly into the build system (for instance, into a system which is based on MSBuild, NMake or even GNU Make) at the same level where C/C++ compiler is being called. Of course, such integration already provides us, by its own definition, with complete enumeration of all of the source files being built, with all of their compilation parameters. In turn, this allows for a substitution (or supplementation) of a compiler call by call to the analyzer. Although the described scenario is fully supported by PVS-Studio.exe analyzer, it still requires a complete understanding of build system's internals as well as an opportunity to modify a system in the first place, which could be problematic or even impossible at times. Therefore, the integration of the analyzer into the build process can be performed in a more convenient way, on a higher level (i.e. at the level of Continuous Integration Server), by utilizing Visual Studio extension commands through the /command switch, for example, by using the PVS- Studio.CheckSolution command to perform analysis on MSVS solution. Of course, such use case is only possible when building Visual C++ native project types (vcproj/vcxproj). In case Visual Studio is started form a command line, the /command switch will be executed immediately after the environment is fully loaded. In this case, the IDE will be started as a regular GUI application, without redirecting its standard I/O streams to the console that was used to launch the environment. It should be noted that, in general, Visual Studio is a UI based development environment and so it is not intended for command line operations. It is recommended to employ Microsoft MSBuild utility for building inside build automation systems, as this tool supports all of native Visual Studio project types. Caution should be applied when using Visual Studio /command switch together with non-interactive desktop mode (for example when calling IDE from a Windows service). We've encountered several interesting issues ourselves when we were evaluating the possibility of integrating PVS-Studio static analysis into Microsoft Team Foundation build process, as Team Foundation operates as a Windows service by default. At that moment, our plug-in had not been tested for non-interactive desktop sessions and was incorrectly handling its child windows and dialogs, which in turn lead to exceptions and crashes. But Visual Studio itself experienced none of such issues, almost none to be more precise. The case is, Visual Studio displays a particular dialog for every user when it is started for a first time after an installation, and this dialog offers the user to select a default UI configuration. And it was this dialog that Visual Studio displayed for a LocalSystem account, the account which actually owns the Team Foundation service. It turns out that the same dialog is 'displayed' even in the non-interactive desktop mode, and it subsequently blocks the execution of the /command switch. As this user doesn't have an interactive desktop, he is also unable to close this dialog normally by manually starting the IDE himself. But, in the end, we were able to close the dialog manually by launching Visual Studio for LocalSystem account in the interactive mode through psexec tool from PSTools utilities. Creating and handling commands in VSPackage. Vsct files. VSPackage extension utilizes Visual Studio command table (*.vsct) file for creating and managing commands that it integrates into the IDE. Command tables are text files in XML format which can be compiled by VSCT compiler into binary CTO files (command table output). CTO files are then included as a resources into final builds of IDE extension packages. With the help of VSCT, commands can be associated with menus or toolbar buttons. Support for VSCT is available starting from Visual Studio
  • 26. 2005. Earlier IDE versions utilized CTC (command table compiler) files handling their commands, but they will not be covered in this article. In a VSCT file each command is assigned a unique ID — CommandID, a name, a group and a quick access hotkey combination, while its representation in the interface (if any) is specified by special flags. Let's examine a basic structure of VSCT file. The root element of file is 'CommandTable' node that contains the 'Commands' sub-node, which defines all of the user's commands, groups, menu items, toolbars etc. Value of the "Package" attribute of the "Commands" node must correspond with the ID of your extension. The "Symbols" sub-node should contain definitions for all identifiers used throughout this VSCT file. The 'KeyBindings' sub-node contains default quick access hotkey combinations for the commands. <CommandTable"http://schemas.microsoft.com/VisualStudio/2005-10- 18/CommandTable"> <Extern href="stdidcmd.h"/> <Extern href="vsshlids.h"/> <Commands> <Groups> ... </Groups> <Bitmaps> ... </Bitmaps> </Commands> <Commands package="guidMyPackage"> <Menus> ... </Menus> <Buttons> ... </Buttons> </Commands> <KeyBindings> <KeyBinding guid="guidMyPackage" id="cmdidMyCommand1" editor="guidVSStd97" key1="221" mod1="Alt" /> </KeyBindings> <Symbols> <GuidSymbol name="guidMyPackage" value="{B837A59E-5BF0-4190-B8FC- FDC35BE5C342}" /> <GuidSymbol name="guidMyPackageCmdSet" value="{CC8B1E36-FE6B-48C1- B9A9-2CC0EAB4E71F}"> <IDSymbol name="cmdidMyCommand1" value="0x0101" /> </GuidSymbol> </Symbols> </CommandTable>
  • 27. The 'Buttons' node defines the commands themselves by specifying their UI representation style and binding them to various command groups. <Button guid="guidMyPackageCmdSet" id="cmdidMyCommand1" priority="0x0102" type="Button"> <Parent guid="guidMyPackageCmdSet" id="MyTopLevelMenuGroup" /> <Icon guid="guidMyPackageCmdSet" id="bmpMyCommand1" /> <CommandFlag>Pict</CommandFlag> <CommandFlag>TextOnly</CommandFlag> <CommandFlag>IconAndText</CommandFlag> <CommandFlag>DefaultDisabled</CommandFlag> <Strings> <ButtonText>My &amp;Command 1</ButtonText> </Strings> </Button> The 'Menus' node defines the structure of UI elements (such as menus and toolbars), also binding them to command groups in the 'Groups' node. A group of commands bound with a 'Menu' element will be displayed by the UI as a menu or a toolbar. <Menu guid=" guidMyPackageCmdSet" id="SubMenu1" priority="0x0000" type="Menu"> <Parent guid="guidMyPackageCmdSet" id="MyTopLevelMenuGroup"/> <Strings> <ButtonText>Sub Menu 1</ButtonText> </Strings> </Menu> <Menu guid=" guidMyPackageCmdSet" id="MyToolBar1" priority="0x0010" type="Toolbar"> </Menu> And finally, the 'Groups' element organizes user's IDE command groups. <Group guid="guidMyPackageCmdSet" id="MySubGroup1" priority="0x0020"> <Parent guid="guidMyPackageCmdSet" id="MyGroup1" /> </Group> To include vsct file into MSBuild-based VSPackage project, it is necessary to insert the following node used for calling VSCT compiler into your csproj project file (note, that in the auto-generated project created from an SDK template, a vsct file will be already included in a project): <ItemGroup> <VSCTCompile Include="TopLevelMenu.vsct"> <ResourceName>Menus.ctmenu</ResourceName> </VSCTCompile> </ItemGroup> Next, the ProvideMenuResource attribute of your Package-derived class should point to this node that you've inserted into your project earlier:
  • 28. [ProvideMenuResource("Menus.ctmenu", 1)] ... public sealed class MyPackage : Package Assigning handlers to the commands defined in a VSCT file is possible through a service that is available through the IMenuCommandService. A reference for it can be obtained by the GetService method of your Package subclass: OleMenuCommandService MCS = GetService(typeof(IMenuCommandService)) as OleMenuCommandService; Let's examine an example in which we assign a handler to a menu command (this command should be declared in a vsct file beforehand): EventHandler eh = new EventHandler(CMDHandler); CommandID menuCommandID = new CommandID(guidCommand1CmdSet, id); //ID and GUID should be the same as in the VCST file OleMenuCommand menuItem = new OleMenuCommand(eh, menuCommandID); menuItem.ParametersDescription = "$"; MCS.AddCommand(menuItem); To obtain command's arguments while handling its invocation, the EventArgs object should be casted into OleMenuCmdEventArgs: void CMDHandler(object sender, EventArgs e) { OleMenuCmdEventArgs eventArgs = (OleMenuCmdEventArgs)e; if (eventArgs.InValue != null) param = eventArgs.InValue.ToString(); ... } Handling commands through EnvDTE.DTE interfaces. The EnvDTE.DTE automation object allows for a direct manipulation (creation, modification and execution) of commands through the dte.Commands interface and dte.ExecuteCommand method. Utilizing the Automation Object Model for invoking, modifying and creating IDE commands, as opposed to using VSCT mechanism available only for VSPackage, allows the interaction with IDE commands from within Add-In extension packages as well. The DTE automation object allows a direct creation, modification and invocation of commands through the DTE.Commands interface. A command can be directly added to the IDE by Commands.AddNamedCommand method (but only for an Add-In extension): dte.Commands.AddNamedCommand(add_in, "MyCommand", "My Command", "My Tooltip", true); The command added in this way will be preserved by the IDE — it will reappear in the menu after IDE restart, even if the extension which created the command is not loaded itself. That's why this method should only be utilized during the first initialization of an Add-In module, after its installation (this is described in the article dedicated to Visual Studio Automation Object Model). The OnConnection
  • 29. method of an Add-In contains a special initialization mode which is invoked only for a single time in the module's entire lifetime. This method can be used to integrate UI elements into the IDE: public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom) { switch(connectMode) { case ext_ConnectMode.ext_cm_UISetup: ... break; ... } } The EnvDTE.Command interface represents a single IDE command. This interface can be used to modify a command which it references. It permits managing IDE commands from either a VSPackage, or an Add-In module. Let's obtain a reference to the EnvDTE.Command object for our custom command 'MyCommand1' and utilize this interface to assign a 'hot-key' to it for a quick access: EnvDTE.Command MyCommand1 = MyPackage.DTE.Commands.Item("MyGroup.MyCommand1", -1); MyCommand1.Bindings = new object[1] { "Global::Alt+1" }; The quick-access combination assigned to MyGroup.MyCommand1 will now be available through 'Keyboard, Environment' environment settings dialog. As was mentioned before, Visual Studio command is not a UI element by itself. The Commands.AddCommandBar method allows the creation of such UI elements, as main menu items, toolbars, context menus and the association of these elements with user-created commands. CommandBar MyToolbar = dte.Commands.AddCommandBar("MyToolbar1", vsCommandBarType.vsCommandBarTypeToolbar) as CommandBar; CommandBar MyMenu = dte.Commands.AddCommandBar("MyMenu1", vsCommandBarType.vsCommandBarTypeMenu) as CommandBar; CommandBarButton MyButton1 = MyCommand1.AddControl(MyToolbar) as CommandBarButton; MyButton1.Caption = "My Command 1"; The 'Delete' method of Command/ CommandBar objects could be utilized to remove a command or toolbar from IDE. MyCommand1.Delete(); In general, it is not recommended creating commands each time an Add-In plug-in is loaded and removing them each time it is un-loaded, as such behavior could slow-down the initialization of IDE itself. Even more, in case the OnDisconnect method is somehow interrupted in the process, it is possible that the user commands will not be completely deleted from the IDE. That is why it is advised that the integration, and subsequent removal, of IDE commands should be handled at the times of module's
  • 30. installation/uninstallation, as for example, by obtaining DTE interface reference from a stand-alone installer application. The initialization of Add-In modules and acquisition of DTE references is thoroughly described in the article devoted to EnvDTE Automation Object Model. Any IDE command (either custom or default one) could be called by the ExecuteComand method. Here is the example of invoking our custom MyCommand1 command: MyPackage.DTE.ExecuteCommand("MyGroup.MyCommand1", args); To handle command execution, an Add-In extension should be derived from the IDTCommandTarget interface and it should also implement the Exec method: public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled) { handled = false; if(executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault) { if(commandName == "MyAddin1.Connect.MyCommand1") { ... handled = true; return; } } } References 1. MSDN. Visual Studio Commands and Switches. 2. MSDN. Visual Studio Command Table (.Vsct) Files. 3. MSDN. Designing XML Command Table (.Vsct) Files. 4. MSDN. Walkthrough: Adding a Toolbar to the IDE. 5. MSDN. How VSPackages Add User Interface Elements to the IDE. 6. MZ-Tools. HOWTO: Adding buttons, commandbars and toolbars to Visual Studio .NET from an add-in. 7. MSDN. How to: Create Toolbars for Tool Windows. Visual Studio tool windows Abstract This article covers the extension of Visual Studio IDE through integration of a custom user toolwindow into the environment. Discussed are the issues of window registration and initialization in VSPackage and Add-In plug-in modules, hosting of user components and handling of window's events and states. Introduction Tool windows are child windows of Visual Studio MDI (Multiple Document Interface) interface and they are responsible for presenting various pieces of information to the user. Solution Explorer and Error List
  • 31. are the examples of tool windows. Usually tool windows' contents are not associated with any files and do not contain any editors, as separate document windows are reserved for such tasks. For instance, PVS-Studio extension package integrates several tool windows into the IDE, with Output Window being the primary one. All other of its tool windows can be opened from this main window, as, for example, a search window for the grid. PVS-Studio Output Window itself can be opened from Visual Studio main menu (PVS-Studio -> Show PVS-Studio Output Window), but it also will be invoked automatically each time the analysis starts. In most cases IDE creates and utilizes just a single instance for each one of its toolwindows, and this instance will be preserved until IDE itself needs to shut down. Therefore, pressing the 'close' button on a tool window does actually hide it, and when this window is invoked for the second time, it becomes visible again, thus preserving any data that it contained before being 'closed'. But still, is it possible to crate Multi-Instance toolwindows in the IDE, which are the windows that can exist in several instances at once. A toolwindow can also be associated with a certain UI context (as the so called dynamic window), and such window will be automatically displayed when the user enters this context. Integration of a tool window into the IDE is supported by VSPackage and Add-In extensions (although the methods for it are different); it requires the specification of the window's initial settings and its registration in the system registry. Registering and initializing user tool windows A VSPackage project template that is installed together with Visual Studio SDK allows you to create a sample tool window in the extension project which this template generates. Such a project should already contain all of the basic components which will be described below, so it could be conveniently used as a sample for experimenting with Visual Studio toolwindow integration process for VSPackage plug-ins. Registering, initializing and invoking a tool window in VSPackage Registering a custom user window in the environment requires writing of the data that defines this window into a special section of Visual Studio registry hive. This process can be automated by generating a pkgdef file that can contain all of the required window registration information. The contents of this pkgdef files can be specified through special registration attributes of your Package subclass. The immediate registration of a user-created tool window into VSPackage extension is handled by ProvideToolWindow attribute of Package subclass: [ProvideToolWindow(typeof(MyWindowPane), Orientation = ToolWindowOrientation.Right, Style = VsDockStyle.Tabbed, Window = Microsoft.VisualStudio.Shell.Interop.ToolWindowGuids.Outputwindow, MultiInstances = false, Transient = true, Width = 500, Height = 250, PositionX = 300, PositionY = 300)] Let's examine several parameters of this attribute. The 'Typeof' parameter points to user implementation of the window's client area (a subclass of ToolWindowPane). The 'MultiInstances' parameter enables the Multi-Instance mode for a window, in which multiple instances of the window can be opened simultaneously. The Orientation, Size and Style parameters specify the initial position of a window when it is opened for the first time by the user. It should be noted that the position specified by these parameters will only be used once, when a tool window is displayed for the first time; at all of
  • 32. the subsequent iterations of opening this window, the IDE will be restoring its screen position from the previous one, that is the position before a window was closed. The 'Transient' parameter indicates whether the window will be automatically opened after Visual Studio environment is loaded in case it already have been opened during the previous session of the IDE. It should also be remembered that the initialization of a user window by VSPackage (the initialization itself will be covered later) does not necessarily occur at the same moment as the initialization of a Package subclass for which we provided this registration attribute. For example, after implementing a tool window for PVS-Studio plug-in, we've encountered an issue in which our custom window was automatically opened (but not focused/displayed) and placed among other window tabs at the bottom of the main window, and it was done immediately after Visual Studio started up, even though we've passed the 'Transient=true' parameter to the ProvideToolWindow attribute. Although the plug-in itself is always initialized at IDE start-up, the window had not been fully initialized until after a first call to it, which was evident by the corrupted icon on aforementioned tab. A dynamic visibility context can be specified for a window by the ProvideToolWindowVisibility attribute: [ProvideToolWindowVisibility(typeof(MyWindowPane), /*UICONTEXT_SolutionExists*/"f1536ef8-92ec-443c-9ed7-fdadf150da82")] In this example, the window is set to be automatically displayed when the user enters the "Solution Exists" UI context. Take a note that each one of user's toolwindow requires a separate attribute and a window's type should be passed as a first argument to it. The FindToolWindow method of a Package subclass can be utilized to create and display a toolwindow from a VSPackage extension. This method returns a reference to the specified toolwindow object, creating it if necessary (for instance, in case a single-instance window is called for a first time). Following is the example of invoking a single-instance toolwindow: private void ShowMyWindow(object sender, EventArgs e) { ToolWindowPane MyWindow = this.FindToolWindow(typeof(MyToolWindow), 0, true); if ((null == MyWindow) || (null == MyWindow.Frame)) { throw new NotSupportedException(Resources.CanNotCreateWindow); } IVsWindowFrame windowFrame = (IVsWindowFrame) MyWindow.Frame; ErrorHandler.ThrowOnFailure(windowFrame.Show()); } In this example, the window will be created in case it is called for the first time, or the window will be made visible in case it had been created before and then hidden. The FindToolWindow 's third argument of the bool type specifies whether a new instance of a window should be created if the method was unable to find an already existing one. To create a Multi-Instance tool window, the CreateToolWindow method can be used. It allows the creation of a window with a pre-defined identifier. An example of invoking such window: private void CreateMyWindow(object sender, EventArgs e) { for (int i = 0; ; i++)
  • 33. { // Find existing windows. var currentWindow = this.FindToolWindow(typeof(MyToolWindow), i, false); if (currentWindow == null) { // Create the window with the first free ID. var window = (ToolWindowPane)this.CreateToolWindow(typeof(MyToolWindow), i); if ((null == window) || (null == window.Frame)) { throw new NotSupportedException(Resources.CanNotCreateWindow); } IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; ErrorHandler.ThrowOnFailure(windowFrame.Show()); break; } } } Note that in this example the FindToolWindow method receives 'false' value as its third argument, i.e. we are searching for an unoccupied index before initializing a new window instance. As was mentioned above, the environment will preserve position of a window after it is closed. But if, for whatever reason, it is necessary to specify the size and position of a window, it could be achieved through the SetFramePos method of the IVsWindowFrame interface: Guid gd = Guid.Empty; windowFrame.SetFramePos(VSSETFRAMEPOS.SFP_fDockBottom, ref gd, 20, 20, 200, 200); A call to the SetFramePos() should always be made only after the Show() method is executed. Creating and invoking a window from Add-In extension A user tool window can be initialized from an Add-In extension with the help of the EnvDTE Window2 interface: public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom) { _applicationObject = (DTE2)application; _addInInstance = (AddIn)addInInst; EnvDTE80.Windows2 window; AddIn add_in; object ctlobj = null; Window myWindow; // Get the window object
  • 34. add_in = _applicationObject.AddIns.Item(1); window = (Windows2)_applicationObject.Windows; // This section specifies the path and class name of the windows // control that you want to host in the new tool window, as well as // its caption and a unique GUID. string assemblypath = "C:MyToolwindowMyToolWindowControl.dll"; string classname = " MyToolWindowControl.MyUserControl"; string guidpos = "{E87F0FC8-5330-442C-AF56-4F42B5F1AD11}"; string caption = "My Window"; // Creates the new tool window and inserts the user control into it. myWindow = window.CreateToolWindow2(add_in, assemblypath, classname, caption, guidpos, ref ctlobj); myWindow.Visible = true; } In the example above, a user toolwindow was created using the MyToolWindowControl.MyUserControl as a client area control. The MyToolWindowControl.MyUserControl class could either be located in the same assembly as the add-in that initializes it, or it could be provided by a stand-alone assembly with a full COM visibility (though the 'Register for COM Interop' option in project settings). The regular composite UserControl subclass could be utilized as MyUserControl. Implementing a user toolwindow in a VSPackage module Tool window consists of a frame border and a client area. A frame is provided by the environment and is responsible for performing docking with other interface objects of the environment, as well as for size and position of the window itself. A client area is a pane, controlled by a user, which houses the contents of a window. Tool windows can host user-created WinForms and WPF components and are capable of handling regular events, such as OnShow , OnMove, etc. A user toolwindow, or its client area to be more precise, can be implemented by inheriting the class representing a standard empty IDE window — ToolWindowPane. [Guid("870ab1d8-b434-4e86-a479-e49b3c6797f0")] public class MyToolWindow : ToolWindowPane { public MyToolWindow():base(null) { this.Caption = Resources.ToolWindowTitle; this.BitmapResourceID = 301; this.BitmapIndex = 1; ... } } The Guid attribute is used to uniquely identify each custom user window. In case a plug-in module creates several windows of different types, each one of them should be identified by its own unique Guid. A ToolWIndowPane subclass can be subsequently modified and host user-controlled components.
  • 35. Hosting user components A base ToolWindowPane class implements an empty tool window of the environment. Inheriting form this class allows hosting user-created WinForms or WPF components. Up until Visual Studio 2008 version, toolwindows only provided a native supported for WinForms user components, although it still was possible to host WPF components through the WPF Interoperability ElementHost object. Starting from Visual Studio 2010, toolwindows themselves are based on WPF technology, although they still provide a backward compatibility for hosting of WinForms components. To host a user-created WinForms component inside a user toolwindow, the Window property of the ToolWindowPane base class should be overridden: public MyUserControl control; public MyToolWindow():base(null) { this.Caption = Resources.ToolWindowTitle; this.BitmapResourceID = 301; this.BitmapIndex = 1; this.control = new MyUserControl(); } public override IWin32Window Window { get { return (IWin32Window)control; } } In the example above, the 'MyUserControl' object is a regular composite component of the System.Windows.Forms.UserControl type and it can host any other user component inside itself. UserControl can also host WPF components by using WPF ElementHost object. Starting from Visual Studio 2010, WPF components can be hosted by tool windows natively. To do this, a reference to the WPF component should be passed to the 'Content' property of a base class: public MyToolWindow():base(null) { this.Caption = Resources.ToolWindowTitle; this.BitmapResourceID = 301; this.BitmapIndex = 1; base.Content = new MyWPFUserControl(); } Please note that using the two methods described above simultaneously is not possible. When a reference to WPF component is assigned to the base.Content property, an overridden Window property is ignored. The main PVS-Studio 'Output' window of our extension plug-in hosts a virtual grid based on SourceGrid open-source project. This window provides an interface for handling the results of static analysis. The grid itself is bound to a regular ADO.NET table of the System.Data.Datatable type, which is utilized for storing analysis results. Until 4.00 version of PVS-Studio extension, it utilized a regular IDE 'Error List'
  • 36. window, but as the analyzer evolved, the capabilities of this default window became insufficient. Apart from being un-extendable with such specific static analysis UI elements as, for example, false positive suppression and filtering mechanisms, the Error List is itself basically a 'real' grid, as it stores all of the displayed elements inside itself. Therefore, this grid only permits an adequate handling of 1-2k messages at a time, performance wise, as a greater number of messages already can cause quite a noticeable lag to the environment's UI. On the other hand, our own practice of using static analysis on relatively large projects, such as Chromium or LLVM, demonstrated that a total number of diagnostic messages (taking into account all of the marked false alarms and low-lever user diagnostics as well) could easily reach tens of thousands or even more. Therefore, by implementing a custom output window, based on virtual grid that is connected to a DB table, PVS-Studio is able to display and provide convenient handling for hundreds of thousands of diagnostic messages at once. Also, the ability for a convenient and flexible filtering of the analysis results is quite an important aspect of handling a static analyzer, as the manual examination even of only such a "tiny" amount of messages as 1-2k is nearly impossible for a single user. The storage of analysis results in a Datatable object by itself provides quite a convenient filtering mechanism based on a simple SQL queries, even more so because the results of such queries become visible immediately inside the bound virtual grid. Handling tool windows events A client area of a tool window (represented by our ToolWindowPane subclass) can process the regular events of user-interface interactions. The IVsWindowFrameNotify3 interface can be used for subscribing to window events. Let's provide an example of implementing this interface: public sealed class WindowStatus: IVsWindowFrameNotify3 { // Private fields to keep track of the last known state private int x = 0; private int y = 0; private int width = 0; private int height = 0; private bool dockable = false; #region Public properties // Return the current horizontal position of the window public int X { get { return x; } } // Return the current vertical position of the window public int Y { get { return y; } } // Return the current width of the window public int Width {
  • 37. get { return width; } } // Return the current height of the window public int Height { get { return height; } } // Is the window dockable public bool IsDockable { get { return dockable; } } #endregion public WindowStatus() {} #region IVsWindowFrameNotify3 Members // This is called when the window is being closed public int OnClose(ref uint pgrfSaveOptions) { return Microsoft.VisualStudio.VSConstants.S_OK; } // This is called when a window "dock state" changes. public int OnDockableChange(int fDockable, int x, int y, int w, int h) { this.x = x; this.y = y; this.width = w; this.height = h; this.dockable = (fDockable != 0); return Microsoft.VisualStudio.VSConstants.S_OK; } // This is called when the window is moved public int OnMove(int x, int y, int w, int h) { this.x = x; this.y = y; this.width = w; this.height = h; return Microsoft.VisualStudio.VSConstants.S_OK; }
  • 38. // This is called when the window is shown or hidden public int OnShow(int fShow) { return Microsoft.VisualStudio.VSConstants.S_OK; } /// This is called when the window is resized public int OnSize(int x, int y, int w, int h) { this.x = x; this.y = y; this.width = w; this.height = h; return Microsoft.VisualStudio.VSConstants.S_OK; } #endregion } As evident by this sample code above, the WindowsStatus class implementing the interface is able to process such window state changes, as the alterations in window's size, position, visibility properties and so on. Now, let's subscribe our window for handling these events. It requires the OnToolWindowCreated method to be overridden in our ToolWindowPane subclass: public class MyToolWindow: ToolWindowPane { public override void OnToolWindowCreated() { base.OnToolWindowCreated(); // Register to the window events WindowStatus windowFrameEventsHandler = new WindowStatus(); ErrorHandler.ThrowOnFailure(((IVsWindowFrame)this.Frame).SetProperty( (int)__VSFPROPID.VSFPROPID_ViewHelper, (IVsWindowFrameNotify3)windowFrameEventsHandler)); } ... } Controlling window state A window state can be controlled through event handlers of our IVsWindowFrameNotify3 implementation. The 'OnShow' method notifies the extension package about changes in tool window's visibility state, allowing to track the appearance of the window to a user, when, for example, user switches windows by
  • 39. clicking on window tabs. Current visibility state could be obtained by the fShow parameter, which corresponds to the __FRAMESHOW list. The 'OnClose' method notifies about the closure of a window frame, allowing to define IDE behavior in case ofthis event with the pgrfSaveOptions parameter, which controls the default document saving dialog (__FRAMECLOSE). The OnDockableChange method informs the package on window's docking status changes. The fDockable parameter indicates whether a window is docked to another one; other parameters control window's size and position before and after the docking event. The parameters of 'OnMove' and 'OnSize' methods provide window's coordinates and size while it is being dragged of resized. References 1. MSDN. Kinds of Windows. 2. MSDN. Tool Windows. 3. MSDN. Tool Window Essentials. 4. MSDN. Tool Window Walkthroughs. 5. MSDN. Arranging and Using Windows in Visual Studio. 6. MZ-Tools. HOWTO: Understanding toolwindow states in Visual Studio. Integrating into Visual Studio settings Abstract This article covers the extension of Visual Studio by integrating into its 'Settings' dialog pages. Option page registration and integration into the IDE for different kinds of extension packages will be examined, as well as the means to display various standard and user-created components inside a custom settings page. Also covered are the ways of accessing environment settings through Visual Studio Automation model and preservation mechanism for option pages. Introduction Visual Studio employs a single unified dialog window to provide an access to the settings of its various components. This window is available through the IDE Tools -> Options main menu item. A basic element of Visual Studio settings is an Options Page. The Options dialog window arranges its pages in a tree-like structure according to the membership of the pages in their respective functional groups. Each one of these pages could be uniquely identified by the name of its group and its own name. For example, Visual Basic source code editor settings page is "Text Editor, Basic". Extension packages are able to access and modify the values of various settings from option pages registered in the IDE. They can also create and register their own custom options pages in the environment through the automation object model and MPF classes (Managed Package Framework, available only to VSPackage extensions). Visual Studio contains an embedded mechanism for preserving the state of its settings objects; it is enabled by default, but can be overridden or disabled. Creating and registering user options pages It can be useful for a Visual Studio extension plug-in to be associated with one or several custom options pages from the Tools->Options dialog window. Such tool for configuring an extension will conform to
  • 40. the environment's UI paradigm and is actually quite convenient for handling your extension's settings from within the IDE itself. The methods of implementing and integrating custom user options page into the IDE can vary, as they depend upon the type of the extension being developed and the technology being used (either an automation model or MPF). Integrating settings through an MPF class Managed Package Framework allows creating custom options pages by inheriting from the DialogPage class. As the environment loads each of its options pages independently when accessing the corresponding section of the Tools->Options dialog, each page must be implemented with an independent object as a result. The object which implements a custom page should be associated with your VSPackage through the ProvideOptionPage attribute of the corresponding Package subclass. [ProvideOptionPageAttribute(typeof(OptionsPageRegistration), "MyPackage", "MyOptionsPage", 113, 114, true)] This attribute designates the names for the options page itself and for group that it belongs to, as it should be displayed in the IDE options dialog. A separate attribute should be used for every custom page that is to be integrated by the extension. In fact, this attribute is used to provide a registration for the page through pkgdef file and it does not directly affect the execution in any other way. For the user options page to be correctly rendered by the environment, the page should be registered in the following node of the system registry: HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<VsVersion>ToolsOptionsPages Here <VsVersion> is the version number of Visual Studio IDE, 10.0 for example. This record will be automatically created when ProvideOptionPage attribute is utilized. It should be noted that a correct uninstallation of an extension package also requires purging all of the records that this extension had written to the system registry before, including the ones belonging to its options pages. As the versions of Visual Studio IDE starting from 2010 can utilize VSIX packages to deploy/uninstall VSPackage plug-ins, the VSIX installer will automatically perform such registry operations according to its pkgdef file. But earlier versions of IDE may require manual registry cleaning, for instance by a stand-alone installer application. The 6th bool-type argument of the attribute's constructor allows the user's custom options page to be registered as an automation object. This exposes the page to the Automation Object Model, providing an access to the its settings through the EnvDTE interfaces for other third-party plug-ins. Registering an automation object requires the creation of several records in the system registry (it is performed automatically when using the aforementioned attributes) in the following nodes: HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<VersionPackages <PackageGUID>Automation HKEY_LOCAL_MACHINESOFTWAREMicrosoftVisualStudio<Version> AutomationProperties The ProvideProfile attribute allows registering an options page or any other independent object with the build-in mechanism for setting's state preservation, provided that such user object implements the IProfileManager interface.