有关使用 C++/WinRT 编写和调用 Windows 运行时 API 时你可能会遇到的问题的解答。
Important
有关 C++/WinRT 的发行说明,请参阅 C++/WinRT 2.0 中的新闻和更改。
注释
如果你的问题与你看到的错误消息有关,另请参阅 “C++/WinRT 故障排除 ”主题。
在哪里可以找到 C++/WinRT 示例应用?
请参阅 C++/WinRT 示例应用。
如何将 C++/WinRT 项目重定向到更高版本的 Windows SDK?
为什么我的新项目无法编译,现在我已移动到 C++/WinRT 2.0?
有关完整的更改内容(包括重大更改),请参阅 C++/WinRT 2.0 中的新闻和更改。 例如,如果你在 Windows 运行时 集合上使用基于范围的 for,那么现在你需要 #include <winrt/Windows.Foundation.Collections.h>。
为什么我的新项目无法编译? 我使用的是 Visual Studio 2017(版本 15.8.0 或更高版本),SDK 版本 17134
如果使用 Visual Studio 2017(版本 15.8.0 或更高版本),并面向 Windows SDK 版本 10.0.17134.0(Windows 10, 版本 1803),然后新创建的 C++/WinRT 项目可能无法编译,并出现错误“错误 C3861: 'from_abi': 找不到标识符”,以及源自 base.h 的其他错误。 解决方案是面向更高版本的 Windows SDK,或设置项目属性 C/C++>语言>一致性模式:否(如果 /permissive- 显示在项目属性 C/C++>命令行中的“其他选项”下,请将其删除)。
如何解决生成错误“C++/WinRT VSIX 不再提供项目生成支持。 请添加对 Microsoft.Windows.CppWinRT NuGet 包的项目引用?
在项目中安装 Microsoft.Windows.CppWinRT NuGet 包。 有关详细信息,请参阅 早期版本的 VSIX 扩展。
如何在 NuGet 包中自定义生成支持?
C++/WinRT 生成支持(属性/目标)记录在 Microsoft.Windows.CppWinRT NuGet 包自述文件中。
C++/WinRT Visual Studio 扩展 (VSIX)的要求是什么?
有关 VSIX 扩展及更高版本的 1.0.190128.4 版,请参阅Visual Studio对 C++/WinRT 的支持。 有关其他版本,请参阅 VSIX 扩展的早期版本。
什么是运行时类?
运行时类是一种类型,可通过新式 COM 接口激活和使用,通常跨可执行边界。 但是,还可以在实现运行时的编译单元中使用运行时类。 使用接口定义语言(IDL)声明运行时类,可以使用 C++/WinRT 在标准 C++ 中实现它。
投影类型和实现类型的含义是什么?
如果你只是使用某个 Windows 运行时 类(运行时类),那么你接触到的就只会是投影类型。 C++/WinRT 是一种语言投影,所以投影类型是通过 C++/WinRT 投影到 C++ 中的 Windows 运行时的表面的一部分。 有关详细信息,请参阅 通过 C++/WinRT 使用 API。
实现类型包含运行时类的实现,因此它仅在实现运行时类的项目中可用。 当你在实现运行时类的项目中工作时(例如 Windows 运行时 组件项目,或使用 XAML UI 的项目),务必要清楚区分运行时类的实现类型,以及该运行时类投影到 C++/WinRT 后所对应的投影类型。 有关详细信息,请参阅 使用 C++/WinRT 创作 API。
是否需要在运行时类的 IDL 中声明构造函数?
仅当运行时类设计为从其实现编译单元外部进行使用时(即适用于 Windows 运行时客户端应用的一般使用的 Windows 运行时组件)。 有关在 IDL 中声明构造函数的目的和后果的完整详细信息,请参阅 运行时类构造函数。
为什么编译器向我显示一个“C3779: consume_Something: 返回 'auto' 的函数在定义该函数之前不能使用”错误?
你在未先包含对应的命名空间头文件的情况下使用了 Windows 运行时 对象。 包含以 API 的命名空间命名的头文件,然后重新构建。 有关详细信息,请参阅 C++/WinRT 投影标头。
为什么链接器给我一个“LNK2019:未解析的外部符号”错误?
如果未解析的符号是 Windows 运行时 自由函数,例如 RoInitialize,那么你需要在项目中显式链接 WindowsApp.lib 总括库。 C++/WinRT 投影依赖于其中一些免费的(非成员)函数和入口点。 如果你的应用程序使用了 C++/WinRT Visual Studio 扩展(VSIX)项目模板之一,那么系统会自动为你链接 WindowsApp.lib。 否则,您可以通过项目链接设置将其包含进来,或者直接在源代码中进行设置。
#pragma comment(lib, "windowsapp")
务必通过链接 WindowsApp.lib 而非其他静态链接库,尽可能解决所有可解决的链接器错误;否则,您的应用程序将无法通过 Visual Studio 和 Microsoft Store 用于验证提交内容的 Windows 应用认证工具包 测试(这意味着您的应用程序将无法成功纳入 Microsoft Store)。
如果未解析的符号是构造函数,则你可能忘记了包含所构造类的命名空间头文件。 包含以该类的命名空间命名的头文件,然后重新生成。 有关详细信息,请参阅 C++/WinRT 投影标头。
为什么会收到“类未注册”异常?
在这种情况下,症状表现为:在创建运行时类实例或访问静态成员时,会在运行时引发异常,并且该异常的 HRESULT 值为 REGDB_E_CLASSNOTREGISTERED。
原因之一可能是你的 Windows 运行时 组件无法加载。 确保组件的Windows 运行时元数据文件 () 的名称与组件二进制文件 (.winmdthe.dll) 相同,这也是项目的名称和根命名空间的名称。 此外,还请确保 Windows 运行时 元数据和二进制文件已由生成过程正确复制到使用方应用的 Appx 文件夹中。 并确认使用方应用的 AppxManifest.xml(也位于 Appx 文件夹中)包含一个 <InProcessServer> 元素,该元素正确声明了可激活类和二进制文件名。
统一构造 如果尝试通过任何投影类型的构造函数(而不是其 std::nullptr_t 构造函数)实例化本地实现的运行时类,也可能发生此错误。 为此,需要通常称为统一构造的 C++/WinRT 2.0 功能。 如果要选择加入该功能,有关详细信息和代码示例,请参阅 “选择加入统一构造”和直接实现访问。
若要了解如何实例化本地实现的、不需要统一构造的运行时类,请参阅 XAML 控件;绑定到 C++/WinRT 属性。
是否应实现 Windows::Foundation::IClosable,如果是,应如何实现?
如果你有一个运行时类会在其析构函数中释放资源,并且该运行时类旨在供其实现编译单元之外的代码使用(即它是一个供 Windows 运行时 客户端应用普遍使用的 Windows 运行时 组件),那么我们建议你也实现 IClosable,以便支持那些缺乏确定性终结机制的语言使用你的运行时类。 无论调用析构函数、IClosable::Close,还是同时调用两者,都要确保资源得到释放。 IClosable::Close 可以调用任意次数。
我是否需要对所使用的运行时类调用 IClosable::Close?
IClosable 存在以支持缺乏确定性最终化的语言。 因此,通常不需要从 C++/WinRT 调用 IClosable::Close 。 但请考虑一下这条一般规则的例外情况。
- 在涉及关闭竞争或半死锁的极罕见情况下,需调用 IClosable::Close。 例如,如果使用的是 Windows.UI.Composition 类型,则可能会遇到按照设定的顺序释放对象的情况,作为允许 C++/WinRT 包装器的析构为你执行该工作的替代方式。
- 如果你无法保证自己持有的是该对象仅剩的最后一个引用(因为你已将它传递给其他 API,而这些 API 可能仍保留着对它的引用),那么调用 IClosable::Close 是个不错的做法。
- 如果不能肯定,则为了安全起见,可以手动调用 IClosable::Close,而不是等待包装器在析构时调用它。
因此,如果你知道你有最后一个引用,则可以让包装器析构函数执行该工作。 如果需要在最后一个引用消失之前关闭,则需要调用 Close。 为了具有异常安全性,应采用“资源获取即初始化”(RAII) 类型的 Close(以使关闭在释放时发生)。 C++/WinRT 没有 unique_close 封装器,但你可以自己实现一个。
是否可以使用 LLVM/Clang 通过 C++/WinRT 进行编译?
我们不支持适用于 C++/WinRT 的 LLVM 和 Clang 工具链,但我们确实在内部使用它来验证 C++/WinRT 的标准符合性。 例如,如果想要模拟我们在内部执行的操作,则可以尝试试验,例如下面所述的试验。
转到 LLVM 下载页,查找下载 LLVM 6.0.0>预生成的二进制文件,并下载 Clang for Windows(64 位)。 在安装过程中,选择将 LLVM 添加到 PATH 系统变量,以便可以从命令提示符调用它。 出于此试验的目的,如果看到这些错误,可以忽略任何“找不到 MSBuild 工具集目录”和/或“MSVC 集成安装失败”错误。 可通过多种方式调用 LLVM/Clang;下面的示例只显示了一种方式。
C:\ExperimentWithLLVMClang>type main.cpp
// main.cpp
#pragma comment(lib, "windowsapp")
#pragma comment(lib, "ole32")
#include <winrt/Windows.Foundation.h>
#include <stdio.h>
#include <iostream>
using namespace winrt;
int main()
{
winrt::init_apartment();
Windows::Foundation::Uri rssFeedUri{ L"https://blogs.windows.com/feed" };
std::wcout << rssFeedUri.Domain().c_str() << std::endl;
}
C:\ExperimentWithLLVMClang>clang-cl main.cpp /EHsc /I ..\.. -Xclang -std=c++17 -Xclang -Wno-delete-non-virtual-dtor -o app.exe
C:\ExperimentWithLLVMClang>app
windows.com
由于 C++/WinRT 使用了 C++17 标准中的特性,因此你需要使用启用该支持所需的编译器标志;这些标志因编译器而异。
Visual Studio是我们支持并推荐用于 C++/WinRT 的开发工具。 请参阅对 C++/WinRT 的Visual Studio支持。
为什么只读属性的生成的实现函数没有 const 限定符?
在 MIDL 3.0 中声明只读属性时,你可能会期望 cppwinrt.exe 工具为你生成一个带有 const 限定符的实现函数(const 函数会将 this 指针视为 const)。
我们当然建议尽可能使用 const,但 cppwinrt.exe 工具本身不会尝试推理哪些实现函数可能是常理的,也可能不是。 可以选择将任何实现函数设为常量,如以下示例所示。
struct MyStringable : winrt::implements<MyStringable, winrt::Windows::Foundation::IStringable>
{
winrt::hstring ToString() const
{
return L"MyStringable";
}
};
如果确定需要在 ToString 实现中更改某些对象状态,则可以删除 constToString 上的该限定符。 但是,将每个成员函数设为常量或非常量,而不是两者。 换句话说,不要基于 const 对实现函数进行重载。
除了你的实现函数之外,另一个会涉及 const 的地方是在 Windows 运行时 函数投影中。 请考虑此代码。
int main()
{
winrt::Windows::Foundation::IStringable s{ winrt::make<MyStringable>() };
auto result{ s.ToString() };
}
对于上述对 ToString 的调用,Visual Studio 中的 Go To Declaration 命令显示,Windows 运行时 的 IStringable::ToString 在 C++/WinRT 中的投影看起来如下。
winrt::hstring ToString() const;
无论选择如何限定投影的实现,投影上的函数都是常量。 在后台,投影调用应用程序二进制接口(ABI),这相当于通过 COM 接口指针进行调用。 投影 ToString 与之交互的唯一状态是 COM 接口指针;并且它当然不需要修改该指针,因此该函数是常的。 这样就可以保证不会更改调用时所用的 IStringable 引用的任何信息,并确保即使是使用对 IStringable 的 const 引用,也能调用 ToString。
请理解,这些 const 示例属于 C++/WinRT 投影和实现的实现细节;这样做是为了帮助你保持良好的代码规范。 无论在 COM 还是 Windows 运行时 ABI 中,都不存在 const 这种东西(就成员函数而言)。
是否有任何建议来减少 C++/WinRT 二进制文件的代码大小?
使用 Windows 运行时 对象时,应避免采用如下所示的编码模式,因为它会导致生成不必要的额外二进制代码,从而对应用程序产生负面影响。
anobject.b().c().d();
anobject.b().c().e();
anobject.b().c().f();
在 Windows 运行时 环境中,编译器无法缓存 c() 的值,也无法缓存通过间接引用('.')调用的每个方法的接口。 除非进行干预,否则会造成虚拟调用数和引用计数开销增大。 以上模式很容易使得生成的代码量是严格需要量的两倍。 相反,在可行的情况下,应优先使用下面所示的写法。 它生成的代码要少得多,而且还可以显著提高运行时性能。
auto a{ anobject.b().c() };
a.d();
a.e();
a.f();
上面所示的建议模式不仅适用于 C++/WinRT,也适用于所有Windows 运行时语言投影。
如何将字符串转换为类型(例如导航) ?
在 导航视图代码示例 (主要在 C# 中)末尾,有一个 C++/WinRT 代码片段,演示如何执行此操作。
如何使用 GetCurrentTime 和/或 TRY 解决歧义?
头文件 winrt/Windows.UI.Xaml.Media.Animation.h 声明名为 GetCurrentTime 的方法,而 windows.h (通过 winbase.h)定义名为 GetCurrentTime 的宏。 当两个冲突时,C++ 编译器会生成“错误 C4002:函数般的宏调用 GetCurrentTime 的参数过多”。
同样, winrt/Windows.Globalization.h 声明名为 TRY 的方法,同时 afx.h 定义名为 TRY 的宏。 当这些情况发生冲突时,C++ 编译器会报出“错误 C2334:在“{”前出现意外的标记;跳过明显的函数体”。
若要修复一个或两个问题,可以执行此操作。
#pragma push_macro("GetCurrentTime")
#pragma push_macro("TRY")
#undef GetCurrentTime
#undef TRY
#include <winrt/include_your_cppwinrt_headers_here.h>
#include <winrt/include_your_cppwinrt_headers_here.h>
#pragma pop_macro("TRY")
#pragma pop_macro("GetCurrentTime")
如何加快符号加载速度?
在 Visual Studio 中,依次选择 工具>选项>调试>符号>,然后勾选 仅加载指定的模块。 然后,可以在堆栈列表中右键单击 DLL 并加载单个模块。
注释
如果本主题未回答你的问题,则可以通过访问 Visual Studio C++ 开发人员社区或使用 c++-winrt Stack Overflow 上的标记找到帮助。