C++/WinRT 中的新增功能

随着 C++/WinRT 的后续版本发布,本主题介绍新增功能和更改内容。

截至 2020 年 3 月的最新改进/新增内容汇总

构建时间最多可缩短 23%

C++/WinRT 和 C++ 编译器团队已展开协作,尽一切可能缩短生成时间。 我们已深入分析编译器分析,以了解 C++/WinRT 的内部结构,以帮助 C++ 编译器消除编译时开销,以及如何改进 C++ 编译器本身来处理 C++/WinRT 库。 C++/WinRT 已针对编译器进行了优化;并且编译器已针对 C++/WinRT 进行了优化。

让我们以这样一种最坏情况为例:构建一个包含所有 C++/WinRT 投影命名空间头文件的预编译头(PCH)。

版本 PCH 大小(字节) 时间 (s)
7 月起支持 Visual C++ 16.3 的 C++/WinRT 3,004,104,632 31
C++/WinRT 2.0.200316.3 版本,配合 Visual C++ 16.5 2,393,515,336 24

大小减少 20%,生成时间减少 23%。

改进了 MSBuild 支持

我们投入了大量工作来改进 MSBuild 对大量不同方案的支持。

更快速的工厂缓存

我们改进了工厂缓存内联,以便更好地内联热路径,从而提升执行速度。

这种改进不会影响代码大小,如 Optimized EH 代码代中所述,如果应用程序使用 C++ 异常处理非常严重,则可以使用/d2FH4选项来收缩二进制文件,该选项在使用 Visual Studio 2019 16.3 和更高版本创建的新项目中默认处于打开状态。

更高效的装箱

在 XAML 应用程序中使用时,winrt::box_value 现在效率更高(请参阅 装箱和拆箱)。 执行大量装箱的应用程序也会发现代码体积有所减小。

支持实现 IInspectable 的 COM 接口

如果需要实现刚好实现 IInspectable 的 COM 接口(非Windows-Runtime),则现在可以使用 C++/WinRT 执行此操作。 请参阅 实现 IInspectable 的 COM 接口

模块锁定改进

控制模块锁定现在允许自定义托管方案和完全消除模块级锁定。 请参阅 模块锁定改进

支持非 Windows Runtime 错误信息的处理

某些 API(甚至某些 Windows 运行时 API)在未使用 Windows 运行时 错误源 API 的情况下报告错误。 在这种情况下,C++/WinRT 现在改为使用 COM 错误信息。 请参阅 C++/WinRT 对非 WinRT 错误信息的支持

启用 C++ 模块支持

C++ 模块支持已恢复,但仅以实验形式提供。 该功能尚未在 C++ 编译器中完成。

更高效的协同例程恢复

C++/WinRT 协程本身已经具备良好的性能,但我们仍在继续寻找进一步提升其性能的方法。 请参阅 改进协同例程恢复的可伸缩性

新的 when_allwhen_any 异步辅助函数

when_all辅助函数创建一个IAsyncAction对象,该对象会在所有提供的可等待对象均已完成后完成。 when_any辅助函数会创建一个IAsyncAction,该操作会在所提供的任一可等待对象完成时完成。

请参阅 添加 when_any 异步帮助程序添加 when_all 异步帮助程序

其他优化和添加

此外,引入了许多 bug 修复和次要优化和新增功能,包括简化调试和优化内部和默认实现的各种改进。 请遵循此链接获取详尽列表: https://github.com/microsoft/xlang/pulls?q=is%3Apr+is%3Aclosed

C++/WinRT 2.0 中的新闻和更改

有关 C++/WinRT Visual Studio 扩展 (VSIX)的详细信息,Microsoft.Windows。CppWinRT NuGet 包以及cppwinrt.exe工具(包括如何获取和安装它们)请参阅对 C++/WinRT、XAML、VSIX 扩展和 NuGet 包的Visual Studio支持

对版本 2.0 的 C++/WinRT Visual Studio 扩展(VSIX)的更改

  • 调试可视化工具现在支持 Visual Studio 2019;并继续支持 Visual Studio 2017。
  • 进行了许多 bug 修复。

Microsoft.Windows.CppWinRT NuGet 包 2.0 版的更改

  • cppwinrt.exe 工具现已包含在 Microsoft.Windows.CppWinRT NuGet 包中,并且该工具会按需为每个项目生成平台投影头文件。 因此,该工具cppwinrt.exe不再依赖于Windows SDK(尽管该工具仍附带 SDK,但出于兼容性原因)。
  • cppwinrt.exe 现在会在每个特定于平台/配置的中间目录($IntDir)下生成投影头文件,以支持并行生成。
  • C++/WinRT 构建支持(props/targets)现已提供完整的文档说明,方便你在需要时手动自定义项目文件。 请参阅 Microsoft.Windows.CppWinRT NuGet 包的自述文件
  • 进行了许多 bug 修复。

对版本 2.0 的 C++/WinRT 的更改

开源

cppwinrt.exe工具接受一个 Windows 运行时 元数据(.winmd)文件作为输入,并据此生成一个基于头文件的标准 C++ 库,将元数据中描述的 API 投影出来。 这样一来,你就可以在你的 C++/WinRT 代码中调用这些 API。

此工具现在是一个完全开放源代码的项目,可在GitHub上使用。 访问Microsoft/cppwinrt

xlang 库

完全可移植的仅标头库(用于分析Windows 运行时使用的 ECMA-335 元数据格式)构成了今后所有Windows 运行时和 xlang 工具的基础。 值得注意的是,我们还使用 xlang 库从头重新改写 cppwinrt.exe 该工具。 这提供了更准确的元数据查询,解决了 C++/WinRT 语言投影的一些长期问题。

依赖项更少

由于 xlang 元数据读取器, cppwinrt.exe 工具本身的依赖项较少。 这使得它更加灵活,并在更多方案中可用,尤其是在受约束的生成环境中。 值得注意的是,它不再依赖 RoMetadata.dll。   这些是 2.0 的 cppwinrt.exe 依赖项。  

  • ADVAPI32.dll
  • KERNEL32.dll
  • SHLWAPI.dll
  • XmlLite.dll

所有这些 DLL 不仅在Windows 10上可用,而且一直到Windows 7,甚至Windows Vista。 如果需要,运行Windows 7的旧生成服务器现在可以运行cppwinrt.exe,为项目生成 C++ 标头。 稍加处理一下,如果你对此感兴趣,甚至可以在 Windows 7 上运行 C++/WinRT

将上面的列表与 1.0 具有的这些依赖项 cppwinrt.exe 进行对比。

  • ADVAPI32.dll
  • SHELL32.dll
  • api-ms-win-core-file-l1-1-0.dll
  • XmlLite.dll
  • api-ms-win-core-libraryloader-l1-2-0.dll
  • api-ms-win-core-processenvironment-l1-1-0.dll
  • RoMetadata.dll
  • SHLWAPI.dll
  • KERNEL32.dll
  • api-ms-win-core-rtlsupport-l1-1-0.dll
  • api-ms-win-core-heap-l1-1-0.dll
  • api-ms-win-core-timezone-l1-1-0.dll
  • api-ms-win-core-console-l1-1-0.dll
  • api-ms-win-core-localization-l1-2-0.dll
  • OLEAUT32.dll
  • api-ms-win-core-winrt-error-l1-1-0.dll
  • api-ms-win-core-winrt-error-l1-1-1.dll
  • api-ms-win-core-winrt-l1-1-0.dll
  • api-ms-win-core-winrt-string-l1-1-0.dll
  • api-ms-win-core-synch-l1-1-0.dll
  • api-ms-win-core-threadpool-l1-2-0.dll
  • api-ms-win-core-com-l1-1-0.dll
  • api-ms-win-core-com-l1-1-1.dll
  • api-ms-win-core-synch-l1-2-0.dll

Windows 运行时 noexcept 特性

Windows 运行时有一个新[noexcept]属性,可用于修饰 MIDL 3.0 中的方法和属性。 该属性的存在会向支持该属性的工具表明,你的实现不会引发异常(也不会返回失败的 HRESULT)。 这使语言投影能够通过避免为支持可能失败的应用程序二进制接口(ABI)调用所需的异常处理开销来优化代码生成。

C++/WinRT 通过生成使用代码和创作代码的 C++ noexcept 实现来利用这一点。 如果你有无故障的 API 方法或属性,并且你担心代码大小,则可以调查此属性。

优化的代码生成

C++/WinRT 现在生成更高效的 C++ 源代码(幕后),以便 C++ 编译器可以生成尽可能小且最有效的二进制代码。 许多改进旨在通过避免不必要的展开信息来降低异常处理成本。 使用大量 C++/WinRT 代码的二进制文件将大致减少 4% 代码大小。 由于指令计数减少,代码效率也更高(运行速度更快)。

这些改进也依赖于可供你使用的新互操作功能。 所有属于资源所有者的 C++/WinRT 类型现在都包含用于直接获取所有权的构造函数,从而避免了前面的两步方法。

ABI::Windows::Foundation::IStringable* raw = ...

IStringable projected(raw, take_ownership_from_abi);

printf("%ls\n", projected.ToString().c_str());

优化的异常处理(EH)代码生成

此更改补充了 Microsoft C++ 优化器团队完成的工作,以减少异常处理的成本。 如果在代码中大量使用应用程序二进制接口(如 COM),则会看到许多遵循此模式的代码。

int32_t Function() noexcept
{
    try
    {
        // code here constitutes unique value.
    }
    catch (...)
    {
        // code here is always duplicated.
    }
}

C++/WinRT 本身为每个实现的 API 生成此模式。 使用数千个 API 函数,此处的任何优化都可能很重要。 过去,优化器无法识别这些 catch 代码块其实都完全相同,因此它会在每个 ABI 周边重复生成大量代码(这也进一步让人们认为,在系统代码中使用异常会产生体积庞大的二进制文件)。 但是,从 Visual Studio 2019 开始,C++ 编译器会合并所有这些 catch funclet,并且只保留其中唯一的那些。 结果是,对于高度依赖这种模式的二进制文件,其代码大小总体上进一步减少了 18%。 EH 代码现在比使用返回代码更有效,而且对较大的二进制文件的关注现在也是过去的事情。

增量构建改进

该工具 cppwinrt.exe 现在将生成的标头/源文件的输出与磁盘上任何现有文件的内容进行比较,并且仅当文件已发生更改时才会写出该文件。 这可以节省大量磁盘 I/O 的时间,并确保 C++ 编译器不会将文件视为“脏”。 结果是,在许多情况下,可以避免或减少重新编译。

泛型接口现已全部生成

由于 xlang 元数据读取器,C++/WinRT 现在从元数据生成所有参数化或泛型接口。 Windows::Foundation::Collections::IVector<T> 等接口现在从元数据生成,而不是手动写入winrt/base.h。 结果是,winrt/base.h 的大小减少了一半,并且优化会直接生成到代码中(而这一点用手写方式实现起来很棘手)。

Important

给定的示例等接口现在出现在各自的命名空间标头中,而不是出现在 winrt/base.h其中。 因此,如果尚未这样做,则必须包含适当的命名空间标头才能使用该接口。

组件优化

此更新新增了对 C++/WinRT 的另外几项需显式启用的优化的支持,具体见下文各节。 由于这些优化属于破坏性变更(你可能需要做一些小的修改才能适配),因此需要显式启用这些优化。 在Visual Studio中,将项目属性 Common Properties>C++/WinRT>Optimized 设置为 Yes。 这样会将 <CppWinRTOptimized>true</CppWinRTOptimized> 添加到你的项目文件中。 在从命令行调用-opt[imize]时,它与添加cppwinrt.exe开关的效果相同。

根据项目模板创建的新项目将默认使用 -opt

统一的构造方式,以及对实现的直接访问

这两项优化使组件能够直接访问其自身的实现类型,即使它只使用投影类型。 如果只想使用公共 API 图面,则无需使用 makemake_self,也无需 get_self 。 你的调用会被编译成对实现的直接调用,这些调用甚至可能会被完全内联。

有关更多信息和代码示例,请参阅 选择采用统一构造方式和直接访问实现

类型擦除工厂

此优化可避免 #include 依赖项 module.g.cpp ,因此每次发生任何单个实现类发生更改时都不需要重新编译它。 其结果是提升了构建性能。

面向包含多个库的大型项目,更智能、更高效module.g.cpp

该文件 module.g.cpp 现在还包含另外两个可组合的帮助程序,名为 winrt_can_unload_nowwinrt_get_activation_factory。 这些设计用于大型项目,其中 DLL 由多个库组成,每个库都有自己的运行时类。 在这种情况下,需要手动将 DLL 的 DllGetActivationFactoryDllCanUnloadNow 拼凑在一起。 这些辅助工具通过避免无谓的来源错误,让你更容易做到这一点。 cppwinrt.exe该工具的-lib标志还可用于为每个库指定其各自的前导码(而不是winrt_xxx),这样便可分别命名每个库中的函数,从而无歧义地将它们组合在一起。

协同程序支持

默认包含协程支持。 以前,支持驻留在多个地方,我们认为这种支持太有限了。 然后,在 v2.0 中曾暂时需要一个 winrt/coroutine.h 头文件,但现在已经不再需要了。 由于Windows 运行时异步接口现已生成,而不是手动写入,因此它们现在驻留在winrt/Windows.Foundation.h内。 除了更易于维护和支持之外,还意味着 resume_foreground 等协同例程帮助程序不必再被附加到特定命名空间标头的末尾。 相反,它们可以更自然地包括其依赖项。 这进一步使 resume_foreground 不仅支持在给定的 Windows::UI::Core::CoreDispatcher 上继续执行,还支持在给定的 Windows::System::DispatcherQueue 上继续执行。 此前,只能支持其中一个,而不能同时支持两者,因为该定义只能位于一个命名空间中。

下面是 DispatcherQueue 支持的示例。

...
#include <winrt/Windows.System.h>
using namespace Windows::System;
...
fire_and_forget Async(DispatcherQueueController controller)
{
    bool queued = co_await resume_foreground(controller.DispatcherQueue());
    assert(queued);

    // This is just to simulate queue failure...
    co_await controller.ShutdownQueueAsync();

    queued = co_await resume_foreground(controller.DispatcherQueue());
    assert(!queued);
}

这些协程辅助程序现在也使用 [[nodiscard]] 进行了修饰,从而提高了其可用性。 如果你忘记了(或者没有意识到你必须这样做)co_await 才能使其生效,那么由于 [[nodiscard]],这类错误现在会触发编译器警告。

关于诊断直接(栈)分配的帮助

由于投影类和实现类的名称(默认情况下)相同,并且仅命名空间不同,因此可能会将两者混淆,并意外地在栈上创建实现类实例,而不是使用 make 系列辅助函数。 在某些情况下,这可能很难排查,因为对象可能会在尚未完成的引用操作仍在进行时就被销毁。 现在,在调试版本中,断言会捕捉到这一点。 虽然该断言无法检测协程内部的栈分配,但它仍有助于发现大多数这类错误。

有关详细信息,请参阅 诊断直接分配

改进了捕获帮助器和可变委托

此更新还通过支持投影类型修复了捕获帮助程序的限制。 在 Windows 运行时 互操作 API 中,当它们返回投影类型时,这种情况时不时会出现。

此更新还增加了在创建可变参数(非 Windows 运行时)委托时对 get_strongget_weak 的支持。

支持延迟销毁以及在销毁过程中安全地进行 QI

在运行时类对象的析构函数中,调用会暂时增加引用计数的方法并不少见。 当引用计数返回到零时,对象将再次析构。 在 XAML 应用程序中,可能需要在析构函数中进行 QueryInterface(QI),以便调用层次结构中上层或下层的某些清理实现。 但对象的引用计数已达到零,因此 QI 也构成引用计数弹跳。

此次更新增加了对引用计数进行防抖处理的支持,确保一旦引用计数降为零,对象就绝不会被重新激活;同时仍允许你在销毁过程中对所需的任何临时对象执行 QI。 此过程在某些 XAML 应用程序/控件中是不可避免的,C++/WinRT 现在具有复原能力。

可以通过在实现类型上提供静态 final_release 函数来延迟销毁。 指向对象的最后一个剩余指针以 std::unique_ptr 的形式传递给 final_release。 然后,可以选择将该指针的所有权移动到其他一些上下文。 你可以安全地对该指针执行 QI,而不会触发重复析构。 但在析构对象时,对引用计数的净更改必须为零。

final_release的返回值可以是void异步操作对象,如 IAsyncActionwinrt::fire_and_forget

struct Sample : implements<Sample, IStringable>
{
    hstring ToString()
    {
        return L"Sample";
    }

    ~Sample()
    {
        // Called when the unique_ptr below is reset.
    }

    static void final_release(std::unique_ptr<Sample> self) noexcept
    {
        // Move 'self' as needed to delay destruction.
    }
};

在下面的示例中,在 MainPage 发布(最后一次)后,将调用 final_release 。 该函数在线程池中等待五秒钟,然后在页面的 Dispatcher 上恢复执行(这需要通过 QI/AddRef/Release 才能正常工作)。 然后,它会清理该 UI 线程上的资源。 最后,它会将 unique_ptr 清空,从而真正调用 MainPage 的析构函数。 即使在该析构函数中,也会调用 DataContext ,这需要 IFrameworkElement 的 QI。

无需将 final_release 实现为协程。 但是,这确实起作用,它使得将销毁移动到另一个线程非常简单,这就是此示例中发生的事情。

struct MainPage : PageT<MainPage>
{
    MainPage()
    {
    }

    ~MainPage()
    {
        DataContext(nullptr);
    }

    static IAsyncAction final_release(std::unique_ptr<MainPage> self)
    {
        co_await 5s;

        co_await resume_foreground(self->Dispatcher());
        co_await self->resource.CloseAsync();

        // The object is destructed normally at the end of final_release,
        // when the std::unique_ptr<MyClass> destructs. If you want to destruct
        // the object earlier than that, then you can set *self* to `nullptr`.
        self = nullptr;
    }
};

有关详细信息,请参阅 延迟销毁

改进了对 COM 样式单接口继承的支持

除了Windows 运行时编程,C++/WinRT 还用于创作和使用仅限 COM 的 API。 通过此更新,可以实现存在接口层次结构的 COM 服务器。 Windows 运行时不需要此操作,但某些 COM 实现需要这样做。

正确处理 out 参数

处理out参数可能很棘手,尤其是 Windows 运行时 数组。 通过此次更新,C++/WinRT 在处理 out params 和数组时变得更加稳健,也更能容忍错误;无论这些参数是通过语言投影传入的,还是来自使用原始 ABI 的 COM 开发人员——而这些开发人员会因未始终一致地初始化变量而出错。 无论哪种情况,C++/WinRT 现在都能在将投影类型传递给 ABI 时做正确的处理(会记得释放相关资源),并且在对通过 ABI 传入的参数进行置零或清空时也能正确处理。

事件现在可靠地处理无效令牌

winrt::event 实现现在正常处理其删除方法使用无效令牌值(数组中不存在的值)调用的情况。

协同例程本地变量现在在协同例程返回之前被销毁

实现协程类型的传统方式可能会导致协程中的局部变量在协程返回/完成才被销毁(而不是在最终挂起之前)。 为避免此问题并带来其他益处,任何等待者的恢复现均推迟到最终挂起时才进行。

Windows SDK 版本 10.0.17763.0 中的新闻和更改(Windows 10版本 1809)

下表包含 Windows SDK 版本 10.0.17763.0(Windows 10 版本 1809)中 C++/WinRT 的新闻和更改。

新增或更改的功能 详细信息
破坏性变更。 为了进行编译,C++/WinRT 不依赖于 Windows SDK 中的标头。 请参阅下文的与 Windows SDK 头文件隔离
Visual Studio项目系统格式已更改。 请参阅下面的如何将 C++/WinRT 项目重定向到更高版本的 Windows SDK
有新的函数和基类可帮助你将集合对象传递给Windows 运行时函数,或实现自己的集合属性和集合类型。 请参阅 使用 C++/WinRT 的集合
可以将 {Binding} 标记扩展用于 C++/WinRT 运行时类。 有关详细信息和代码示例,请参阅 数据绑定概述
对取消协程的支持允许您注册取消回调。 有关详细信息和代码示例,请参阅 “取消异步操作”和“取消回调”。
创建指向成员函数的委托时,可以在注册处理程序时将对当前对象的引用设为强引用或弱引用(而不是使用裸 this 指针)。 有关详细信息和代码示例,请参阅“ 如果使用成员函数作为委托 子部分,请参阅”安全地 使用事件处理委托访问 指针“部分中的成员函数。
修复了因 Visual Studio 对 C++ 标准的符合性提高而暴露出的错误。 LLVM 和 Clang 工具链还更好地用于验证 C++/WinRT 的标准一致性。 你将不再遇到 为什么我的新项目无法编译?我使用的是 Visual Studio 2017(版本 15.8.0 或更高版本),以及 SDK 版本 17134 中所述的问题

其他更改。

  • 破坏性变更 winrt::get_abi(winrt::hstring const>) 现在返回 void* 而不是 HSTRING。 你可以使用 static_cast<HSTRING>(get_abi(my_hstring)); 来获取 HSTRING。 请参阅 与 ABI 的 HSTRING 进行互操作
  • 破坏性变更 winrt::put_abi(winrt::hstring&) 现在返回 void** 而不是 HSTRING*。 你可以使用 reinterpret_cast<HSTRING*>(put_abi(my_hstring)); 获取 HSTRING*。 请参阅 与 ABI 的 HSTRING 进行互操作
  • 破坏性变更。 HRESULT 现在映射为 winrt::hresult。 如果需要 HRESULT(执行类型检查或支持类型特征),则可以 static_cast 使用 winrt::hresult。 否则,只要在包含任何 C++/WinRT 标头之前包含 unknwn.hwinrt::hresult 就可转换为 HRESULT。
  • 破坏性变更。 GUID 现在投影为 winrt::guid。 对于实现的 API,必须为 GUID 参数使用 winrt::guid 。 否则,只要在包含任何 C++/WinRT 标头之前包含 unknwn.hwinrt::guid 就可转换为 GUID。 请参阅 与 ABI 的 GUID 结构进行互操作
  • 破坏性变更 winrt::handle_type 构造函数通过显式进行强化(现在很难编写不正确的代码)。 如果需要分配原始句柄值,请改为调用 handle_type::attach 函数
  • 破坏性变更WINRT_CanUnloadNowWINRT_GetActivationFactory的签名已更改。 根本不必须声明这些函数。 相反,应包含 winrt/base.h(如果你包含了任何 C++/WinRT Windows 命名空间头文件,则会自动包含它),以引入这些函数的声明。
  • 对于 winrt::clock 结构from_FILETIME/to_FILETIME 已弃用,建议改用 from_file_time/to_file_time
  • 接受 IBuffer 参数的简化 API。 大多数 API 更喜欢集合或数组。 但我们认为,我们应该更轻松地调用依赖于 IBuffer 的 API。 此更新提供对 IBuffer 实现背后的数据的直接访问。 它使用的数据命名约定与 C++ 标准库容器所使用的命名约定相同。 该约定还避免了与通常以大写字母开头的元数据名称冲突。
  • 改进了代码生成:通过各种改进来减小代码大小、改进内联和优化工厂缓存。
  • 删除了不必要的递归。 当命令行指向某个文件夹而不是特定的 .winmd 时,cppwinrt.exe 工具不再递归搜索 .winmd 文件。 cppwinrt.exe 工具现在也能够更智能地处理重复项,从而对用户错误和格式不正确的 .winmd 文件具有更强的容错能力。
  • 强化的智能指针。 此前,事件撤销器在通过移动赋值接收新值时,未能执行撤销。 这有助于发现一个问题:智能指针类无法可靠地处理自赋值;其根源在于 winrt::com_ptr struct templatewinrt::com_ptr 已修复,事件撤销器也已修正为能够正确处理移动语义,从而在赋值时执行撤销。

Important

在版本 1.0.181002.2 中以及版本 1.0.190128.4 中对 C++/WinRT Visual Studio 扩展(VSIX)进行了重要更改。 有关这些更改的详细信息,以及这些更改会如何影响您现有的项目,请参阅 Visual Studio 对 C++/WinRT 的支持VSIX 扩展的早期版本

与 Windows SDK 头文件相隔离

这对你的代码来说可能是破坏性变更。

为了进行编译,C++/WinRT 不再依赖于 Windows SDK 中的头文件。 C 运行时库(CRT)和 C++ 标准模板库(STL)中的头文件也不包含任何Windows SDK 标头。 这提高了标准合规性,避免了无意的依赖项,并大大减少了必须防范的宏数量。

这种独立性意味着 C++/WinRT 现在更可移植且符合标准,并进一步使它成为跨编译器和跨平台库的可能性。 这也意味着 C++/WinRT 标头不会对宏产生不利影响。

如果你之前一直由 C++/WinRT 负责在你的项目中包含任何 Windows 头文件,那么现在就需要你自己来包含这些头文件。 无论如何,最佳做法始终是显式包含你所依赖的头文件,而不要依赖其他库替你包含这些头文件。

目前,Windows SDK 头文件隔离的唯一例外是内部函数和数值。 这些最后剩余的依赖项没有已知问题。

如果需要,可以在项目中重新启用与 Windows SDK 头文件的互操作。 例如,你可能想要实现 COM 接口(根于 IUnknown)。 对于该示例,请在包含任何 C++/WinRT 标头之前包含 unknwn.h 。 这样做会导致 C++/WinRT 基库启用各种挂钩来支持经典 COM 接口。 有关代码示例,请参阅 使用 C++/WinRT 创作 COM 组件。 同样,显式包含任何其他Windows SDK 标头,这些标头声明要调用的类型和/或函数。

如何将 C++/WinRT 项目重定向到更高版本的 Windows SDK

对项目重新定向目标的方法中,最有可能引发最少编译器和链接器问题的那一种,同时也是最费力的方法。 该方法涉及创建新项目(面向所选的 Windows SDK 版本),然后从旧项目将文件复制到新项目。 你的旧 .vcxproj.vcxproj.filters 文件中有些部分可以直接复制过来,这样就可以省去在 Visual Studio 中添加文件的麻烦。

不过,在 Visual Studio 中,还有另外两种方法可以重新定向项目目标。

  • 转到项目属性“常规>Windows SDK 版本”,然后选择“所有配置”和“所有平台”。 将Windows SDK 版本设置为要面向的版本。
  • 解决方案资源管理器中,右键单击项目节点,单击“重定目标项目”,选择要面向的版本,然后单击“确定”。

如果在使用这两种方法之一后遇到任何编译器或链接器错误,则可以尝试清理解决方案(生成>清理解决方案 和/或手动删除所有临时文件夹和文件),然后再尝试再次生成。

如果 C++ 编译器报出“错误 C2039: 'IUnknown': 不是‘全局命名空间’的成员”,则请将 #include <unknwn.h> 添加到你的 pch.h 文件顶部(在包含任何 C++/WinRT 标头之前)。

你可能还需要在此之后添加 #include <hstring.h>

如果 C++ 链接器报告“错误 LNK2019:函数 _VSDesignerCanUnloadNow@0 中引用了无法解析的外部符号 _WINRT_CanUnloadNow@0”,则可以通过将 #define _VSDESIGNER_DONT_LOAD_AS_DLL 添加到 pch.h 文件中来解决此错误。