本主题是系列中的第一个部分,介绍如何将 C++/CX 项目中的源代码移植到 C ++/WinRT 中的等效代码。
如果项目还使用 Windows 运行时 C++ 模板库 (WRL) 类型,请参阅从 WRL 移动到 C++/WinRT。
移植策略
值得注意的是,从 C++/CX 移植到 C++/WinRT 通常比较直接,唯一的例外是从 并行模式库(PPL)任务迁移到协程。 模型不同。 没有从 PPL 任务到协同例程的自然一对一映射,也没有简单的方法来机械移植适用于所有情况的代码。 有关移植中这一特定方面的帮助,以及这两种模型之间实现互操作的可选方法,请参阅 异步,以及 C++/WinRT 与 C++/CX 之间的互操作。
开发团队经常报告,一旦他们超过移植异步代码的障碍,移植工作的其余部分基本上是机械的。
在一个通道中移植
如果你可以一次性移植整个项目,那么只需阅读本主题即可了解所需信息(并且无需阅读本主题之后的 互操作 主题)。 建议首先使用 C++/WinRT 项目模板之一在 Visual Studio中创建新项目(请参阅 C++/WinRT Visual Studio支持)。 然后,将源代码文件移到该新项目中,并像这样将所有 C++/CX 源代码移植到 C++/WinRT。
或者,如果想要在现有 C++/CX 项目中执行移植工作,则需要向其添加 C++/WinRT 支持。 执行该操作所需遵循的步骤在 将 C++/WinRT 支持添加到 C++/CX 项目中 一文中有说明。 完成移植后,你将将纯 C++/CX 项目转换为纯 C++/WinRT 项目。
注释
如果你有一个 Windows 运行时 组件项目,那么一次性完成迁移是你唯一的选择。 用 C++ 编写的Windows 运行时组件项目必须包含所有 C++/CX 源代码或所有 C++/WinRT 源代码。 它们不能在此项目类型中共存。
逐步进行项目移植
除了Windows 运行时组件项目之外,如上一部分所述,如果代码库的大小或复杂性使得需要逐步移植项目,则需要一个移植过程,在该过程中,C++/CX 和 C++/WinRT 代码在同一项目中并排存在。 除了阅读本主题,还请参阅 C++/WinRT 与 C++/CX 与Asynchrony 之间的互操作,以及 C++/WinRT 和 C++/CX 之间的互操作。 这些主题提供信息和代码示例,演示如何在两种语言投影之间进行互操作。
若要使项目准备好进行逐步移植过程,一个选项是向 C++/CX 项目添加 C++/WinRT 支持。 执行该操作所需遵循的步骤在 将 C++/WinRT 支持添加到 C++/CX 项目中 一文中有说明。 然后,你就可以在此基础上逐步移植了。
另一种选择是使用 C++/WinRT 项目模板之一在Visual Studio中创建新项目(请参阅Visual Studio C++/WinRT 支持)。 然后向该项目添加 C++/CX 支持。 你为此遵循的步骤在 将 C++/CX 支持添加到 C++/WinRT 项目中 中有说明。 然后,您就可以开始将源代码迁移到那里,并在此过程中将 部分 C++/CX 源代码移植到 C++/WinRT。
无论哪种情况,你都将能够在你的 C++/WinRT 代码与任何尚未移植的 C++/CX 代码之间进行互操作(双向)。
注释
C++/CX 和 Windows SDK 都声明根命名空间中的类型Windows。 投影到 C++/WinRT 的 Windows 类型与该 Windows 类型具有相同的完全限定名,但它位于 C++ winrt 命名空间中。 通过这些不同的命名空间,你可以按照自己的节奏从 C++/CX 移植到 C++/WinRT。
逐步移植 XAML 项目
Important
对于使用 XAML 的项目,在任何给定时间,所有 XAML 页面类型都需要完全为 C++/CX 或完全 C++/WinRT。 你仍然可以在同一项目中,在 XAML 页面类型之外混合使用 C++/CX 和 C++/WinRT(包括在模型、视图模型和其他位置中)。
对于此方案,我们建议的工作流是创建新的 C++/WinRT 项目,并从 C++/CX 项目复制源代码和标记。 只要所有 XAML 页面类型都是 C++/WinRT,就可以使用 Project>Add New Item 添加新的 XAML 页面...>Visual C++>空白页 (C++/WinRT) 。
或者,你也可以在移植 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++/CX 与Asynchrony 之间的互操作,以及 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 会从 *.h 和 *.cpp 文件名中移除 .xaml。
C++/WinRT 添加了一个附加的开发人员文件 Midl 文件 (.idl)。 C++/CX 在内部自动生成该文件,并将每个 public 和 protected 成员添加到其中。 在 C++/WinRT 中,可以自行添加和创作文件。 有关更多详细信息、代码示例和创作 IDL 的演练,请参阅 XAML 控件;绑定到 C++/WinRT 属性。
运行时类
C++/CX 不会对头文件的名称施加限制;通常将多个运行时类定义放入单个头文件中,尤其是对于小型类。 但 C++/WinRT 要求每个运行时类都有自己的标头文件,该文件以类名命名。
| C++/CX | C++/WinRT |
|---|---|
Common.href class A { ... }ref class B { ... } |
Common.idlruntimeclass A { ... }runtimeclass B { ... } |
A.hnamespace implements {struct A { ... };} |
|
B.hnamespace implements {struct B { ... };} |
C++/CX 中不太常见(但仍合法)是对 XAML 自定义控件使用不同命名的头文件。 需要重命名这些头文件以匹配类名。
| C++/CX | C++/WinRT |
|---|---|
A.xaml<Page x:Class="LongNameForA" ...> |
A.xaml<Page x:Class="LongNameForA" ...> |
A.hpartial ref class LongNameForA { ... } |
LongNameForA.hnamespace 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 类意味着必须添加此 include。
// MainPage.h
#include <winrt/Microsoft.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 源代码时,可以使用插入符号 (^) 变量来引用 Windows 运行时对象,并使用箭头 (->) 运算符对插入符号变量进行解引用。
IVectorView<User^>^ userList = User::Users;
if (userList != nullptr)
{
for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
...
在移植到等效的 C++/WinRT 代码时,只需去掉 hat 符号,并将箭头运算符(->)改为点运算符(.),就能完成大部分工作。 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 函数。
在以下示例中, XboxUserId、 UserState、 PresenceDeviceRecords 和 Size 都是属性。
从属性检索值
下面介绍如何在 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++ 集合类型使用默认构造函数,这可能会导致意外的对象构造。
| Scenario | 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(Microsoft::UI::Xaml::DependencyObject^ d, Microsoft::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(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::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 关键字 来表明你希望类可以用作基类。
unsealed runtimeclass BasePage : Microsoft.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; |
| Operation | 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 函数。
| Scenario | C++/CX | C++/WinRT |
|---|---|---|
| 对已知整数进行拆箱 | i = (int)o; |
i = unbox_value<int>(o); |
| 如果 o 为 null | Platform::NullReferenceException |
崩溃 |
| 如果 o 不是装箱的 int | 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 派生的,因此从技术上来说,它不是一个 对象。 此外,null HSTRING 表示空字符串。 对于并非派生自 IInspectable 的对象,其装箱是通过将其包装在 IReference<T> 中来实现的,而 Windows 运行时 则以 PropertyValue 对象的形式提供了标准实现(自定义类型会被标记为 PropertyType::OtherType)。
C++/CX 将Windows 运行时字符串表示为引用类型;而 C++/WinRT 将字符串投影为值类型。 这意味着,装箱后的 null 字符串可能会有不同的表示形式,具体取决于你是通过哪种方式得到它的。
此外,C++/CX 允许取消引用 null String^,在这种情况下,它的行为与字符串 ""类似。
| Behavior | C++/CX | C++/WinRT |
|---|---|---|
| 声明 | Object^ o;String^ s; |
IInspectable o;hstring s; |
| 字符串类型类别 | 引用类型 | 值类型 |
| null HSTRING 映射为 | (String^)nullptr |
hstring{} |
null 和 "" 相同? |
Yes | Yes |
| 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 |
基本装箱和拆箱。
| Operation | C++/CX | C++/WinRT |
|---|---|---|
| 将字符串装箱 | o = s;空字符串变为 nullptr。 |
o = box_value(s);空字符串变为非 null 对象。 |
| 对已知字符串进行拆箱 | s = (String^)o;Null 对象变为空字符串。 如果不是字符串类型,则会引发 InvalidCastException。 |
s = unbox_value<hstring>(o);空对象崩溃。 如果不是字符串,则崩溃。 |
| 取消装箱可能的字符串 | s = dynamic_cast<String^>(o);null 对象或非字符串值会变为空字符串。 |
s = unbox_value_or<hstring>(o, fallback);Null 或非字符串变为回退。 保留空字符串。 |
并发和异步操作
并行模式库 (PPL)(例如 concurrency::task)已更新,以支持 C++/CX hat 引用。
对于 C++/WinRT,你应该改用协程和 co_await。 有关详细信息和代码示例,请参阅 使用 C++/WinRT 的并发和异步操作。
在 XAML 标记中使用对象
在 C++/CX 项目中,可以在 XAML 标记中访问私有成员和已命名元素。 但在 C++/WinRT 中,使用 XAML {x:Bind} 标记扩展 使用的所有实体都必须在 IDL 中公开。
此外,绑定到布尔值时,在 C++/CX 中会显示 true 或 false,而在 C++/WinRT 中则会显示 Windows.Foundation.IReference`1<Boolean>。
有关更多信息和代码示例,请参阅 从标记中使用对象。
将 C++/CX 平台 类型映射到 C++/WinRT 类型
C++/CX 在 Platform 命名空间中提供了多种数据类型。 这些类型不是标准 C++,因此只能在启用Windows 运行时语言扩展时使用它们(Visual Studio项目属性 C/C++>General>Use Windows 运行时 Extension>Yes (/ZW))。 下表帮助你从 平台 类型移植到 C++/WinRT 中的等效项。 完成此操作后,由于 C++/WinRT 是标准 C++,因此可以关闭该 /ZW 选项。
| C++/CX | C++/WinRT |
|---|---|
| Platform::Agile^ | winrt::agile_ref |
| Platform::Array^ | 请参阅 Port 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
C++/CX 中的 Platform::Agile^ 类型表示可从任何线程访问的Windows 运行时类。 C++/WinRT 等效项为 winrt::agile_ref。
在 C++/CX 中。
Platform::Agile<Windows::UI::Core::CoreWindow> m_window;
在 C++/WinRT 中(WinUI 3 使用 Microsoft::UI::Xaml::Window 而不是 CoreWindow)。
winrt::agile_ref<Microsoft::UI::Xaml::Window> m_window;
端口 平台::Array^
如果 C++/CX 要求使用数组,C++/WinRT 允许使用任何连续容器。 有关为什么 std::vector 是一个不错的选择,请参阅 默认构造函数如何影响集合。
因此,每当在 C++/CX 中有 Platform::Array^ 时,移植选项包括使用初始值设定项列表、 std::array 或 std::vector。 有关详细信息和代码示例,请参阅 标准初始值设定项列表 和 标准数组和向量。
将 Platform::Exception^ 迁移到 winrt::hresult_error
当 Windows 运行时 API 返回非S_OK HRESULT 时,在 C++/CX 中生成 Platform::Exception^ 类型。 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 | ERROR_CANCELLED |
| 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^ 改用 winrt::hstring 的任何 C++/CX 代码。
下面是采用字符串的 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。 你需要为任何想要转换为字符串的其他类型添加重载。
| 语言 | 将 int 转换为字符串 | 将枚举转换为字符串 |
|---|---|---|
| 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 进行字符串生成。
| Operation | C++/CX | C++/WinRT |
|---|---|---|
| 追加字符串,保留 null | stream.print(s->Data(), s->Length); |
stream << std::wstring_view{ s }; |
| 追加字符串,遇到第一个 null 时停止 | stream << s->Data(); |
stream << s.c_str(); |
| 提取结果 | ws = stream.str(); |
ws = stream.str(); |
更多示例
在下面的示例中, ws 是 std::wstring 类型的变量。 此外,虽然 C++/CX 可以从 8 位字符串构造 Platform::String ,但 C++/WinRT 不会这样做。
| Operation | C++/CX | C++/WinRT |
|---|---|---|
| 从文本构造字符串 | String^ s = "hello";String^ s = L"hello"; |
// winrt::hstring s{ "hello" }; // Doesn't compilewinrt::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 转换,遇到第一个空字符时停止 | 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,保留 null | 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 compileMethod(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
相关主题
注释
许多 C++/WinRT 主题正在从 UWP 文档迁移到本部分。 在迁移完成前,以下列表中的链接可能会指向 UWP 文档部分。 对于 UWP 和 WinUI 3 应用,C++/WinRT 语言投影是相同的,因此内容适用于这两种上下文。 这些文章中明确指出了任何特定于 UWP 的模式(如应用生命周期或 Windows.UI 命名空间 API)。