从 C++/CX 移动到 C++/WinRT
本主题是系列文章中的第一篇,介绍如何将 C++/CX 项目中的源代码移植到 C++/WinRT 中的等效项。
如果你的项目还使用 Windows 运行时 C++ 模板库 (WRL) 类型,请参阅从 WRL 迁移到 C++/WinRT。
移植策略
值得注意的是,从 C++/CX 到 C++/WinRT 的移植通常很简单,但从并行模式库 (PPL) 任务迁移到协同程序的情况例外。 其模型不同。 从 PPL 任务到协同程序没有自然的一对一映射,也没有适用于所有情况的机械移植代码的简单方法。 有关移植的这一特定方面的帮助信息,以及在这两个模型之间进行互操作的选项,请参阅实现 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 支持中介绍了执行此操作的步骤。 然后,你就可以从这里逐步进行移植。
另一种选择是使用 C++/WinRT 项目模板之一在 Visual Studio 中创建一个新项目(请参阅 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++/CX 项目添加 C++/WinRT 支持(请参阅采用 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 之间的互操作和实现 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 在内部自动生成此文件,并将其添加到每个公开的和受保护的成员。 在 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/Windows.UI.Xaml.Shapes.h>
如果忘记了头文件,则仍可正常编译,但会出现链接器错误,因为缺少 consume_
类。
参数传递
编写 C++/CX 源代码时,在顶帽 (^) 引用时,你将 C++/CX 类型作为函数参数传递。
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 代码时,可以通过删除顶帽,并将箭头运算符 (->) 更改为点运算符 (.),来完成大量工作。 C++/WinRT 投影类型是值,而不是指针。
IVectorView<User> userList = User::Users();
if (userList != nullptr)
{
for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
...
C++/CX 顶帽引用的默认构造函数会将它初始化为 null。 下面是一个 C++/CX 代码示例,我们在其中创建一个具有正确类型,但是未初始化的变量/字段。 换句话说,它最初不引用 TextBlock;我们打算在以后分配引用。
TextBlock^ textBlock;
class MyClass
{
TextBlock^ textBlock;
};
有关 C++/WinRT 中的等效项,请参阅延迟初始化。
“属性”
C++/CX 语言扩展包括属性概念。 编写 C++/CX 源代码时,你可以像访问字段那样访问属性。 标准 C++ 没有属性概念,因此,在 C++/WinRT 中,你调用获取和设置函数。
在随后的示例中,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 对象的句柄来处理它,通常称为顶帽 (^) 引用。 通过 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 顶帽引用的默认构造函数会将它初始化为 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); |
在 map 中设置值 | 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 示例的详细信息
std::map 的 []
下标运算符的行为如下所示。
- 如果在 map 中发现此键,则返回现有值(可以将其覆盖)的引用。
- 如果未在 map 中发现此键,则在包含此键(在可移动的情况下已经移动)的 map 中创建新条目和默认构造的值,并返回此值(可以随后将其覆盖)的引用。
换言之,[]
运算符始终在 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 关键字来指示你希望将类用作基类。
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); |
如果尝试取消值类型的 null 指针的装箱,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 |
崩溃 |
取消整数的装箱,在为 null 的情况下使用回退;任何其他情况则崩溃 | i = o ? (int)o : fallback; |
i = o ? unbox_value<int>(o) : fallback; |
尽可能取消整数的装箱;在任何其他情况下使用回退 | 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^ ,在这种情况下,其行为类似于字符串 ""
。
行为 | 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); Null 对象或非字符串变为空字符串。 |
s = unbox_value_or<hstring>(o, fallback); Null 或非字符串变为 fallback。 空字符串被保留。 |
并发和异步操作
并行模式库 (PPL)(例如 concurrency::task)已更新,现在支持 C++/CX 顶帽引用。
对于 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<布尔值>。
有关详细信息和代码示例,请参阅使用标记中的对象。
将 C++/CX 平台类型映射到 C++/WinRT 类型
C++/CX 在平台命名空间中提供了多个数据类型。 这些类型不是标准的 C++,因此只能在启用 Windows 运行时语言扩展(Visual Studio 项目属性“C/C++”>“常规”>“使用 Windows 运行时扩展”>“是(/ZW)”)的情况下使用。 下表帮助你从平台类型移植到 C++/WinRT 中的等效项。 完成后,由于 C++/WinRT 是标准 C++,因此你可以关闭 /ZW
选项。
C++/CX | C++/WinRT |
---|---|
Platform::Agile^ | 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
C++/CX 中的 Platform::Agile^ 类型表示可以从任何线程访问的 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::array 或 std::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 | 调用 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,并提供 message 函数,其返回该 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::Data 属性来作为 C 样式 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,但仅限数目有限的一些类型。 对于任何其他需要字符串化的类型,你需要添加重载。
Language | 将整数字符串化 | 将枚举字符串化 |
---|---|---|
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 |
---|---|---|
追加字符串,保留 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 并不这样做。
操作 | 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 进行转换,保留 null | 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,保留 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 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 |