Working with Visual C++ Project Properties

Here's how to use the interfaces in Microsoft.VisualStudio.VCProjectEngine.dll—in particular IVCRulePropertyStorage—to get and set Visual C++ project properties and project-item properties. We recommend that you use these interfaces because Visual C++ project properties are not available through GetProperty and are not guaranteed to be stable across versions.

Visual C++ project properties are defined in the files in %ProgramFiles%\MSBuild\Microsoft.Cpp\v4.0\V120\1033\. For example, general.xml contains general project properties, debugger_general.xml contains general debugger properties, and so on. You can use the IVCRulePropertyStorage interface to read and write only those properties for which one of these files exists.

You set and get Visual C++ project-item properties from the Tool property.

Different file item types have different sets of properties. To find the item type of a file in the Visual Studio Solution Explorer, open the shortcut menu for the file and choose Properties. For example, the item type of a .cpp file is displayed as C/C++ compiler. Here's how to find all properties of a given item type:

  1. Search for the item-type display name in the %ProgramFiles%\MSBuild\Microsoft.Cpp\v4.0\V120\1033\ directory. For example, if you search for "C/C++ compiler", the ProjectItemsSchema.xml file shows that the name of the corresponding item type is ClCompile.

  2. To find the rules file for that item type, search in the same directory. (In this case, because ClCompile shows up in many unrelated .targets and .props files, you can restrict your search to .xml files.) The rules file for the ClCompile item type is cl.xml.

  3. Search the rules file for the property you want to find. For example, the property that has the display name Additional Include Directories has the property name AdditionalIncludeDirectories.

  4. The rules file also determines where a given property is persisted. The properties associated with the ClCompile item type are persisted in the project file. (Look for the Persistence attribute.) The property values you change are persisted in the project file.

For information about how to use and extend Visual C++ property pages, see the following articles. They mention Visual Studio 2010, but the information about project properties is still valid.

To observe how the following code works, you can integrate it into a VSPackage named TestVCProjectProperties that has a menu command named Test Project Properties. For information about how to do this, see Walkthrough: Creating a Menu Command By Using the Visual Studio Package Template.

Getting and setting Visual C++ project properties

  1. In the Project Properties dialog box, on the Extensions tab, add a reference to Microsoft.VisualStudio.VCProjectEngine, and on the Framework tab, add a reference to System.Windows.Forms.

  2. Open the TestVCProjectPropertiesPackage.cs file. Add these using directives:

    using EnvDTE;
    using EnvDTE80;
    using System.Windows.Forms;
    using Microsoft.VisualStudio.VCProjectEngine;
    
  3. Add a reference to the application object—in this case DTE2—to the TestVCProjectPropertiesPackage class, and instantiate it in the Initialize method:

    public sealed class TestProjectPropertiesPackage : Package
    {
        DTE2 dte2;
        . . .
        protected override void Initialize()
        {
            Debug.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();
    
            // Add command handlers for the menu (commands must exist in the .vsct file)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if ( null != mcs )
            {
                // Create the command for the menu item.
                CommandID menuCommandID = new CommandID(GuidList.guidTestProjectPropertiesCmdSet, (int)PkgCmdIDList.cmdidMyCommand);
                MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
                mcs.AddCommand( menuItem );
            }
    
            dte2 = (DTE2)GetService(typeof(DTE));
        }
    }
    
  4. Remove the existing code from the MenuItemCallback method. Add code to get the open projects. Make sure that a project is actually open.

    private void MenuItemCallback(object sender, EventArgs e) 
    {
        VCProject prj; 
        Projects projColl = dte2.Solution.Projects; 
    
        if (projColl.Count == 0) 
        {
            MessageBox.Show("You must have a project open in the experimental instance."); 
            return; 
        }
        if (projColl.Count > 0) 
        {
            // To be filled in later
        }
    }
    
  5. Get the first project and find the project configuration named Win32|Debug:

    private void MenuItemCallback(object sender, EventArgs e) 
    {
        VCProject prj; 
        Projects projColl = dte2.Solution.Projects; 
    
        if (projColl.Count == 0) 
        {
            MessageBox.Show("You must have a project open in the experimental instance."); 
            return; 
        }
        if (projColl.Count > 0) 
        {
            prj = (VCProject)dte2.Solution.Projects.Item(1).Object;
             VCConfiguration config = prj.Configurations.Item("Debug|Win32");
        }
    }
    
  6. In this step, you get the IncludePath property from the ConfigurationDirectories rule and add D:\Include to the current value.

    Note

    You can use either of two methods to get the property values. The GetUnevaluatedPropertyValue method gets the value without evaluating any properties—for example, $(SolutionDir)—and the GetEvaluatedPropertyValue method expands these properties.

    private void MenuItemCallback(object sender, EventArgs e) 
    {
        VCProject prj; 
        Projects projColl = dte2.Solution.Projects; 
    
        if (projColl.Count == 0) 
        {
            MessageBox.Show("You must have a project open in the experimental instance."); 
            return; 
        }
        if (projColl.Count > 0) 
        {
            prj = (VCProject)dte2.Solution.Projects.Item(1).Object;
             VCConfiguration config = prj.Configurations.Item("Debug|Win32");
    
             IVCRulePropertyStorage rule = config.Rules.Item("ConfigurationDirectories") as IVCRulePropertyStorage;
             string rawValue = rule.GetUnevaluatedPropertyValue("IncludePath");
             string evaluatedValue = rule.GetEvaluatedPropertyValue("IncludePath");
             rule.SetPropertyValue("IncludePath", rawValue + "D:\\Include;");
    
             // Get the new property value
             MessageBox.Show(rule.GetUnevaluatedPropertyValue("IncludePath"));
        }
    }
    
  7. Build the solution and start debugging. The build appears in a second instance of Visual Studio known as the experimental instance. Open a Visual C++ project in the experimental instance. On the menu bar, choose Tools, Test Project Properties. A dialog box shows the value $(VC_IncludePath);$(WindowsSDK_IncludePath);D:\Include;D:\Include;.

Getting and setting Visual C++ project-item properties

  1. In the TestVCProjectProperties project you created in the previous procedure, go to the MenuItemCallback method. Add the following code to find a .cpp file in the project, get the Additional Include Directories property and set it to D:\Include, and display a dialog box that shows the new value:

    private void MenuItemCallback(object sender, EventArgs e)
    {
        VCProject prj;
        Projects projColl = dte2.Solution.Projects;
    
        if (projColl.Count == 0)
        {
            MessageBox.Show("You must have a project open in the experimental instance.");
            return;
        }
        if (projColl.Count > 0)
        {
            prj = (VCProject)dte2.Solution.Projects.Item(1).Object;
    
            VCConfiguration config = prj.Configurations.Item("Debug|Win32");
            IVCRulePropertyStorage rule = config.Rules.Item("ConfigurationDirectories") as IVCRulePropertyStorage;
             string rawValue = rule.GetUnevaluatedPropertyValue("IncludePath");
             string evaluatedValue = rule.GetEvaluatedPropertyValue("IncludePath");
             rule.SetPropertyValue("IncludePath", rawValue + "D:\\Include;");
    
             // Get the new property value
             MessageBox.Show(rule.GetUnevaluatedPropertyValue("IncludePath"));
    
            foreach (VCFile file in prj.Files)
            {
                if (file.FileType == eFileType.eFileTypeCppCode)
                {
                    VCFileConfiguration fileConfig = file.FileConfigurations.Item("Debug|Win32") as VCFileConfiguration;
                    IVCRulePropertyStorage fileRule = fileConfig.Tool as IVCRulePropertyStorage;
                    string evaluatedValue2 = fileRule.GetEvaluatedPropertyValue("AdditionalIncludeDirectories");
                    fileRule.SetPropertyValue("AdditionalIncludeDirectories", "D:\\Include");
                    MessageBox.Show(fileRule.GetEvaluatedPropertyValue("AdditionalIncludeDirectories"));
                }
            }  
        }
    }
    
  2. Build the solution and start debugging. Open a Visual C++ project in the experimental instance. On the menu bar, choose Tools, Test Project Properties. A dialog box shows the value $(VC_IncludePath);$(WindowsSDK_IncludePath);D:\Include;D:\Include;, and a second dialog box shows the value D:\Include.

  3. To examine where this value is persisted, save all files in the open project in the experimental instance. The new value appears in the .vcxproj file.

Detecting property value changes

  1. You can subscribe to the ItemPropertyChange2 event to detect when a Visual C++ project property or project-item property gets a different value.

    In the TestVCProjectPropertiesPackage class, create an event handler for this event. In this case, the handler just displays a dialog box that shows the property that was changed.

    void OnVCProjectEngineItemPropertyChange(Object item, string strPropertySheet, string strItemType, string PropertyName)
    {
        MessageBox.Show("got property change event for " + PropertyName);
    }
    
  2. In the Initialize method, get the VCProjectEngineEventsObject from the application object's events, and add the event handler you just created:

    protected override void Initialize()
    {
        Debug.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
        base.Initialize();
    
        // Add the command handlers for the menu (commands must exist in the .vsct file)
        OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
        if ( null != mcs )
        {
            // Create the command for the menu item
             CommandID menuCommandID = new CommandID(GuidList.guidTestProjectPropertiesCmdSet, (int)PkgCmdIDList.cmdidMyCommand);
            MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
            mcs.AddCommand( menuItem );
        }
    
        dte2 = (DTE2)GetService(typeof(DTE));
        VCProjectEngineEvents vcProjectEvents = dte2.Events.GetObject("VCProjectEngineEventsObject") as VCProjectEngineEvents;
    
        vcProjectEvents.ItemPropertyChange2 += new _dispVCProjectEngineEvents_ItemPropertyChange2EventHandler(OnVCProjectEngineItemPropertyChange);
    }
    
  3. Build the solution and start debugging. Open a Visual C++ project in the experimental instance. On the menu bar, choose Tools, Test Project Properties. A dialog box shows $(VC_IncludePath);$(WindowsSDK_IncludePath);D:\Include;D:\Include;, a second dialog box shows Got property change event for IncludePath, a third dialog box shows D:\Include, and a fourth dialog box shows Got property change event for AdditionalIncludeDirectories.

    Note

    You will not get the property change event after the changes in this code have been persisted. To see the event, change the property value to something different.