C++ Native Multi-Targeting

Hello, my name is Li Shao. I am a Software Design Engineer in Test on the C++ team. For the past two years, I have been part of the team working on migrating the C++ build system from VCBuild to MSBuild as well as the new project system which is also built on top of MSBuild. You can see one of my earlier blogs about the task work that we have done. In this blog, I am going to give an overview of the Native Multi-targeting feature. I hope you will find it useful.

 

Multi-targeting is the ability to use the current version of Visual Studio to build your application with a different set of installed tools or Frameworks. In VS2010, C++ applications support two types of Multi-targeting: Native Multi-targeting and Managed Multi-targeting. For more details on Managed Multi-targeting, please visit this blog post by one of my colleagues, Pavan Adharapurapu.

Native Multi-targeting

 

VS2010 can support building against VS2008 toolsets if VS2008 is installed on the same machine. Soma has mentioned this feature in his blog some time ago. This feature enables the C++ customers to use VS2008 toolsets while working in the VS2010 environment. Note that your application created with earlier versions of Visual Studio will need to be converted to the VS2010 version.

 

Why Native Multi-targeting?

An important scenario for C++ customers (let’s call them “large C++ shops” here) is that they develop and build their applications in Visual Studio and then ship the applications to their respective customers. These customers, however, maybe using different versions of Visual Studio for development. Given that, the “large C++ shops” have to keep multiple versions of Visual Studio and multiple versions of the project files, so that they can build binaries targeting different versions of the toolset.

 

With the native Multi-targeting feature, once the “large C++ shops” migrated their applications to VS2010, they have the ability to use VS2010 to target any versions of the toolset theoretically. This eliminates the need to maintain multiple versions of project files and the development work can be done all within VS2010, as long as the targeted toolsets are installed on the same machine.

 

How to enable native Multi-targeting?

The Multi-Targeting feature is available both when building from the IDE and building on the command line. Below is a screen shot of the native Multi-targeting settings on the property page: Platform Toolset controls which version of the toolsets you want to target: v100 targets VS2010 and v90 targets the VS2008 level toolsets and libraries.

 

 

The Platform toolset version is v100 by default for both newly created projects and projects converted to VS2010. In both cases, the “PlatformToolset” property is not written to the project file. There are a couple of ways you can re-target to different PlatformToolset (v90 is used as an example below):

 

For single project, you can choose your targeted platform toolset to be v90 in the property page, the “PlatformToolset” property will be written to your project file as the following:

 

<PropertyGroup Condition=‘$(Configuration)|$(Platform)’==’Debug|Win32’ Label=Configuration>

    <ConfigurationType>Application</ConfigurationType>

    <UseDebugLibraries>true</UseDebugLibraries>

    <CharacterSet>Unicode</CharacterSet>

    <PlatformToolset>v90</PlatformToolset>

</PropertyGroup>

 

 

 

In order to re-target multiple projects at once, you can multi-select them in Solution Explorer and then bring up the Property Pages. To make the changes apply to all configurations, also select from the Configuration dropdown “All configurations” entry and from Platform dropdown “All platforms” entry. Note that multi-select will work only if all projects are of the same type.

 

Once the “PlatformToolset” is set to v90, the compiler, linker, headers, and libraries from VS2008 will be used. Below is a snapshot of the build log when building against the v90 toolset from the VS2010 IDE or command line prompt. You can see that VS2008 compiler is used instead of VS2010 compiler.

 

c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\CL.exe /c /ZI /nologo /W3 /WX- /Od /Oy- /D WIN32 /D _WINDOWS /D _DEBUG /D _UNICODE /D UNICODE /D _AFXDLL /Gm /EHsc /RTC1 /MDd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Yc”StdAfx.h” /Fp”Debug\MFC.pch” /Fo”Debug\\” /Fd”Debug\vc90.pdb” /Gd /TP /analyze- /errorReport:prompt stdafx.cpp

 

Note that changing the value of the Platform Toolset property will cause a full rebuild of the project.

 

How does it work?

In VS2010, there are V90 and V100 folders under %ProgramFiles%\MSBuild\Microsoft.Cpp\v4.0\Platforms\<Platforms>\PlatformToolsets or %ProgramFiles(x86)%\MSBuild\Microsoft.Cpp\v4.0\Platforms\<Platforms>\PlatformToolsets on a x64 machine. Inside each folder, you can find a ToolSet specific props and targets file. Here is a representation of the layout of the file/directory structure on the disk:

 

 

 

Microsoft.Cpp.<Platform>.<PlatformToolset>.props file sets the properties required to build against a certain toolset. For example, in Microsoft.Cpp.Win32.v90.Props, ExecutablePath (PATH), IncludePath (INCLUDE), ReferencePath (LIBPATH), LibraryPath (LIB), SourcePath, ExcludedPath are set based on the VS2008 installation and Windows SDK installation that shipped with VS2008 (6.0A)

 

<VCInstallDir>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Setup\VC@ProductDir)</VCInstallDir>

<VCInstallDir

    Condition=‘$(VCInstallDir)’ == ”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0\Setup\VC@ProductDir)</VCInstallDir>

<VCInstallDir

    Condition=‘$(VCInstallDir)’ == ”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VCExpress\9.0\Setup\VC@ProductDir)</VCInstallDir>

<VCInstallDir

    Condition=‘$(VCInstallDir)’ == ”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VCExpress\9.0\Setup\VC@ProductDir)</VCInstallDir>

<WindowsSdkDir

    Condition=‘$(UseEnv)’ != ‘true’>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows@CurrentInstallFolder)</WindowsSdkDir>

<WindowsSdkDir

    Condition=‘$(WindowsSdkDir)’==”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\MicrosoftSDKs\Windows@CurrentInstallFolder)</WindowsSdkDir>

<ExecutablePath

    Condition=‘$(ExecutablePath)’ == ”>$(VCInstallDir)bin;$(WindowsSDKDir)bin;$(VSInstallDir)Common7\Tools\bin;$(VSInstallDir)Common7\tools;$(VSInstallDir)Common7\ide;$(ProgramFiles)\HTML Help Workshop;$(FrameworkSDKDir)bin;$(FrameworkDir)$(FrameworkVersion);$(VSInstallDir);$(SystemRoot)\SysWow64;$(FxCopDir);$(PATH);</ExecutablePath>

<IncludePath

    Condition=‘$(IncludePath)’ == ”>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)include;</IncludePath>

<ReferencePath

    Condition=‘$(ReferencePath)’ == ”>$(VCInstallDir)atlmfc\lib;$(VCInstallDir)lib</ReferencePath>

<LibraryPath

    Condition=‘$(LibraryPath)’ == ”>$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)lib</LibraryPath>

<SourcePath

    Condition=‘$(SourcePath)’ == ”>$(VCInstallDir)atlmfc\src\mfc;$(VCInstallDir)atlmfc\src\mfcm;$(VCInstallDir)atlmfc\src\atl;$(VCInstallDir)crt\src;</SourcePath>

<ExcludePath

    Condition=‘$(ExcludePath)’ == ”>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)include;$(FrameworkDir)$(FrameworkVersion);$(VCInstallDir)atlmfc\lib;$(VCInstallDir)lib;</ExcludePath>

 

MSBuild can spawn the build environment based on the information you set in the props file for a certain platform toolset. In the command line build scenarios, this means that the PATH, LIBPATH, LIB, INCLUDE that the build system is using can come from the PlatformToolsets itself, which can eliminate the need to use other batch files, such as vcvars32.bat, to set up the build environment. This is a functionality that the old VCBuild system does not have. Below is a code snippet from Microsoft.cpp.Targets that enables the functionality. When UseEnv is not set to true, PATH, LIB, INCLUDE, LIBPATH will be set from the corresponding property values in the platform toolset.  When UseEnv is set to true, as in old build system, the values from environment variables for PATH, INCLUDE, LIB, LIBPATH will be used, instead.

 

<Target Name=SetBuildDefaultEnvironmentVariables

          Condition=‘$(UseEnv)’ != ‘true’>

    <SetEnv Condition=‘$(_IsNativeEnvironment)’ != ‘true’

            Name   =PATH

            Value  =$(ExecutablePath)

            Prefix =false>

      <Output TaskParameter=OutputEnvironmentVariable PropertyName=Path/>

    </SetEnv>

    <SetEnv Condition=‘$(_IsNativeEnvironment)’ == ‘true’

            Name =PATH

            Value =$(NativeExecutablePath)

            Prefix =false>

      <Output TaskParameter=OutputEnvironmentVariable PropertyName=Path/>

    </SetEnv>

    <SetEnv Name   =LIB

            Value  =$(LibraryPath)

            Prefix =false>

      <Output TaskParameter=OutputEnvironmentVariable PropertyName=LIB/>

    </SetEnv>

    <SetEnv Name   =LIBPATH

            Value  =$(ReferencePath)

            Prefix =false>

      <Output TaskParameter=OutputEnvironmentVariable PropertyName=LIBPATH/>

    </SetEnv>

    <SetEnv Name   =INCLUDE

            Value  =$(IncludePath)

            Prefix =false >

      <Output TaskParameter=OutputEnvironmentVariable PropertyName=INCLUDE/>

    </SetEnv>

  </Target>

 

 

For managed applications, since the VS2008 compiler can only target CLR 2.0, targeting v90 platform toolset it allows only one of these versions of .NET Framework: v2.0, v3.0 or v3.5. By default, v90 platform toolset target v3.5 framework.

 

How to extend it?

With this model, if you have a specific SDK that you would like to target, you can create your custom platform toolset. Below is an example of a Microsoft.cpp.Win32.v80.props file for targeting VS2005. Note that for VS2005, WindowsSDKDir is not available, we have PlatformSDK instead.  After creating the v80 directory for each platform along with the corresponding props and targets files, you will have support to build against v80 instantly.

 

<Project xmlns=http://schemas.microsoft.com/developer/msbuild/2003>

  <Import Project=$(VCTargetsPath)\Platforms\Win32\ PlatformToolsets\v80\ImportBefore\*.props Condition=Exists(‘$(VCTargetsPath)\Platforms\Win32\PlatformToolsets\v80\ImportBefore’) />

  <PropertyGroup>

    <PlatformToolsetVersion>80</PlatformToolsetVersion>

    <VCInstallDir>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Setup\VC@ProductDir)</VCInstallDir>

    <VCInstallDir

        Condition=‘$(VCInstallDir)’ == ”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\8.0\Setup\VC@ProductDir) </VCInstallDir>

    <VSInstallDir>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Setup\VS@ProductDir)</VSInstallDir>

    <VSInstallDir

        Condition=‘$(VSInstallDir)’ == ”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\8.0\Setup\VS@ProductDir)</VSInstallDir>

    <FrameworkDir

        Condition=‘$(UseEnv)’ != ‘true’>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework@InstallRoot)</FrameworkDir>

    <FrameworkDir

        Condition=‘$(FrameworkDir)’ == ”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework@InstallRoot)</FrameworkDir>

    <FrameworkSdkDir

        Condition=‘$(UseEnv)’ != ‘true’>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\.NETFrameWork\v2.0@InstallationFolder)</FrameworkSdkDir>

    <FrameworkSdkDir

        Condition=‘$(FrameworkSdkDir)’ == ”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\.NETFrameWork\v2.0@InstallationFolder)</FrameworkSdkDir>

    <FrameworkSdkDir

        Condition=‘$(FrameworkSdkDir)’ == ”>$(Registry:HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\.NETFrameWork\v2.0@InstallationFolder)</FrameworkSdkDir>

    <FrameworkVersion

        Condition=‘$(UseEnv)’ != ‘true’>v2.0.50727</FrameworkVersion>

    <Framework35Version

        Condition=‘$(UseEnv)’ != ‘true’>v3.5</Framework35Version>

    <ExecutablePath

        Condition=‘$(ExecutablePath)’ == ”>$(VCInstallDir)bin;$(VCInstallDir)PlatformSDK\bin;$(VCInstallDir)PlatformSDK\common\bin;$(VSInstallDir)Common7\Tools\bin;$(VSInstallDir)Common7\tools;$(VSInstallDir)Common7\ide;$(ProgramFiles)\HTML Help Workshop;$(FrameworkSDKDir)bin;$(FrameworkDir)$(FrameworkVersion);$(VSInstallDir);$(SystemRoot)\SysWow64;$(FxCopDir);$(PATH);</ExecutablePath>

    <IncludePath

        Condition=‘$(IncludePath)’ == ”>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(VCInstallDir)PlatformSDK\include;$(VCInstallDir)PlatformSDK\common\include;$(FrameworkSDKDir)include;</IncludePath>

    <ReferencePath

        Condition=‘$(ReferencePath)’ == ”>$(FrameworkDir)$(FrameworkVersion);$(VCInstallDir)atlmfc\lib;</ReferencePath>

    <LibraryPath

        Condition=‘$(LibraryPath)’ == ”>$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(VCInstallDir)atlmfc\lib\i386;$(VCInstallDir)PlatformSDK\lib;$(VCInstallDir)PlatformSDK\common\lib;$(FrameworkSDKDir)lib;$(VSInstallDir);$(VSInstallDir)lib;</LibraryPath>

    <SourcePath

        Condition=‘$(SourcePath)’ == ”>$(VCInstallDir)atlmfc\src\mfc;$(VCInstallDir)atlmfc\src\atl;$(VCInstallDir)crt\src;</SourcePath>

    <ExcludePath

        Condition=‘$(ExcludePath)’ == ”>$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(VCInstallDir)PlatformSDK\include;$(VCInstallDir)PlatformSDK\common\include;$(FrameworkSDKDir)include;$(FrameworkDir)$(FrameworkVersion);$(VCInstallDir)atlmfc\lib;</ExcludePath>

    <NativeExecutablePath

        Condition=‘$(NativeExecutablePath)’ == ”>$(ExecutablePath)</NativeExecutablePath>

  </PropertyGroup>

  <Import Project=$(VCTargetsPath)\Platforms\Win32\ PlatformToolsets\v80\ImportAfter\*..props Condition=Exists(‘$(VCTargetsPath)\Platforms\Win32\ PlatformToolsets\v80\ImportAfter’) />

</Project>

 

 

Below is a snapshot of the IDE build setting and the build log:

 

 

The Platform Toolset feature also provides the extensibility points. In the Microsoft.Cpp.Win32.<PlatformToolset version>.props file, there are the following sections:

 

<Import Project=$(VCTargetsPath)\Platforms\Win32\PlatformToolsets\v100\ImportBefore\*.props

        Condition=Exists(‘$(VCTargetsPath)\Platforms\Win32\PlatformToolsets\v100\ImportBefore’) />

 

<Import Project=$(VCTargetsPath)\Platforms\Win32\PlatformToolsets\v100\ImportAfter\*.props

        Condition=Exists(‘$(VCTargetsPath)\Platforms\Win32\PlatformToolsets\v100\ImportAfter’) />

 

With this design, you can create your own special props file that is specific to a certain Platform Toolset. For example, you might create a custom props file called DirectX.props (it could be called anything) which you then drop into the following folder: win32\PlatformToolsets\v100\ImportAfter. The Properties defined in DirectX.props (below) would be imported after the properties in Microsoft.Cpp.Win32.<PlatformToolset version>.props are imported.

 

<Project xmlns=http://schemas.microsoft.com/developer/msbuild/2003>

  <ItemDefinitionGroup>

    <ClCompile>

<AdditionalIncludeDirectories>$(DirectXSDK)\Latest\Include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

    </ClCompile>

    <Link>

<AdditionalLibraryDirectories>$(DirectXSDK)\Latest\Lib\x86%(AdditionalLibraryDirectories);</AdditionalLibraryDirectories>

    </Link>

  </ItemDefinitionGroup>

</Project>

 

Upon compile, you will get /I d:\DirectX\Latest\include being passed to CL.exe and /LIBPATH:d:\DirectX\Latest\Lib\x86 being passed to the linker when compiling a project targeting the Win32 platform and the v100 platform toolset. Any other project configurations will not be impacted.

 

Windows SDK will adopt this Platform Toolset concept for future releases so that re-targeting of different WinSDK versions becomes an easier task. We also encourage our customers to use this feature and let us know if you have any feedback.

 

Li Shao

Project and Build Team