从 C++/CX 迁移到 C++/WinRT

本主题是本系列中的第一个,介绍如何将 C++/CX 项目中的源代码移植到 C++/WinRT中的等价代码。

如果项目同时使用 Windows 运行时 C++ 模板库(WRL) 类型,请参阅 从 WRL 迁移到 C++/WinRT

移植策略

值得注意的是,从 C++/CX 移植到 C++/WinRT 通常都很简单,唯一的例外是将 并行模式库(PPL) 的任务迁移到协程。 模型不同。 没有从 PPL 任务到协同例程的自然一对一映射,也没有简单的方法来机械移植适用于所有情况的代码。 有关移植这一特定方面的帮助,以及两种模型之间的互操作选项,请参阅 Asynchrony,以及 C++/WinRT 与 C++/CX之间的互操作。

开发团队经常报告,一旦他们克服移植异步代码的挑战,移植过程的其余部分基本上都是机械性的。

一次性移植

如果您可以一次性移植整个项目,那么只需要这个主题来获取所需的信息(而不需要 互操作 后续主题)。 建议首先使用 C++/WinRT 项目模板之一在 Visual Studio 中创建新项目(请参阅 Visual Studio 对 C++/WinRT 的支持)。 然后将源代码文件移到该新项目中,并在此过程中将所有 C++/CX 源代码迁移到 C++/WinRT。

或者,如果想要在现有 C++/CX 项目中执行移植工作,则需要向其添加C++/WinRT 支持。 要实现这一点,请按照 在 C++/CX 项目中添加 C++/WinRT 支持中描述的步骤进行操作。 完成移植后,你将将纯C++/CX 项目转换为纯C++/WinRT 项目。

注释

如果你有 Windows 运行时组件项目,移植一次是唯一的选项。 以C++编写的 Windows 运行时组件项目必须包含所有C++/CX 源代码,或者所有C++/WinRT 源代码。 它们不能在此项目类型中共存。

逐步地移植项目

除了 Windows 运行时组件项目,如上一部分所述,如果基本代码的大小或复杂性使得需要逐步移植项目,则需要一个移植过程,其中一段时间C++/CX 和 C++/WinRT 代码并排存在于同一项目中。 除了阅读本主题,还请参阅 C++/WinRT 与 C++/CX 之间的互操作 以及 C++/WinRT 和 C++/CX 之间的异步及互操作。 这些主题提供信息和代码示例,演示如何在两种语言投影之间进行互作。

若要使项目准备好进行逐步移植过程,一个选项是向 C++/CX 项目添加C++/WinRT 支持。 要实现这一点,请按照 在 C++/CX 项目中添加 C++/WinRT 支持中描述的步骤进行操作。 然后,你可以从那里逐步移植。

另一个选项是在 Visual Studio 中使用 C++/WinRT 项目模板之一创建新项目(请参阅 Visual Studio 对 C++/WinRT 的支持)。 然后向该项目添加C++/CX 支持。 按照 中所述的步骤来执行 C++/WinRT 项目并添加 C++/CX 支持。 然后,可以开始将源代码移到其中,并移植 C++/CX 源代码的一些,以便C++/WinRT。

无论哪种情况,您的C++/WinRT代码都会与尚未移植的任何C++/CX代码进行双向互操作。

注释

C++/CX 和 Windows SDK 都在根命名空间 Windows 中声明类型。 投影到 C++/WinRT 的 Windows 类型的完全限定名称与 Windows 类型相同,但它位于 C++ winrt 命名空间中。 通过这些不同的命名空间,你可以按照自己的节奏从 C++/CX 移植到 C++/WinRT。

逐步移植 XAML 项目

重要

对于使用 XAML 的项目,在任何时候,所有 XAML 页面类型都需要完全是 C++/CX 或完全是 C++/WinRT。 你仍然可以在同一项目的模型、视图模型以及其他地方,除了 XAML 页面类型 外,混合使用 C++/CX 和 C++/WinRT

对于此方案,我们建议的工作流是创建新的 C++/WinRT 项目,并从 C++/CX 项目复制源代码和标记。 只要所有的 XAML 页面类型都是 C++/WinRT,那么您可以通过使用 项目>中的“添加新项…”>Visual C++>选择“空白页(C++/WinRT)”来添加新的 XAML 页面。

或者,您可以在移植 XAML C++/CX 项目时使用 Windows 运行时组件 (WRC) 将代码从项目中分离出来。

  • 可以创建新的C++/CX WRC 项目,将尽可能多的C++/CX 代码移动到该项目,然后将 XAML 项目更改为 C++/WinRT。
  • 或者,可以创建新的 C++/WinRT WRC 项目,将 XAML 项目保留为 C++/CX,并开始将 C++/CX 移植到 C++/WinRT,并将生成的代码移出 XAML 项目和组件项目。
  • 还可以在同一解决方案中将C++/CX 组件项目与 C++/WinRT 组件项目一起,从应用程序项目引用这两个项目,然后逐渐从一个组件移植到另一个组件项目。 同样,有关在同一项目中使用两种语言投影的详细信息,请参阅 C++/WinRT 与 C++/CX 之间的 互操作。

将 C++/CX 项目移植到 C++/WinRT 的第一步

无论您的移植策略为何(一次性移植或逐步移植),您的第一步是为移植做好项目准备。 以下是我们在 策略方面所描述的关于移植 的内容,以及您将要开始的项目类型及其设置方法的回顾。

  • 一遍完成移植。 使用 C++/WinRT 项目模板之一在 Visual Studio 中创建新项目。 将文件从 C++/CX 项目移动到该新项目中,并移植 C++/CX 源代码。
  • 逐渐移植非 XAML 项目。 可以选择将 C++/WinRT 支持添加到 C++/CX 项目(请参阅 “获取C++/CX 项目并添加 C++/WinRT 支持”),并逐步移植。 或者,可以选择创建新的 C++/WinRT 项目,并向其添加C++/CX 支持(请参阅 “获取C++/WinRT 项目并添加C++/CX 支持”),逐步移动文件和端口。
  • 移植 XAML 项目逐渐。 创建新的C++/WinRT 项目、移动文件以及逐步移植。 在任何给定时间,XAML 页面类型都必须 所有C++/WinRT 所有C++/CX。

无论选择哪种移植策略,本主题的其他部分均适用。 它包含将源代码从 C++/CX 移植到 C++/WinRT 所涉及的技术详细信息目录。 如果要逐步移植,则你可能还希望看到 C++/WinRT 与 C++/CXAsynchrony 之间的互操作,以及 C++/WinRT 与 C++/CX之间的互操作。

文件命名约定

XAML 标记文件

文件源 C++/CX C++/WinRT
开发人员 XAML 文件 MyPage.xaml
MyPage.xaml.h
MyPage.xaml.cpp
MyPage.xaml
MyPage.h
MyPage.cpp
MyPage.idl (请参阅下文)
生成的 XAML 文件 MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.xaml.g.h
MyPage.xaml.g.hpp
MyPage.g.h

请注意,C++/WinRT 将 .xaml 从文件名 *.h*.cpp 中删除。

C++/WinRT 添加了一个额外的开发人员文件,Midl 文件(.idl)。 C++/CX 在内部自动生成此文件,并将每个公共和受保护的成员添加到其中。 在 C++/WinRT 中,可以自行添加和创作文件。 有关更多详细信息、代码示例和创作 IDL 的演练,请参阅 XAML 控件;绑定到 C++/WinRT 属性

另请参阅 将运行时类拆分为 Midl 文件(.idl)

运行时类

C++/CX 不会对头文件的名称施加限制;通常将多个运行时类定义放入单个头文件中,尤其是对于小型类。 但是,C++/WinRT 要求每个运行时类都有自己的标头文件,该文件以类名命名。

C++/CX C++/WinRT
Common.h
ref class A { ... }
ref class B { ... }
Common.idl
runtimeclass A { ... }
runtimeclass B { ... }
A.h
namespace implements {
  struct A { ... };
}
B.h
namespace implements {
  struct B { ... };
}

C++/CX 中不太常见(但仍然合法)是对 XAML 自定义控件使用不同的命名头文件。 需要重命名这些头文件以匹配类名。

C++/CX C++/WinRT
A.xaml
<Page x:Class="LongNameForA" ...>
A.xaml
<Page x:Class="LongNameForA" ...>
A.h
partial ref class LongNameForA { ... }
LongNameForA.h
namespace implements {
  struct LongNameForA { ... };
}

头文件要求

C++/CX 不需要包含任何特殊头文件,因为它在内部自动生成 .winmd 文件中的头文件。 通常,C++/CX 对按名称使用的命名空间使用 using 指令。

using namespace Windows::Media::Playback;

String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
    return item->VideoTracks->GetAt(0)->Name;
}

using namespace Windows::Media::Playback 指令允许我们在没有命名空间前缀的情况下编写 MediaPlaybackItem 。 我们还涉及了 Windows.Media.Core 命名空间,因为 item->VideoTracks->GetAt(0) 返回了 Windows.Media.Core.VideoTrack。 但是,我们不必在任何地方输入名称 VideoTrack,因此我们不需要 using Windows.Media.Core 指令。

但是,C++/WinRT 要求你包含一个对应于你使用的每个命名空间的头文件,即使你没有命名它。

#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!

using namespace winrt;
using namespace Windows::Media::Playback;

winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
    return item.VideoTracks().GetAt(0).Name();
}

另一方面,尽管 MediaPlaybackItem.AudioTracksChanged 事件是类型 TypedEventHandler<MediaPlaybackItem、Windows.Foundation.Collections.IVectorChangedEventArgs>,但是由于我们没有使用该事件,所以不需要包括 winrt/Windows.Foundation.Collections.h

C++/WinRT 还要求你包含 XAML 标记所用命名空间的头文件。

<!-- MainPage.xaml -->
<Rectangle Height="400"/>

使用 Rectangle 类意味着您必须添加此包含。

// MainPage.h
#include <winrt/Windows.UI.Xaml.Shapes.h>

如果忘记了头文件,则所有内容都会编译正常,但你将收到链接器错误,因为缺少 consume_ 类。

参数传递

编写 C++/CX 源代码时,请将 C++/CX 类型作为 hat(^) 引用传递,以用作函数参数。

void LogPresenceRecord(PresenceRecord^ record);

在 C++/WinRT 中,对于同步函数,默认情况下应使用 const& 参数。 这将避免复制和锁定开销。 但是,你的协同例程应使用传递值来确保它们按值捕获并避免生存期问题(有关详细信息,请参阅 并发和异步操作以及C++/WinRT)。

void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);

C++/WinRT 对象本质上是一个值,它保存指向支持 Windows 运行时对象的接口指针。 复制 C++/WinRT 对象时,编译器将复制封装的接口指针,从而递增其引用计数。 复制的最终销毁涉及递减引用计数。 因此,仅在必要时产生副本的开销。

变量和字段引用

编写C++/CX 源代码时,可以使用 hat (^) 变量引用 Windows 运行时对象,并使用箭头 (->) 运算符取消引用 hat 变量。

IVectorView<User^>^ userList = User::Users;

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
    ...

移植到等效的 C++/WinRT 代码时,通过去掉帽符并将箭头运算符(->)更改为点运算符(.),可以大大简化过程。 C++/WinRT 投影类型是值,而不是指针。

IVectorView<User> userList = User::Users();

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
    ...

C++/CX hat 引用的默认构造函数将其初始化为 null。 下面是一个C++/CX 代码示例,在其中创建正确类型的变量/字段,但未初始化。 换句话说,它最初不引用 TextBlock;我们打算稍后指定引用。

TextBlock^ textBlock;

class MyClass
{
    TextBlock^ textBlock;
};

有关 C++/WinRT 中的等效项,请参阅 延迟初始化

性能

C++/CX 语言扩展包括属性的概念。 编写C++/CX 源代码时,可以像是字段一样访问属性。 标准C++没有属性的概念,因此,在 C++/WinRT 中,调用 get 和 set 函数。

在以下示例中, XboxUserIdUserStatePresenceDeviceRecordsSize 都是属性。

从属性中获取值

下面介绍了如何在 C++/CX 中获取属性值。

void Sample::LogPresenceRecord(PresenceRecord^ record)
{
    auto id = record->XboxUserId;
    auto state = record->UserState;
    auto size = record->PresenceDeviceRecords->Size;
}

等效C++/WinRT 源代码调用与属性同名但没有参数的函数。

void Sample::LogPresenceRecord(PresenceRecord const& record)
{
    auto id = record.XboxUserId();
    auto state = record.UserState();
    auto size = record.PresenceDeviceRecords().Size();
}

请注意, PresenceDeviceRecords 函数返回本身具有 Size 函数的 Windows 运行时对象。 由于返回的对象也是一种C++/WinRT 投影类型,因此我们将使用点运算符取消引用以调用 Size

将属性设置为新值

将属性设置为新值遵循类似的模式。 首先,在 C++/CX 中。

record->UserState = newValue;

若要在 C++/WinRT 中执行等效作,请调用与属性同名的函数,并传递参数。

record.UserState(newValue);

创建类的实例

通过句柄处理C++/CX 对象,通常称为 hat (^) 引用。 通过 ref new 关键字创建新对象,进而调用 RoActivateInstance 来激活运行时类的新实例。

using namespace Windows::Storage::Streams;

class Sample
{
private:
    Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};

C++/WinRT 对象是一个值;因此,可以在堆栈上或作为对象的字段进行分配。 你 从不 使用 ref new(或 new)来分配C++/WinRT 对象。 在幕后, RoActivateInstance 仍在被调用。

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
private:
    Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};

如果资源初始化成本高昂,则通常会延迟初始化资源,直到实际需要。 如前所述,C++/CX hat 引用的默认构造函数将其初始化为 null。

using namespace Windows::Storage::Streams;

class Sample
{
public:
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer^ m_gamerPicBuffer;
};

移植到 C++/WinRT 的代码相同。 请注意 std::nullptr_t 构造函数的使用。 有关该构造函数的详细信息,请参阅 延迟初始化

using namespace winrt::Windows::Storage::Streams;

struct Sample
{
    void DelayedInit()
    {
        // Allocate the actual buffer.
        m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
    }

private:
    Buffer m_gamerPicBuffer{ nullptr };
};

默认构造函数如何影响集合

C++集合类型使用默认构造函数,这可能会导致意外的对象构造。

情景 C++/CX C++/WinRT (不正确) C++/WinRT (正确)
局部变量,最初为空 TextBox^ textBox; TextBox textBox; // Creates a TextBox! TextBox textBox{ nullptr };
成员变量,最初为空 class C {
  TextBox^ textBox;
};
class C {
  TextBox textBox; // Creates a TextBox!
};
class C {
  TextBox textbox{ nullptr };
};
全局变量,最初为空 TextBox^ g_textBox; TextBox g_textBox; // Creates a TextBox! TextBox g_textBox{ nullptr };
空的引用向量 std::vector<TextBox^> boxes(10); // Creates 10 TextBox objects!
std::vector<TextBox> boxes(10);
std::vector<TextBox> boxes(10, nullptr);
在地图中设置值 std::map<int, TextBox^> boxes;
boxes[2] = value;
std::map<int, TextBox> boxes;
// Creates a TextBox at 2,
// then overwrites it!
boxes[2] = value;
std::map<int, TextBox> boxes;
boxes.insert_or_assign(2, value);
空引用数组 TextBox^ boxes[2]; // Creates 2 TextBox objects!
TextBox boxes[2];
TextBox boxes[2] = { nullptr, nullptr };
一对 std::pair<TextBox^, String^> p; // Creates a TextBox!
std::pair<TextBox, String> p;
std::pair<TextBox, String> p{ nullptr, nullptr };

更多关于空引用集合的信息

每当在 C++/CX 中有 Platform::Array^(请参阅 端口 Platform::Array^),可以选择将其移植到 C++/WinRT 中的 std::vector(事实上,任何连续容器),而不是将其保留为数组。 选择 std::vector有其优势。

例如,虽然有这样的速记来创建固定大小的空引用矢量(见上表),但是没有这种速记来创建空引用的 数组。 您必须对数组中的每个元素重复执行 nullptr 。 如果太少,则额外内容将默认构造。

对于向量,可以在初始化时用空引用(如上表中所示)填充它,或者在初始化后,可以通过如下代码来填充空引用。

std::vector<TextBox> boxes(10); // 10 default-constructed TextBoxes.
boxes.resize(10, nullptr); // 10 empty references.

关于 std::map 示例的更多信息

[] 下标运算符行为如下。

  • 如果在映射中找到键,则返回现有值的引用(此引用可以被覆盖)。
  • 如果在映射中找不到该键,则在映射中创建一个新条目,其中包含键(如果可移动则移动),默认构造的一个值,并返回对该值的引用(然后可以覆盖该值)。

换句话说, [] 运算符始终在地图中创建一个条目。 这不同于 C#、Java 和 JavaScript。

从基运行时类转换为派生类

通常情况下,你会有一个指向基类的引用,而你知道这个引用指的是一个派生类型的对象。 在 C++/CX 中,将基类引用转换为派生类引用时需使用 dynamic_cast。 这个 dynamic_cast 确实只是对 QueryInterface 的隐藏调用。 下面是一个典型的示例-你正在处理依赖项属性更改事件,并且你想要从 DependencyObject 转换回拥有依赖属性的实际类型。

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject^ d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
    BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };

    if (theControl != nullptr)
    {
        // succeeded ...
    }
}

等效的 C++/WinRT 代码将 dynamic_cast 替换为对 IUnknown::try_as 函数的调用,这个调用封装了 QueryInterface。 还可以选择调用 IUnknown::as,如果未返回查询所需的接口(所请求类型的默认接口),则会引发异常。 下面是C++/WinRT 代码示例。

void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
    {
        // succeeded ...
    }

    try
    {
        BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
        // succeeded ...
    }
    catch (winrt::hresult_no_interface const&)
    {
        // failed ...
    }
}

派生类

若要从运行时类派生,基类必须是 可组合的。 C++/CX 不要求你采取任何特殊步骤来使类可组合,但 C++/WinRT 则需要这样做。 你可以使用 关键字 来指示类是开放的,可以作为基类使用。

unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

在实现的标头类中,必须在包含派生类的自动生成标头之前,先包含基类的头文件。 否则,将收到错误,例如“非法使用此类型作为表达式”。

// DerivedPage.h
#include "BasePage.h"       // This comes first.
#include "DerivedPage.g.h"  // Otherwise this header file will produce an error.

namespace winrt::MyNamespace::implementation
{
    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        ...
    }
}

使用委托处理事件

下面是在 C++/CX 中处理事件的典型示例,在本例中将 lambda 函数用作委托。

auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

这在 C++/WinRT 中是等效的。

auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
    // Handle the event.
    // Note: locals are captured by value, not reference, since this handler is delayed.
});

可以选择将委托实现为自由函数或成员函数指针,而不是 lambda 函数。 有关详细信息,请参阅 通过使用 C++/WinRT 中的委托处理事件

如果您从一个使用事件和委托但仅限于内部(不跨二进制文件)的 C++/CX 代码库进行移植,那么 winrt::delegate 将帮助您在 C++/WinRT 中复制该模式。 另请参阅项目中的 参数化委托、简单信号和回调

撤消委托

在 C++/CX 中,您使用 -= 操作符以撤销之前的事件注册。

myButton->Click -= token;

这在 C++/WinRT 中是等效的。

myButton().Click(token);

有关选项和详细信息,请参阅 撤销已注册的代表

装箱和拆箱

C++/CX 自动将标量封装成对象。 C++/WinRT 要求显式调用 winrt::box_value 函数。 你需要在这两种语言中显式地进行取消装箱操作。 请参阅 C++/WinRT 装箱和拆箱

在后面的表中,我们将使用这些定义。

C++/CX C++/WinRT
int i; int i;
String^ s; winrt::hstring s;
Object^ o; IInspectable o;
操作 C++/CX C++/WinRT
拳击 o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
拆 箱 i = (int)o;
s = (String^)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

如果尝试将空指针解包为值类型,C++/CX 和 C# 会引发异常。 C++/WinRT 将此视为编程错误,并且它崩溃。 在 C++/WinRT 中,如果要处理对象不是你认为的类型的情况,请使用 winrt::unbox_value_or 函数。

情景 C++/CX C++/WinRT
拆箱已知整数 i = (int)o; i = unbox_value<int>(o);
如果 o 为 null Platform::NullReferenceException 崩溃
如果 o 不是封装的整数 Platform::InvalidCastException 崩溃
拆箱 int,如果为 null,则使用备用方案;如果是其他情况,则崩溃。 i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
如果可能,拆箱 int;为其他情况使用备用方案。 auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

字符串的装箱和拆箱

字符串在某些方面是值类型,在其他方面是引用类型。 C++/CX 和 C++/WinRT 以不同的方式对待字符串。

ABI 类型 HSTRING 是指向引用计数字符串的指针。 但它不派生自 IInspectable,因此从技术上来说,它不是 对象。 此外,值为空的 HSTRING 表示空字符串。 从不派生自 IInspectable 的事物进行封装是通过将它们封装在 IReference<T>中来完成的,Windows 运行时以 PropertyValue 对象的形式提供了一个标准实现(自定义类型报告为 PropertyType::OtherType)。

C++/CX 将 Windows 运行时字符串表示为引用类型;而C++/WinRT 将字符串投影为值类型。 这意味着封装的空字符串可以有不同的表示形式,这取决于你获取它的方式。

此外,C++/CX 允许取消引用 null String^,在这种情况下,它的行为类似于字符串 ""

行为 C++/CX C++/WinRT
声明 Object^ o;
String^ s;
IInspectable o;
hstring s;
字符串类型类别 引用类型 值类型
null HSTRING 项目表示为 (String^)nullptr hstring{}
null 和 "" 相同? 是的 是的
null 的有效性 s = nullptr;
s->Length == 0 (有效)
s = hstring{};
s.size() == 0 (有效)
如果将 null 字符串分配给对象 o = (String^)nullptr;
o == nullptr
o = box_value(hstring{});
o != nullptr
如果将 "" 分配给对象 o = "";
o == nullptr
o = box_value(hstring{L""});
o != nullptr

基本装箱和拆箱。

操作 C++/CX C++/WinRT
将字符串封装 o = s;
空字符串变为 nullptr。
o = box_value(s);
空字符串变为非 null 对象。
解包已知字符串 s = (String^)o;
Null 对象变为空字符串。
如果不是字符串,会引发InvalidCastException。
s = unbox_value<hstring>(o);
Null 对象崩溃。
如果不是字符串,则崩溃。
解包可能的字符串 s = dynamic_cast<String^>(o);
空对象或非字符串类型将转换为空字符串。
s = unbox_value_or<hstring>(o, fallback);
为空或非字符串时,将使用回退值。
保留空字符串。

并发和异步操作

并行模式库(PPL)(例如,concurrency::task)已更新,以支持 C++/CX hat 引用。

对于 C++/WinRT,应改用协同例程和 co_await。 有关更多信息和代码示例,请参阅 使用 C++/WinRT 的并发和异步操作

从 XAML 标记中调用对象

在 C++/CX 项目中,可以访问 XAML 标记中的私有成员和命名元素。 但在 C++/WinRT 中,所有通过 XAML {x:Bind} 标记扩展 使用的实体都必须在 IDL 中公开。

此外,在 C++/CX 中绑定到一个布尔值将显示 truefalse,而在 C++/WinRT 中则显示 Windows.Foundation.IReference`1<Boolean>

有关更多信息和代码示例,请参阅 从标记中使用对象的示例

将C++/CX 平台 类型映射到 C++/WinRT 类型

C++/CX 在 Platform 命名空间中提供了多种数据类型。 这些类型不是标准C++,因此只能在启用 Windows 运行时语言扩展(Visual Studio 项目属性 C/C++>常规>使用 Windows 运行时扩展>是(/ZW)时使用这些类型。 下表帮助你从 平台 类型移植到 C++/WinRT 中的等效项。 完成此作后,由于 C++/WinRT 是标准C++,因此可以关闭该 /ZW 选项。

C++/CX C++/WinRT
平台:敏捷^ winrt::agile_ref
Platform::Array^ 请参阅 端口 Platform::Array^
Platform::Exception^ winrt::hresult_error
Platform::InvalidArgumentException^ winrt::hresult_invalid_argument
Platform::Object^ winrt::Windows::Foundation::IInspectable
Platform::String^ winrt::hstring

移植 Platform::Agile^winrt::agile_ref

Platform::Agile^ 类型在 C++/CX 中表示一个可以从任何线程访问的 Windows 运行时类。 C++/WinRT 等效项 winrt::agile_ref

在 C++/CX 中。

Platform::Agile<Windows::UI::Core::CoreWindow> m_window;

在 C++/WinRT 中。

winrt::agile_ref<Windows::UI::Core::CoreWindow> m_window;

端口 Platform::Array^

如果C++/CX 需要使用数组,C++/WinRT 允许使用任何连续容器。 请参阅 默认构造函数如何影响集合,了解为何 std::vector 是一个不错的选择。

因此,每当在 C++/CX 中有 Platform::Array^ 时,移植选项包括使用初始化列表、std::arraystd::vector。 有关详细信息和代码示例,请参阅 标准初始值设定项列表标准数组和向量

端口 Platform::Exception^winrt::hresult_error

当 Windows 运行时 API 返回非 S_OK 的 HRESULT 时,Platform::Exception^ 类型会在 C++/CX 中产生。 C++/WinRT 等效项 winrt::hresult_error

若要移植到 C++/WinRT,请将使用 Platform::Exception^ 的所有代码更改为使用 winrt::hresult_error

在 C++/CX 中。

catch (Platform::Exception^ ex)

在 C++/WinRT 中。

catch (winrt::hresult_error const& ex)

C++/WinRT 提供这些异常类。

异常类型 基类 HRESULT
winrt::hresult_error call hresult_error::to_abi
winrt::hresult_access_denied winrt::hresult_error E_ACCESSDENIED(访问被拒绝)
winrt::hresult_canceled winrt::hresult_error 错误_已取消
winrt::hresult_changed_state winrt::hresult_error E_CHANGED_STATE
winrt::hresult_class_not_available winrt::hresult_error CLASS_E_CLASSNOTAVAILABLE(类不可用错误)
winrt::hresult_illegal_delegate_assignment winrt::hresult_error 非法委托分配 (E_ILLEGAL_DELEGATE_ASSIGNMENT)
winrt::hresult_illegal_method_call winrt::hresult_error E_ILLEGAL_METHOD_CALL (非法方法调用)
winrt::hresult_illegal_state_change winrt::hresult_error E_ILLEGAL_STATE_CHANGE (非法状态更改)
winrt::hresult_invalid_argument winrt::hresult_error E_INVALIDARG
winrt::hresult_no_interface winrt::hresult_error E_NOINTERFACE
winrt::hresult_not_implemented winrt::hresult_error E_NOTIMPL
winrt::hresult_out_of_bounds winrt::hresult_error E_BOUNDS
winrt::hresult_wrong_thread winrt::hresult_error RPC_E_WRONG_THREAD

请注意,每个类(通过 hresult_error 基类)都提供 一个to_abi 函数,该函数返回错误的 HRESULT,以及一个 消息 函数,该函数返回该 HRESULT 的字符串表示形式。

下面是在 C++/CX 中引发异常的示例。

throw ref new Platform::InvalidArgumentException(L"A valid User is required");

C++/WinRT 中的等效项。

throw winrt::hresult_invalid_argument{ L"A valid User is required" };

Platform::Object^ 移植到 winrt::Windows::Foundation::IInspectable

与所有C++/WinRT 类型一样, winrt::Windows::Foundation::IInspectable 是一种值类型。 下面介绍如何将该类型的变量初始化为 null。

winrt::Windows::Foundation::IInspectable var{ nullptr };

Platform::String^ 迁移到 winrt::hstring

Platform::String^ 等效于 Windows 运行时 HSTRING ABI 类型。 对于 C++/WinRT,相当于 winrt::hstring。 但是,使用 C++/WinRT,您可以使用 C++ 标准库的宽字符串类型(例如 std::wstring)和/或宽字符串字面值来调用 Windows 运行时 API。 有关更多详细信息和代码示例,请参阅 C++/WinRT 中的字符串处理

使用 C++/CX,可以访问 Platform::String::D ata 属性,以 C 样式 const wchar_t* 数组(例如,将字符串传递给 std::wcout)。

auto var{ titleRecord->TitleName->Data() };

若要对 C++/WinRT 执行相同的作,可以使用 hstring::c_str 函数获取以 null 结尾的 C 样式字符串版本,就像可以从 std::wstring 中一样。

auto var{ titleRecord.TitleName().c_str() };

在实现采用或返回字符串的 API 时,通常会更改使用 Platform::String^ 的任何C++/CX 代码,以改用 winrt::hstring

下面是采用字符串的 C++/CX API 的示例。

void LogWrapLine(Platform::String^ str);

对于 C++/WinRT,可以在 MIDL 3.0 中声明该 API,如下所示。

// LogType.idl
void LogWrapLine(String str);

然后,C++/WinRT 工具链将为你生成如下所示的源代码。

void LogWrapLine(winrt::hstring const& str);

ToString()

C++/CX 类型提供 Object::ToString 方法。

int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"2".

C++/WinRT 不直接提供此设施,但你可以转向替代方法。

int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".

C++/WinRT 还支持有限数量的类型 winrt::to_hstring。 需要为任何想要转换为字符串的其他类型添加重载。

语言 将整数转换为字符串 将枚举转化为字符串
C++/CX String^ result = "hello, " + intValue.ToString(); String^ result = "status: " + status.ToString();
C++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

在将枚举转换为字符串的情况下,您需要提供 winrt::to_hstring的实现。

namespace winrt
{
    hstring to_hstring(StatusEnum status)
    {
        switch (status)
        {
        case StatusEnum::Success: return L"Success";
        case StatusEnum::AccessDenied: return L"AccessDenied";
        case StatusEnum::DisabledByPolicy: return L"DisabledByPolicy";
        default: return to_hstring(static_cast<int>(status));
        }
    }
}

这些字符串化通常由数据绑定隐式使用。

<TextBlock>
You have <Run Text="{Binding FlowerCount}"/> flowers.
</TextBlock>
<TextBlock>
Most recent status is <Run Text="{x:Bind LatestOperation.Status}"/>.
</TextBlock>

这些绑定将执行绑定属性 winrt::to_hstring。 对于第二个示例(StatusEnum),您必须提供一个自定义的 winrt::to_hstring重载,否则会遇到编译错误。

构建字符串

C++/CX 和 C++/WinRT 依赖用于生成字符串的标准 std::wstringstream

操作 C++/CX C++/WinRT
附加字符串,并保留空值 stream.print(s->Data(), s->Length); stream << std::wstring_view{ s };
追加字符串,在第一个空值时停止 stream << s->Data(); stream << s.c_str();
提取结果 ws = stream.str(); ws = stream.str();

更多示例

在下面的示例中,ws 是 std::wstring类型 变量。 此外,虽然 C++/CX 可以从 8 位字符串构造 Platform::String ,但 C++/WinRT 不会这样做。

操作 C++/CX C++/WinRT
从文本构造字符串 String^ s = "hello";
String^ s = L"hello";
// winrt::hstring s{ "hello" }; // Doesn't compile
winrt::hstring s{ L"hello" };
std::wstring转换,保留空值 String^ s = ref new String(ws.c_str(),
  (uint32_t)ws.size());
winrt::hstring s{ ws };
s = winrt::hstring(ws);
// s = ws; // Doesn't compile
std::wstring转换,遇到第一个 null 时停止 String^ s = ref new String(ws.c_str()); winrt::hstring s{ ws.c_str() };
s = winrt::hstring(ws.c_str());
// s = ws.c_str(); // Doesn't compile
转换为 std::wstring,保留空值 std::wstring ws{ s->Data(), s->Length };
ws = std::wstring(s>Data(), s->Length);
std::wstring ws{ s };
ws = s;
转换为 std::wstring,在第一个 null 时停止 std::wstring ws{ s->Data() };
ws = s->Data();
std::wstring ws{ s.c_str() };
ws = s.c_str();
将字面值传递给方法 Method("hello");
Method(L"hello");
// Method("hello"); // Doesn't compile
Method(L"hello");
std::wstring 传递给方法 Method(ref new String(ws.c_str(),
  (uint32_t)ws.size()); // Stops on first null
Method(ws);
// param::winrt::hstring accepts std::wstring_view

重要 API