The Structure of the Visual Studio Project Model

DZone 's Guide to

The Structure of the Visual Studio Project Model

· ·
Free Resource
About a year ago in our blog a series, we published several articles on the development of Visual Studio plugins in C#. We have recently revised those materials and added new sections and now invite you to have a look at the updated version of the manual as a series of articles here on DZone.

The other articles in the series can be found here:

This article (the sixth and final one in the series) covers the structure of the 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.


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:

  |- Project -- Object(unique for the project type)
      |- ProjectItems (a collection of ProjectItem)
          |- ProjectItem (single object) -- ProjectItems (another
              |- Object(unique for the project type)

The “Project’s” 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
  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
  IVsHierarchy hierarchy;
  solution.GetProjectOfUniqueName(project.FullName, out hierarchy);
  if (hierarchy != null)
    Guid projectGuid;

      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(
    ref vsppProjectGuid, out penum);
  if ((VSConstants.S_OK == hr) && (penum != null))
    uint fetched;
    IVsHierarchy[] rgelt = new IVsHierarchy[1];
    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 the Android platform. As a result, this project model extension had caused crashes in our plug-in because it did not provide several properties that 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 the 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)
  int hr; object pVar;

  hr = hierarchy.GetProperty(itemid, 
    (int)__VSHPROPID.VSHPROPID_ExtObject, out pVar);

  ProjectItem projectItem = pVar as ProjectItem;
  if (projectItem != null)

  //Get the first child node of the current hierarchy being walked
  hr = hierarchy.GetProperty(itemid,
    (visibleNodesOnly ? (int)__VSHPROPID.VSHPROPID_FirstVisibleChild 
    out pVar);
  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, 
        hr = hierarchy.GetProperty(childId,
          (visibleNodesOnly ?
          (int)__VSHPROPID.VSHPROPID_NextVisibleSibling :
          out pVar);
        if (VSConstants.S_OK == hr)
          childId = GetItemId(pVar);

  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

The DTE.Solution.Projects interface can be used to enumerate all projects in the solution:

if (m_DTE.Solution.Projects != null)
      foreach (object prj in m_DTE.Solution.Projects)
        EnvDTE.Project proj = prj as EnvDTE.Project;
        if (proj != null)

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);
      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))

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("<MiscFiles>", 
  return true;
  Solution2 solution = m_DTE.Solution as Solution2;
  SolutionBuild2 solutionBuild = 
    SolutionContexts projectContexts = 
    //Skip this  project if it is excluded from build.
    bool shouldbuild = 
    return !shouldbuild;

Enumerating selected elements

The DTE.SelectedItems interface can be used to enumerate solution elements that 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);
        projectHierarchy, 0, false, files, showProgressDialog);
      else if (item.Project.ProjectItems != null)
        //solution folder
        if (!ProjectUnloaded(item.Project))
    else if (item.ProjectItem != null)
      //walking files
      else if (item.ProjectItem.ProjectItems != null)
      if (item.ProjectItem.ProjectItems.Count > 0)
private void WalkProjectItemTree(object CurrentItem)
  Project CurProject = null;
  CurProject = CurrentItem as Project;
  if (CurProject != null)
    IVsHierarchy projectHierarchy = ToHierarchy(CurProject);
      projectHierarchy, 0, false);

    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)

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 a project's build configuration (Debug, Release, etc.) and a platform (Win32, IA64, x64, etc.) is associated with a separate collection of settings. Although the 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 that 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 that 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 
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 the 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, the 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 filed on 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 = 

  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 that 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 that 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 that 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 that 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, the 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 two types of projects: C and C++ projects. Please note that theses project types possess different GUIDs, so this should be taken into account when traversing the project tree.

Atmel project model also provides two compile toolchains – 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 toolsets 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<String> 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<String> SystemIncludes = options. DefaultIncludePaths;

But most of other settings are available only through the OtherProperties property of the Dictionary<String, IList<String>> 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<string, string> MSBuildProps = new Dictionary<string, string>();
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;


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

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}