本文介绍如何使用 C++/WinRT API,无论它们是 Windows 的一部分、由第三方组件供应商或自行实现。
本文中的代码示例较短,并且很容易试验,可以通过创建新的 Windows 控制台应用程序 (C++/WinRT) 项目和复制粘贴代码来重现它们。 但是,不能按该方法从未打包应用中使用任意自定义(第三方)Windows 运行时类型。 只能对 Windows 类型使用该方法。
若要从控制台应用中使用自定义(第三方)Windows 运行时类型,需要为应用指定一个包标识,以便它可以解析已使用的自定义类型的注册。 有关详细信息,请参阅 Windows 应用程序打包项目。
或者,通过“空白应用(C++/WinRT)”、“核心应用(C++/WinRT)”或“Windows 运行时组件(C++/WinRT)”项目模板创建一个新项目 。 这些应用类型已经具有包标识。
如果 API 位于 Windows 命名空间中
这是你使用 Windows 运行时 API 最常见的情况。 对于元数据中定义的 Windows 命名空间中的每个类型,C++/WinRT 都定义了 C++ 友好等效项(称为投影类型 )。 投影类型具有与 Windows 类型相同的完全限定名称,但使用 C++ 语法放置于 C++ winrt 命名空间中。 例如,Windows::Foundation::Uri 作为 winrt::Windows::Foundation::Uri 投影到 C++/WinRT。
以下是一个简单的代码示例。 若要将以下代码示例直接复制并粘贴到 Windows 控制台应用程序 (C++/WinRT) 项目的主源代码文件中,请先在项目属性中设置“不使用预编译的标头” 。
// main.cpp
#include <winrt/Windows.Foundation.h>
using namespace winrt;
using namespace Windows::Foundation;
int main()
{
winrt::init_apartment();
Uri contosoUri{ L"http://www.contoso.com" };
Uri combinedUri = contosoUri.CombineUri(L"products");
}
包含的标头 winrt/Windows.Foundation.h 是 SDK 的一部分,可在文件夹 %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt\ 内找到。 该文件夹中的标头包含投影到 C++/WinRT 中的 Windows 命名空间类型。 在此示例中,winrt/Windows.Foundation.h 包含 winrt::Windows::Foundation::Uri ,它是运行时类 Windows::Foundation::Uri 的投影类型。
如果希望使用来自 Windows 命名空间的类型,请包括与该命名空间对应的 C++/WinRT 标头。 using namespace 指令是可选的,不过这种指令很方便。
在上述代码示例中,在初始化 C++/WinRT 后,我们将通过其公开记录的构造函数之一(本示例中为 Uri(字符串) )堆叠分配 winrt::Windows::Foundation::Uri 投影类型的值。 这是最常见的用例,也是一般情况下你所要做的全部工作。 在有了 C++/WinRT 投影类型值后,你可以将其视为实际 Windows 运行时类型的实例,因为它具有所有相同的成员。
事实上,该投影值是一个代理;它本质上只是支持对象的智能指针。 投影值的构造函数调用 RoActivateInstance 来创建 Windows 运行时支持类(本例中为 Windows.Foundation.Uri )的实例,并将该对象的默认接口存储在新投影值内。 如下所示,你对投影值的成员的调用实际上通过智能指针代理给支持对象;这是发生状态变化的地方。
当 contosoUri 值超出范围时,它将自行销毁,并将其引用发布到默认接口。 如果该引用是对支持 Windows 运行时 Windows.Foundation.Uri 对象的最后一个引用,支持对象也会自行销毁。
映射类型是出于使用 Windows 运行时类型的 API 针对其的包装器。 例如,投影接口是针对 Windows 运行时接口的包装器。
C++/WinRT 头文件
若要使用 C++/WinRT 的 Windows 命名空间 API,你应使用来自 %WindowsSdkDir%Include<WindowsTargetPlatformVersion>\cppwinrt\winrt 文件夹的头文件。 必须包含与所使用的每个命名空间对应的头文件。
例如,对于 Windows::Security::Cryptography::Certificates 命名空间,等效的 C++/WinRT 类型定义驻留在 winrt/Windows.Security.Cryptography.Certificates.h 中。 包括该头文件时,可访问 Windows::Security::Cryptography::Certificates 命名空间中的所有类型。
有时,一个命名空间头文件将包含相关命名空间头文件的一部分,但不应依赖于此实现详细信息。 显式包含使用的命名空间的头文件。
例如,Certificate::GetCertificateBlob 方法返回 Windows::Storage::Streams::IBuffer 接口。 在调用 Certificate::GetCertificateBlob 方法之前,必须包含 winrt/Windows.Storage.Streams.h 命名空间头文件,以确保可以接收并操作返回的 Windows::Storage::Streams::IBuffer。
忘记在使用该命名空间中的类型之前包含所需的命名空间头文件是常见的生成错误。
通过对象、接口或通过 ABI 访问成员
使用 C++/WinRT 投影,Windows 运行时类的运行时表示形式将只是基础 ABI 接口。 不过,为方便起见,你可以通过类作者预期的方式根据类编码。 例如,你可以调用 Uri 的 ToString 方法,就像它是该类的方法(实际上,再深入一层,它是单独的 IStringable 接口上的方法)。
WINRT_ASSERT 是宏定义,并且扩展到 _ASSERTE。
Uri contosoUri{ L"http://www.contoso.com" };
WINRT_ASSERT(contosoUri.ToString() == L"http://www.contoso.com/"); // QueryInterface is called at this point.
// 这种便利是通过对相应接口的查询实现的。 不过始终在你的控制范围内。
// 你可以通过自己检索 IStringable 接口并直接使用它来放弃一点便利,从而提高一点性能。
// 在下方的代码示例中,你在运行时获取实际的 IStringable 接口指针(通过一次性查询)。
// 此后,你对 ToString 的调用是直接的,并且避免进一步调用 QueryInterface。
...
IStringable stringable = contosoUri; // One-off QueryInterface.
WINRT_ASSERT(stringable.ToString() == L"http://www.contoso.com/");
// 如果你知道你将在同一个接口调用多个方法,你可能会选择使用此技巧。
// 顺便说一下,如果你确实希望访问 ABI 级别的成员,那么你可以这样做。
// 代码示例显示了如何操作、实现 C++/WinRT 与 ABI 之间的互操作中还提供了更多信息和代码示例。
#include <Windows.Foundation.h>
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>
using namespace winrt::Windows::Foundation;
int main()
{
winrt::init_apartment();
Uri contosoUri{ L"http://www.contoso.com" };
int port{ contosoUri.Port() }; // Access the Port "property" accessor via C++/WinRT.
winrt::com_ptr<ABI::Windows::Foundation::IUriRuntimeClass> abiUri{
contosoUri.as<ABI::Windows::Foundation::IUriRuntimeClass>() };
HRESULT hr = abiUri->get_Port(&port); // Access the get_Port ABI function.
}