## 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 section 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 ExecuteCommand 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; } } }

# Visual Studio tool windows

This item covers the extension of Visual Studio IDE through integration of a custom user tool window 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) 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 tool windows, 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 tool windows in the IDE, which are the windows that can exist in several instances at once. A tool window 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 tool window 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 file 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 ProvideToolWindowattribute 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 ProvideToolWindowVisibilityattribute:  [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 tool window 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 tool window from a VSPackage extension. This method returns a reference to the specified tool window 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 tool window: 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 Windows2 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:\\MyToolwindow\\MyToolWindowControl.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 tool window 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 tool window, 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 from this class allows hosting user-created WinForms or WPF components. Up until Visual Studio 2008 version, tool windows only provided a native support for WinForms user components, although it still was possible to host WPF components through the WPF Interoperability ElementHost object. Starting from Visual Studio 2010, tool windows 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 tool window, 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 of this 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 or resized.

# Integrating into Visual Studio settings

This item 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 a 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. 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_MACHINE\SOFTWARE\Microsoft\VisualStudio\\ ToolsOptionsPages Here 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 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_MACHINE\SOFTWARE\Microsoft\VisualStudio\\Automation HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\\ 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. Implementing an MPF DialogPage subclass As a minimal requirement for DialogPage subclass to implement an IDE options page, this derived class should contain at least one public property. Here is an example of such a basic implementation: namespace MyPackage { class MyOptionsPage : DialogPage { bool myOption = true; public bool MyOption { get { return this. myOption; } set { this. myOption = value; } } } } To display such a generic implementation of DialogPage subclass, IDE will utilize a standard PropertyGrid control as a client area of the page window, which in turn will contain all public properties of this subclass. This could be convenient in case your extension's configuration properties are rather simple, so handling them through embedded PropertyGrid editors does not present any trouble. Using a control native to IDE will also exempt you from common issues with incorrect UI scaling for components on different DPI resolutions in the Visual Studio Options dialog. However, if you want to host a user-created control inside an options page, it could be achieved by overriding the Window property of your DialogPage subclass: [BrowsableAttribute(false)] protected override IWin32Window Window { get { return MyUserControl; } } A reference to the IWin32Window object implementing the window's client area should be returned by this property. Visual Studio required its options pages to be constant, i.e. they should not be recreated for any of their subsequent calls. As Windows Forms objects can delete and recreate their window handles at will, it is recommend passing a reference to the object derived from a UserControl type. The AutomationObject property of a custom options page derived from the DialogPage class determines the public properties which are shown by the default display mechanism and persisted by the IDE. AutomationObject returns a reference to the DialogPage subclass itself by default, but if it returns a reference to some other object, then the properties of that returned object will be preserved and displayed instead. By default, system registry serves as a local storage for state preservation mechanism. Overriding the DialogPage.SaveSettingsToStorage method makes it possible to change the way of object's state preservation method (similar could be done to the state restoration through LoadSettingsFromStorage override). public override void SaveSettingsToStorage() { ... } Custom pages registered as automation objects can store their settings, along with settings from other options pages, in an external XML file through the standard IDE command Tools -> Import/Export Settings, by the default implementation of the SaveSettingsToXml method which also can be overridden if necessary. Of course, integrating a page into Visual Studio settings dialog is not the exclusive or mandatory way of creating configuration interface for an IDE plug-in. If the capabilities of a regular PropertyGrid are insufficient and there are no future plans to utilize the embedded mechanism for settings preservation, then it could be quite reasonable to implement an IDE-independent settings dialog. The advantages of this approach are high portability (for instance, a plug-in that could be used with multiple IDEs) and complete control over the dialog window itself, which in turn substantially alleviates the support of various end-user configurations. On the downside, such solution makes your settings inaccessible to third-party developers through the automation object model. For configuring its settings, PVS-Studio extension package utilizes a custom state preservation mechanism that operates through an external XML file, so that the options pages which the plug-in integrates into the IDE are provided only as means for displaying and modifying these internal settings. Initially, the embedded settings preservation functionality of Visual Studio created conflicts with PVS-Studio's own settings mechanism in the earlier versions of the plug-in, leading to setting de-synchronization issues. This demonstrated to us that even in the presence of an independent settings management inside the extension, it still may be necessary to override some of Visual Studio regular mechanisms (maybe even by an empty method). Integrating settings through an Add-In XML definition A user options page can be integrated into the IDE through an independent XML definition of an Add-In extension. The contents of such a user page should be implemented as a user component, for example as an System.Windows.Forms.UserControl. This component is not associated with an Add-In itself, thus it can be implemented either inside the extension's assembly or as an independent library altogether. An add-in XML file could even be created for such user component alone, without any definitions for an Add-In extension. Let's examine an XML definition for an Add-In module which also contains a definition for a user's custom options page.  Microsoft Visual Studio Macros 10.0 Microsoft Visual Studio 10.0 My Add in My Addin 1 c:\MyAddIn1\MyAddin1.dll MyAddin1.Connect 0 1 0 c:\MyAddIn1\MyAddin1.dll MyAddin1.UserControl1  A description for custom options page is located inside the node. The sub-node points to the library which contains a user component for the client area of the page. The contains the full name of a user component in a Namespace.ClassName format. The and nodes define position of a user page inside the Tools->Options tree-like structure by specifying page's group and personal names. Any existing group names, as well as a new one, can be used as a value. As evident by the example, a user MyAddin1.UserControl1 component is located inside the same assembly as the add-in itself, though this is not a mandatory requirement. Visual Studio loads a page after it is opened by a user for the first time through the Options dialog window. As opposed to the integration of a page through the Managed Package Framework, the description of a page is stored within an XML description addin file, so the page will be initialized only after the environment discovers such a file. Visual Studio reads addin files which are available to it immediately after start-up. The Environment -> Add-In/Macross Security options page specifies the paths which are used for addin discovery. Contrary to custom option pages implemented through inheriting the MPF classes, such high level approach to the integration does not register such a page as an automation object, and so it does not provide ways to access the page's contents through automation object model or to utilize the embedded state preservation mechanism of the environment.

## Accessing option pages through the automation

Visual Studio Automation Object Model provides the means of accessing various system settings of the Tools->Options dialog, excluding some of the pages, such as 'Dynamic Help' and 'Fonts and Colors pages (they are available through separate APIs). User-created custom option pages are also available through the automation model in case they are registered as automation objects themselves (as described in the previous section). The get_Properties method can be utilized to obtain the necessary settings: Properties propertiesList = PVSStudio.DTE.get_Properties("MyPackage", "MyOptionsPage"); The option page can be identified by its own name and the name of group it belongs to. Here is the example of obtaining value for a specific property: Property MyProp1 = propertiesList.Item("MyOption1"); The value of the property can be accessed and modified through the MyProp1.Value. The ShowOptionPage method of the Package MPF subclass can be used to open and display the custom options page inside the Options window. MyPackage.ShowOptionPage(typeof(MyOptionsPage)); As evident by the example, this method takes the type of a user-created DialogPage subclass. However, if it is required to open any other page which is not part or your extension project, a standard IDE page for example, then it could be located by its GUID identifier available at this registry branch: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\ ToolsOptionsPages\\ Here is the name of a page inside the Tools -> Options dialog. Following is the example of opening a standard TextEditor -> GeneralIDE settings page through the IMenuCommandService global service: string targetGUID = "734A5DE2-DEBA-11d0-A6D0-00C04FB67F6A"; var command = new CommandID(VSConstants.GUID_VSStandardCommandSet97, VSConstants.cmdidToolsOptions); var mcs = GetService(typeof(IMenuCommandService)) as MenuCommandService; mcs.GlobalInvoke(command, targetGUID); In fact, this code is equivalent to the execution of the Tools.Options IDE command. It can be invoked through the ExecuteCommand method of the EnvDTE.DTE: dte.ExecuteCommand("Tools.Options", "734A5DE2-DEBA-11d0-A6D0-00C04FB67F6A").

# Visual Studio project model

This item covers the structure of Visual Studio project model and its implementation on the example of Visual C++ (VCProject). Also included are the cases of using the project model for enumeration of project elements and obtaining their compilation properties through the corresponding configurations. Isolated Shell based Atmel Studio environment will be examined as an example of a third-party implementation of the project model.

## Introduction

Visual Studio project model is a collection of interfaces describing the properties of a compiler, linker and other build tools, as well as the structure of MSVS-compatible projects themselves, and it is connected with the Visual Studio Automation Object Model through the VCProjects late-bound properties. Visual C++ project model extends the standard Visual Studio project model, providing access to the specific functionality of Visual C++ (vcproj/vcxproj) project types. Visual C++ project model is a stand-alone COM component available through the VCProjectEngine.dll assembly, which could also be used independently outside of Visual Studio development environment. It is possible for third-party developers to create custom implementations of the project model, adding the support of new languages and compilers into Visual Studio.

## Project model structure

Visual Studio provides an extendable project-neutral object model that represents solutions, projects, code objects, documents, etc. Every MSVS project type has a corresponding project automation interface. Every tool in the environment that has a project also has an object of the 'Project' type associated with it. Standard Visual C++ project model also complies with this general automation project model scheme: Projects |- Project -- Object(unique for the project type) |- ProjectItems (a collection of ProjectItem) |- ProjectItem (single object) -- ProjectItems (another collection) |- Object(unique for the project type)  The 'Projects' interface provides an ensemble of abstract objects of the 'Project' type. The 'Project' interface defines an abstract project, i.e. it can reference an object from any project model that complies with the standard scheme. Any peculiar properties of a specific model should be defined through a special interface which is unique only to this model alone. A reference for such an object could be acquired through the Project.Object property. For instance, specific properties of Visual C++ project could be obtained through the VCProject interface, and for the Atmel Studio model it will be the AvrGCCNode interface: Project proj; ... VCProject vcproj = proj.Object as VCProject; AvrGCCNode AvrGccProject = proj.Object as AvrGCCNode; It is possible to obtain a list of all projects loaded in IDE and belonging to any project model type through the dte.Solution.Projects field; projects belonging to a particular model can be acquired through the DTE.GetObject method (see the example below for Visual C++ model): Projects vcprojs = m_dte.GetObject("VCProjects") as Projects; To obtain projects of all types, the following code could be used: Projects AllProjs = PVSStudio.DTE.Solution.Projects; The ProjectItems interface represents an ensemble of abstract solution tree elements of ProjectItem type. Similar to the Project interface, the ProjectItem can define any kind of element; it can even contain the same ProjectItems collection inside itself (accessible through the ProjectItem.ProjectItems) or it can be a Project altogether. An object unique for a specific project model can be obtained through the ProjectItem.Object field. For instance, a Visual C++ source code file is represented by a VCFile type, and Atmel Studio source file be the AvrGccFileNode interface: ProjectItem projectItem; ... VCFile file = projectItem.Object as VCFile; AvrGccFileNode file = projectItem.Object as AvrGccFileNode; An embedded project can be obtained in a similar manner when such an element of the hierarchy represents a project: Project proj = projectItem.Object as Project;

## Recursively walking all elements of a Solution tree's branch

The interface for controlling hierarchies IVsHierarchy can be used to perform a passing of Solution tree's branch. This interface provides an access to abstract nodes of a tree, each one of which in turn could be a leaf, a container of elements or a link to another hierarchy. Each tree node is uniquely identified through the DWORD identifier VSITEMID. Such identifiers are unique within the scope of a single hierarchy and possess a limited lifetime within it. A hierarchy object can be obtained for a tree branch of a single project through the VsShellUtilities.GetHierarchy method: public static IVsHierarchy ToHierarchy(EnvDTE.Project project) { System.IServiceProvider serviceProvider = new ServiceProvider(project.DTE as Microsoft.VisualStudio.OLE.Interop.IServiceProvider); Guid guid = GetProjectGuid(serviceProvider, project); if (guid == Guid.Empty) return null; return VsShellUtilities.GetHierarchy(serviceProvider, guid); } In the example above, the hierarchy was obtained for a project through its GUID identifier. Consider the example of obtaining this GUID identifier for a project: private static Guid GetProjectGuid(System.IServiceProvider serviceProvider, Project project) { if (ProjectUnloaded(project)) return Guid.Empty; IVsSolution solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution; IVsHierarchy hierarchy; solution.GetProjectOfUniqueName(project.FullName, out hierarchy); if (hierarchy != null) { Guid projectGuid; ErrorHandler.ThrowOnFailure( hierarchy.GetGuidProperty( VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ProjectIDGuid, out projectGuid)); if (projectGuid != null) { return projectGuid; } } return Guid.Empty; } The IEnumHierarchies interface permits obtaining all of the hierarchies for projects of a particular type through the solution. GetProjectEnum method. Here is an example of obtaining the hierarchies for every Visual C++ project in a solution tree: IVsSolution solution = PVSStudio._IVsSolution; if (null != solution) { IEnumHierarchies penum; Guid nullGuid = Guid.Empty; Guid vsppProjectGuid = new Guid("8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"); //You can ask the solution to enumerate projects based on the //__VSENUMPROJFLAGS flags passed in. For //example if you want to only enumerate C# projects use //EPF_MATCHTYPE and pass C# project guid. See //Common\IDL\vsshell.idl for more details. int hr = solution.GetProjectEnum( (uint)(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION | __VSENUMPROJFLAGS.EPF_MATCHTYPE), ref vsppProjectGuid, out penum); ErrorHandler.ThrowOnFailure(hr); if ((VSConstants.S_OK == hr) && (penum != null)) { uint fetched; IVsHierarchy[] rgelt = new IVsHierarchy[1]; PatternsForActiveConfigurations.Clear(); while (penum.Next(1, rgelt, out fetched) == 0 && fetched == 1) { ... } } } As evident by the example above, the GetProjectEnum method provides hierarchies for projects based on a project kind specified by the GUID identifier. GUID identifiers for regular Visual Studio/MSBuild project types can be obtained here. The penum.Next() method allows us to enumerate all project hierarchies we've acquired (the rgelt array). It should be remembered that user-created project models could possess their own unique identifiers in case they define a new project type for themselves. Such custom user identifiers can be obtained from XML project files of the corresponding models. But our own experience in developing PVS-Studio IDE plug-in demonstrates that an opposite situation is quite possible as well, that is, when a user-created project type uses a GUID from one of the stock project types, usually the one from which it was derived. In particular, we've encountered a VCProject type that was extended to provide development for Android platform. As a result, this project model extension had caused crashes in our plug-in because it did not provide several properties which are otherwise present in VCProject model (OpenMP for example) through the automation API. An intricacy of this situation is that such an extended project model type cannot be differentiated from a regular one, and thus, it is quite hard to correctly process it as well. Therefore, when you are extending a project model through your custom types, to avoid such conflicts with various IDE components (including other third-party extensions as well), it is always important to remember the necessity of providing means to uniquely identify your types. Possessing an IVsHierarchy for the project, we are able to recursively enumerate all the elements of such solution tree branch through the hierarchy.GetProperty method, which in turn provides us with the specified properties for each one of the hierarchy nodes: EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, MyProjectHierarchy, 0, true); ... public void EnumHierarchyItemsFlat(uint itemid, IVsHierarchy hierarchy, int recursionLevel, bool visibleNodesOnly) { if (hierarchy == null) return; int hr; object pVar; hr = hierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_ExtObject, out pVar); ProjectItem projectItem = pVar as ProjectItem; if (projectItem != null) { ... } recursionLevel++; //Get the first child node of the current hierarchy being walked hr = hierarchy.GetProperty(itemid, (visibleNodesOnly ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild :(int)__VSHPROPID.VSHPROPID_FirstChild), out pVar); Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr); if (VSConstants.S_OK == hr) { //We are using Depth first search so at each level we recurse //to check if the node has any children // and then look for siblings. uint childId = GetItemId(pVar); while (childId != VSConstants.VSITEMID_NIL) { EnumHierarchyItemsFlat(childId, hierarchy, recursionLevel, visibleNodesOnly); hr = hierarchy.GetProperty(childId, (visibleNodesOnly ? (int)__VSHPROPID.VSHPROPID_NextVisibleSibling : (int)__VSHPROPID.VSHPROPID_NextSibling), out pVar); if (VSConstants.S_OK == hr) { childId = GetItemId(pVar); } else { Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(hr); break; } } } } private uint GetItemId(object pvar) { if (pvar == null) return VSConstants.VSITEMID_NIL; if (pvar is int) return (uint)(int)pvar; if (pvar is uint) return (uint)pvar; if (pvar is short) return (uint)(short)pvar; if (pvar is ushort) return (uint)(ushort)pvar; if (pvar is long) return (uint)(long)pvar; return VSConstants.VSITEMID_NIL; }  A ProjectItem object that we've acquired for each one of the tree's nodes will allow us to obtain its corresponding Visual C++ object (as well as the object for any other custom project model) through the Object filed, as was described earlier. Enumerating all projects in solution tree DTE.Solution.Projects interface can be used to enumerate all projects in the solution: if (m_DTE.Solution.Projects != null) { try { foreach (object prj in m_DTE.Solution.Projects) { EnvDTE.Project proj = prj as EnvDTE.Project; if (proj != null) WalkSolutionFolders(proj); } } }  Besides projects, Solution tree can also contain folder nodes (Solution Folders). They should also be taken into account while processing each Project element: public void WalkSolutionFolders(Project prj) { VCProject vcprj = prj.Object as VCProject; if (vcprj != null && prj.Kind.Equals(VCCProjectTypeGUID)) { if (!ProjectExcludedFromBuild(prj)) { IVsHierarchy projectHierarchy = ToHierarchy(prj); EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, projectHierarchy, 0, false); } } else if (prj.ProjectItems != null) { foreach (ProjectItem item in prj.ProjectItems) { Project nextlevelprj = item.Object as Project; if (nextlevelprj != null && !ProjectUnloaded(nextlevelprj)) WalkSolutionFolders(nextlevelprj); } } }  Projects that are excluded from the build should be inspected separately, as they are not accessible through the automation model after being unloaded from the IDE: public bool ProjectExcludedFromBuild(Project project) { if (project.UniqueName.Equals("", StringComparison.InvariantCultureIgnoreCase)) return true; Solution2 solution = m_DTE.Solution as Solution2; SolutionBuild2 solutionBuild = (SolutionBuild2)solution.SolutionBuild; SolutionContexts projectContexts = solutionBuild.ActiveConfiguration.SolutionContexts; //Skip this project if it is excluded from build. bool shouldbuild = projectContexts.Item(project.UniqueName).ShouldBuild; return !shouldbuild; } Enumerating selected elements The DTE.SelectedItems interface can be used to enumerate solution elements which are selected in the Solution Explorer window. foreach (SelectedItem item in items) { VCProject vcproj = null; if (item.Project != null) { vcproj = item.Project.Object as VCProject; if (vcproj != null && item.Project.Kind.Equals("{" + VSProjectTypes.VCpp + "}")) { IVsHierarchy projectHierarchy = ToHierarchy(item.Project); PatternsForActiveConfigurations.Clear(); EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, projectHierarchy, 0, false, files, showProgressDialog); } else if (item.Project.ProjectItems != null) { //solution folder if (!ProjectUnloaded(item.Project)) WalkSolutionFolders(item.Project); } } else if (item.ProjectItem != null) { //walking files ... else if (item.ProjectItem.ProjectItems != null) if (item.ProjectItem.ProjectItems.Count > 0) WalkProjectItemTree(item.ProjectItem); } } private void WalkProjectItemTree(object CurrentItem) { Project CurProject = null; CurProject = CurrentItem as Project; if (CurProject != null) { IVsHierarchy projectHierarchy = ToHierarchy(CurProject); PatternsForActiveConfigurations.Clear(); EnumHierarchyItemsFlat(VSConstants.VSITEMID_ROOT, projectHierarchy, 0, false); return; } ProjectItem item = null; item = CurrentItem as ProjectItem; if (item != null) { ... if (item.ProjectItems != null) if (item.ProjectItems.Count > 0) { foreach (object NextItem in item.ProjectItems) WalkProjectItemTree(NextItem); } } }

## Visual C++ project model. Configurations and properties of projects and files

To this moment, we have been examining a general, exterior part of Visual Studio project model, declared mostly in EnvDTE namespace, which is available from any version of project model implementation. Now, let's talk about one of the regular implementations of this model: Microsoft Visual C++. It is declared in the VisualStudio.VCProjectEngine namespace. Visual C++ project model implements a general Visual Studio project model, so interfaces described in the previous chapters can be utilized for projects of this particular model as well. Interfaces that are specific to Visual C++ model are defined inside the Microsoft.VisualStudio.VCProjectEngine.dll assembly. This assembly should be added to the references of the extension that is being developed. Visual C++ physically stores build configurations (compilation and linking parameters, pre-build and post-build steps, external tool command lines, etc.) for C/C++ source files inside its XML-based project files (vcproj/vcxproj). These settings are available to Visual Studio users through property page dialogs. Each combination of project's build configuration (Debug, Release, etc.) and platform (Win32, IA64, x64, etc.) is associated with a separate collection of settings. Although majority of the settings are defined at the project level, it is possible to redefine separate properties for each individual file (file properties are inherited from its project by default). The list of properties which can be redefined at the file level is dependent upon the type of a file in question. For example, only the ExcludedFromBuild property can be redefined for header files, but cpp source files permit the redefinition for any of its compilation properties. Obtaining configurations Visual C++ project model presents property pages through the VCConfiguration (for a project) and VCFileConfiguration (for a file) interfaces. To obtain these objects we will start from a ProjectItem object which represents an abstract Solution tree element. ProjectItem item; VCFile vcfile = item.Object as VCFile; Project project = item.ContainingProject; String pattern = "Release|x64"; if (String.IsNullOrEmpty(pattern)) return null; VCFileConfiguration fileconfig = null; IVCCollection fileCfgs = (IVCCollection)vcfile.FileConfigurations; fileconfig = fileCfgs.Item(pattern) as VCFileConfiguration; if (fileconfig == null) if (fileCfgs.Count == 1) fileconfig = (VCFileConfiguration)fileCfgs.Item(0);  In the example above we've acquired a file configuration for VCFile object (which represents a C/C++ header or a source file) by passing a configuration pattern (configuration's name and platform) to the Item() method. Build configuration pattern is defined on the project level. The following example demonstrates the acquisition of active configuration (the one that is selected in IDE) of a project. ConfigurationManager cm = project.ConfigurationManager; Configuration conf = cm.ActiveConfiguration; String platformName = conf.PlatformName; String configName = conf.ConfigurationName; String pattern = configName + "|" + platformName; return pattern; The ActiveConfiguration interface should be handled with care. Quite often we've encountered exceptions when calling it from our PVS-Studio IDE extension package. In particular, this field sometimes becomes inaccessible through the automation object model when a user is building a project, or in the presence of any other heavy user interaction with Visual Studio UI. As there is no assured way of predicting such user actions, it is advised to provide additional error handlers for such 'bottlenecks' when accessing settings with automation model. It should be noted that this particular situation is not related to COM exception handling that was described in the previous article dedicated to EnvDTE interfaces, and it is probably related to some internal issues within the automation model itself. Next, let's acquire the configuration for a project that contains the file in question: VCConfiguration cfg=(VCConfiguration)fileconfig.ProjectConfiguration; While the interfaces representing configurations themselves contain settings only from the 'General' tab of the property pages, references for individual build tools can be acquired through the VCConfiguration.Tools and VCFileConfiguration.Tool interfaces (note that a single file contains settings respectively for only one build tool). Let's examine the VCCLCompilerTool interface representing the C++ compiler: ct = ((IVCCollection)cfg.Tools).Item("VCCLCompilerTool") as VCCLCompilerTool; ctf = fileconfig.Tool as VCCLCompilerTool; Now let's acquire the contents of, for example, the AdditionalOptions field belonging to the compiler tool, using the Evaluate method to process any macros that we can encounter within its value: String ct_add = fileconfig.Evaluate(ct.AdditionalOptions); String ctf_add = fileconfig.Evaluate(ctf.AdditionalOptions); Property Sheets Property sheets are XML files with a *.props extension. They allow an independent definition of project's build properties, i.e. the command line parameters for various building tools, such as a compiler or a linker. Property sheets also support inheritance and can be used for specifying build configurations for several projects at once, i.e. the configuration defined inside the project file itself (vcproj/vcxproj) could inherit some of its properties from single or multiple props files. To handle property sheets, Visual C++ project model provides the VCPropertySheet interface. A collection of VCPropertySheet objects can be obtained through the VCConfiguration.PropertySheets field: IVCCollection PSheets_all = fileconfig.PropertySheets; Similarly, the PropertySheets field of the VCPropertySheet interface provides a reference to a collection of child property sheet files for this object. Let's examine the recursive enumeration of all of the project's property sheets: private void ProcessAllPropertySheets(VCConfiguration cfg, IVCCollection PSheets) { foreach (VCPropertySheet propertySheet in PSheets) { VCCLCompilerTool ctPS = (VCCLCompilerTool)((IVCCollection)propertySheet.Tools).Item( "VCCLCompilerTool"); if (ctPS != null) { ... IVCCollection InherPSS = propertySheet.PropertySheets; if (InherPSS != null) if (InherPSS.Count != 0) ProcessAllPropertySheets(cfg, InherPSS); } } } In the example above we've obtained an object of VCCLCompilerTool type (that is compilation settings) for PropertySheet on every level. In this way we could gather all compilation parameters defined in every property sheet, including the embedded ones. The VCPropertySheet interface does not contain means to evaluate macros within its fields, so as a work-around, the Evaluate method from the project's configuration can be used instead. But, such approach could also lead to the incorrect behavior in case the value of the macro being evaluated is related to the props file itself. For instance, several MSBuild macros which were introduced in the MSBuild version 4 could also be utilized inside vcxproj projects from Visual Studio 2010. Let's take the MSBuildThisFileDirectory macro that evaluates as a path to the directory containing file in which it is used. Now, evaluating this macro through the cfg.Evaluate will result in a path to the vcxproj file, and not to props file, which actually does contains this macro. All of the property sheets in Visual C++ project can be divided between user and system files. By user files we understand the props files which were created and added to the project by a user himself. But even an empty template-generated MSVC project often includes several property sheets by default. These system props files are utilized by the environment to specify various compilation parameters which were set inside the project's property page interface by the user. For example, setting up the CharacterSet property to use Unicode manually in the Property Page interface will result in the appearance of a special property sheet in the 'Property Sheets' window which will define several preprocessor symbols (Unicode, _Unicode), and this properties subsequently will be inherited by the project. Therefore when processing properties from inside a Property sheet, one should always remember that compilation symbols defined in system props files are also returned by their corresponding property in the project's configuration through the automation API. Evidently, processing these two simultaneously while gathering compilation arguments can result in a duplication of such arguments.

## Atmel Studio project model. Compilation settings in project toolsets.

We have examined an implementation of Visual Studio project model for C/C++ projects from Microsoft Visual C++, the model that is included into Visual Studio distribution by default. But Visual Studio automation model can be extended by adding interfaces for interacting with other custom project models from third-party developers (such custom project model can actually be implemented as a VSPackage extension). Therefore, if a developer of custom project model does provide interfaces for it, when it will be possible to interact with it the same way that we've interacted with Visual Studio regular models, such as Visual C++ one, as was described earlier. For example, let's examine the interfaces of the project model that is provided by Atmel Studio IDE, the environment intended for development of embedded solutions. Now, you could probably ask - how does the topic of this article concern Atmel Studio, and we were always examining the interactions with Visual Studio? But, the truth is - Atmel Studio itself is the Visual Studio isolated shell application. We will not be discussing the essence of isolated shells here, and I will only mention that it is possible to develop the same kinds of extensions and plugins for isolated shells, as it is possible for the regular Visual Studio versions. You can familiarize yourself with some of the specifics regarding the development of Visual Studio plug-ins, including the isolated shell applications, in the previous chapter. Now, Atmel project model itself is the implementation of a standard Visual Studio project model. And, exactly as for the Visual C++ projects, the common interfaces could be utilized with Atmel Studio projects as well. The interfaces that are specific for this model are declared in the AvrGCC.dll, AvrProjectManagement.dll and Atmel.Studio.Toolchain.Interfaces.dll files. These files can be obtained by downloading the Atmel Studio Extension Developer's Kit (XDK). Atmel Studio project model physically stores its build parameters in cproj project files which themselves at the same time serve as project files for MSBuild system (as all of the standard project types in Visual Studio). Atmel project model supports C/C++ languages and utilizes special editions of GCC compilers for the purpose of building its source files. Types of projects and toolchains Atmel Studio provides 2 types of projects: C and C++ projects. Please note that these project types possess different GUIDs, so this should be taken into account when traversing the project tree. Atmel project model also provides 2 compile tool chains - GNU C compiler and GNU C++ Compiler, each one possessing a separate set of options. It should be noted that while C projects can hold only C compiler options, the toolchain of C++ project consists of both C and C++ options sets. The appropriate compile options set will be selected by the build system automatically during compilation according to the extension of the source file being built. This means that in the case of a "mixed" project, 2 compile tool sets will be utilized at the same time! The available option sets for each individual project can be obtained through the ProjectToolchainOptions interface. ProjectItem item; ... AvrGccFileNode file = item.Object as AvrGccFileNode; AvrGCCNode project = file.ProjectMgr as AvrGCCNode; AvrProjectConfigProperties ActiveProps = project.ConfigurationManager.GetActiveConfigProperties(); ProjectToolchainOptions ToolChainOptions = ActiveProps.ToolchainOptions; if (ToolChainOptions.CppCompiler != null) //Toolchain compiler options for C++ compiler if (ToolChainOptions.CCompiler != null) //Toolchain compiler options for C Compiler Obtaining compilation settings To extract individual compilation settings themselves, the CompilerOptions object that we've obtained earlier (a common base type for CppCompilerOptions and CCompilerOptions) can be utilized. Some of the settings could be taken directly from this object, such as Include paths of a project: CompilerOptions options; ... List Includes = options. IncludePaths; Please note that a part of the settings are shared between all project types (i.e. between the C and C++ ones). An example of such common settings is the one holding system includes: List SystemIncludes = options. DefaultIncludePaths; But most of other settings are available only through the OtherProperties property of the Dictionary> type. As evident by the type of this property, each setting (a key) corresponds to a list of one or more values. However, if you wish to obtain a whole command line passed to MSBuild from the project (and to the compiler thereafter), and not the individual settings values, such command line can be immediately obtained through the CommandLine property (which is a lot easier than in the case of VCProjectEngine!): String RawCommandLine = this.compilerOptions.CommandLine; It should be noted that General settings, such as system includes, still will not be present in a command line obtained in this way. Also worth noting is that either individual setting values or the whole command line obtained in this way could still contain a number of unexpanded MSBuild macros. The GetAllProjectProperties method of the AvrGCCNode interface can be utilized to resolve such macros: AvrGCCNode project; ... Dictionary MSBuildProps = new Dictionary(); project.GetAllProjectProperties().ForEach(x =>MSBuildProps.Add(x.Key, x.Value)); Now we can replace each macro we encounter with the corresponding values from MSBuildProps collection. Each file in the project can possess additional build parameters, additional compiler flags to be more precise. We can obtain such flags through the AvrFileNodeProperties interface: AvrGccFileNode file; ... AvrFileNodeProperties FileProps = file.NodeProperties as AvrFileNodeProperties; String AdditionalFlags = FileProps.CustomCompilationSetting;

## References

1. MSDN. Visual C++ Project Model.
2. MSDN. Project Modeling.
3. MSDN. Automation Model Overview.

Report Article

## User Feedback

There are no comments to display.

## Create an account

Register a new account

• 2
• 0
• 0
• 1
• 1

• 12
• 19
• 10
• 14
• 10