This article covers the structure of Visual C++ project model (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.
[2024]Digital Global Overview Report 2024 Meltwater.pdf
Visual C++ project model
1. Visual C++ project model
Author: Paul Eremeev
Date: 29.10.2012
Abstract
This article covers the structure of Visual C++ project model (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.
Introduction
Visual C++ 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.
VCProject 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. 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:
VCProject vcproj = proj.Object as VCProject;
2. 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;
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:
VCFile file = projectItem.Object as VCFile;
An embedded project can be obtained in a similar manner:
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
3. 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
//CommonIDLvsshell.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)
4. {
...
}
}
}
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.
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++;
5. //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 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:
6. 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("<MiscFiles>",
StringComparison.InvariantCultureIgnoreCase))
return true;
Solution2 solution = m_DTE.Solution as Solution2;
SolutionBuild2 solutionBuild =
(SolutionBuild2)solution.SolutionBuild;
7. 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)
{
8. 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);
}
}
}
Configurations and properties of projects and files
Visual C++ 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 a 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;
9. 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
10. 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 filed 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
11. 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.
References
1. MSDN. Visual C++ Project Model.
2. MSDN. Project Modeling.
3. MSDN. Automation Model Overview.
Other articles in this series
0. Introduction.
1. Creating, debugging and deploying extension packages for Microsoft Visual Studio
2005/2008/2010/2012.
2. Visual Studio Automation Object Model. EnvDTE interfaces.
3. Visual Studio commands.
4. Visual Studio tool windows.
5. Integrating into Visual Studio settings.
6. Visual C++ project model.