To better understand how C++/WinRT work from the perspective of a build system, we can look at how it is handled in Visual Studio. The officially recommend method of using C++/WinRT is through Visual Studio and the official NuGet package. The Windows SDK also supplies the C++/WinRT tooling, but it is generally quite outdated and not recommended to be used.
⚠️ Warning: The information here is work in progress and may be inaccurate.
The Windows SDK has shipped C++/WinRT headers for system APIs and the cppwinrt tool for quite a while (likely starting from version 10.0.17134.0), though in more recent versions of the SDK these are present only for compatibility reasons. The version of C++/WinRT included in the Windows SDK will be quite outdated, therefore using it is usually a bad idea.
In the SDK, a copy of the generated headers is provided inside <WindowsSdkDir>\Include\<WindowsTargetPlatformVersion>\cppwinrt
. The cppwinrt tool is inside the corresponding bin dir, i.e. <WindowsSdkDir>\bin\<WindowsTargetPlatformVersion>\<arch>\cppwinrt.exe
.
To make this work using Visual Studio / MSBuild, the header include dir is specified in <WindowsSdkDir>\DesignTime\CommonConfiguration\Neutral\UAP\<WindowsTargetPlatformVersion>\UAP.props
as the MSBuild property CppWinRT_IncludePath
, but this is only set if the property has not been set already (which allows the C++/WinRT NuGet package to override it). This property is included as part of the WindowsSDK_IncludePath
property, also specified in the same file.
Unlike MSBuild, the Visual Studio command prompt hardcodes the include paths to be set to the INCLUDE
and EXTERNAL_INCLUDE
environment variables to match the UAP.props
defaults, so <WindowsSdkDir>\Include\<WindowsTargetPlatformVersion>\cppwinrt
is always in the include path along with the rest of the include dirs.
This is done in Common7\Tools\vsdevcmd\core\winsdk.bat
1 by adding the include paths to __VSCMD_WINSDK_INCLUDE
. This script is called from Common7\Tools\vsdevcmd.bat
1, which then combines the include paths from multiple sources including the winsdk one to form the INCLUDE
and EXTERNAL_INCLUDE
environment variables. There is no way to override this behaviour.
In order to use a custom set of C++/WinRT headers one must add the include path to the cl.exe
command line or prepend the corresponding include path to these environment variables after calling vcvarsall.bat
. Optionally one may also want to remove the SDK cppwinrt include path from the environment variables to avoid confusion (a script can try to look for %WindowsSdkDir%\include\%WindowsSDKVersion%\cppwinrt
but this may not be reliable).
The official NuGet package contains the following:
- The cppwinrt tool (an x86 binary executable)
- MSBuild targets and props to add automatic C++/WinRT integration to the build
- A property page schema for C++/WinRT properties
- Static libraries of
cppwinrt_fast_forwarder.lib
Microsoft.Windows.CppWinRT.props
sets the property CppWinRT_IncludePath
to PreventSdkUapPropsAssignment
-- this is done to prevent the aforementioned UAP.props
from adding the SDK cppwinrt headers to the include path, so that these headers will not be accidentally used. (UAP.props
) only assign CppWinRT_IncludePath
if it has not been set already.)
Microsoft.Windows.CppWinRT.targets
adds various targets for build integration. One of the targets, CppWinRTMakePlatformProjection
, runs the cppwinrt tool before the compiling steps to generate headers for the system APIs into the generated files dir using the winmd files in the Windows SDK. This file also reassign CppWinRT_IncludePath
as the generated files dir but it does not have any effect on the build. The generated files dir is really used through being added to AdditionalIncludeDirectories
.
The headers for referenced components and authored components are generated similarly. There are some fancy build targets which looks for the winmd files of the referenced components to invoke cppwinrt with. Exported components take more steps -- the winmd file has to be generated from IDL files inside the project using the midl tool.
The build target writes a response file containing the command line to use, then calls the cppwinrt tool with it. Presumably this avoids issues with command line length and escaping.
A list of platform winmd files is obtained with the glob <WindowsSdkDir>\References\<WindowsTargetPlatformVersion>\**\*.winmd
(which can be overridden with properties). The Windows SDK also includes a single winmd file <WindowsSdkDir>\UnionMetadata\<WindowsTargetPlatformVersion>\Windows.winmd
containing the API metadata of all the system WinRT APIs combined, but this is not used by the C++/WinRT NuGet package.
The platform API headers are generated by the following command (lines starting with #
are comments), using a response file named by $(MSBuildProjectFile).cppwinrt_plat.rsp
:
cppwinrt.exe @xxx.cppwinrt_plat.rsp
# The following is written to xxx.cppwinrt_plat.rsp:
-in <platform_winmd_file>
# Above is repeated for every platform winmd files.
-out <generated_files_dir>
The projection headers for referenced components are generated by the following command, using a response file named by $(MSBuildProjectFile).cppwinrt_ref.rsp
:
cppwinrt.exe @xxx.cppwinrt_ref.rsp
# The following is written to xxx.cppwinrt_ref.rsp:
-in <reference_winmd_file>
# Above is repeated for every referenced components.
-ref <platform_winmd_file>
# Above is repeated for every platform winmd files.
-out <generated_files_dir>
The component headers, generated sources and component template code for authored components are generated by the following command, using a response file named by $(MSBuildProjectFile).cppwinrt_comp.rsp
:
cppwinrt.exe @xxx.cppwinrt_comp.rsp
# The following is written to xxx.cppwinrt_comp.rsp:
-overwrite
-name <project_name>
-pch pch.h
# The previous line only included if the project uses precompiled header.
-prefix
-comp "<generated_files_dir>\sources"
# -comp is short for -component:
# "Generate component templates, and optional implementation"
-opt
# -opt is short for -optimize:
# "Generate component projection with unified construction support"
-in <component_winmd_file>
# Above is repeated for the component created in the project and for every
# components from static library dependencies.
-ref <platform_winmd_file>
# Above is repeated for every platform winmd files.
-out <generated_files_dir>
To supply a WinRT component for use, the winmd file of the component also needs to be published. Authoring a C++/WinRT component requires you to craft the IDL file (in MIDL 3.0 syntax) of the component, which is picked up by the C++/WinRT build integration, which adds targets to run the midlrt tool to generate the winmd file. The cppwinrt tool is then used to generate headers, sources and template code, as mentioned in the previous section. The template code is intended for the developer to copy back to the project source code to be used as a starting point. The generated sources contains the glue code which binds the exported WinRT COM objects with the C++/WinRT-based classes implemented by the developer.
This extension provides project templates and NatVis debug visualizer for Visual Studio 2019. In Visual Studio 2022, these are already integrated in the installation out of the box.