从 C# 移动到 C++/WinRT
提示
如果你之前已阅读过本主题,并带着特定任务回来复习,可以跳转到本主题的基于要执行的任务查找内容部分。
本主题对 C# 项目中的源代码移植到 C++/WinRT 项目中的等效项时所涉及的技术细节进行了全面分类。
有关移植某一通用 Windows 平台 (UWP) 应用示例的案例研究,请参阅对应的主题将 Clipboard 示例从 C# 移植到 C++/WinRT。 可以通过按照该演练操作并在操作时为自己移植示例,来获取移植实践和体验。
如何准备以及预期的结果
案例研究将 Clipboard 示例从 C# 移植到 C++/WinRT 阐释了在将项目迁移到 C++/WinRT 时要做出的软件设计决策类型的示例。 因此,最好是通过深入了解现有代码的工作原理来准备迁移。 这样,可以大致了解应用的功能和代码的结构,然后,你做出的决策就会始终引领你向正确的方向前进。
在所需的移植更改的类型方面,可以将其分组为四个类别。
- 移植语言投影。 Windows 运行时 (WinRT) 已投影到各种编程语言。 其中的每个语言投影均已设计为符合所涉及的编程语言的语言习惯。 对于 C#,某些 Windows 运行时类型被投影为 .NET 类型。 因此,例如,你会将 System.Collections.Generic.IReadOnlyList<T> 转换回 Windows.Foundation.Collections.IVectorView<T>。 此外,在 C# 中,某些 Windows 运行时操作也被投影为方便的 C# 语言功能。 例如,在 C# 中,可以使用
+=
运算符语法来注册事件处理委托。 因此,你将转换语言功能,例如,转换回要执行的基本操作(在本示例中为事件注册)。 - 移植语言语法。 这些更改中的许多更改都是简单的机械转换(将一个符号替换为另一个符号)。 例如,将点 (
.
) 更改为双冒号 (::
)。 - 移植语言过程。 其中的一些可能是简单、重复的更改(例如
myObject.MyProperty
到myObject.MyProperty()
)。 其他移植则需要更深层次的更改(例如,将涉及使用 System.Text.StringBuilder 的过程迁移到涉及使用 std::wostringstream 的过程)。 - 与移植相关且特定于 C++/WinRT 的任务。 Windows 运行时的某些详细信息会在后台由 C# 隐式处理。 这些详细信息在 C++/WinRT 中是显式处理的。 例如,使用
.idl
文件来定义运行时类。
在后续基于任务的索引之后,本主题中其余部分的结构遵循上面的分类。
根据要执行的任务查找内容
任务 | 内容 |
---|---|
创作 Windows 运行时组件 (WRC) | 只能通过 C++ 实现特定的功能(或调用特定的 API)。 可以将该功能编写为 C++/WINRT WRC,然后从 C#(或其他语言的)应用中使用该 WRC。 请参阅使用 C++/WinRT 的 Windows 运行时组件和如果要在 Windows 运行时组件中创作运行时类。 |
移植异步方法 | 最好将 C++/WinRT 运行时类中异步方法的第一行设为 auto lifetime = get_strong(); (请参阅在类成员协同程序中安全访问 this 指针)。从 Task 移植,请参阅异步行为。从 Task<T> 移植,请参阅异步操作。从 async void 移植,请参阅发后不理方法。 |
移植类 | 首先,确定该类是必须为运行时类,还是可以是普通类。 若要帮助你确定这一信息,请参阅使用 C++/WinRT 创作 API的开始部分。 然后,请参阅下面三行。 |
移植运行时类 | 用于在 C++ 应用外部共享功能的类,或在 XAML 数据绑定中使用的类。 请参阅如果要在 Windows 运行时组件中创作运行时类或如果要创作在 XAML UI 中引用的运行时类。 这些链接对此进行了更详细的介绍,但必须在 IDL 中声明运行时类。 如果你的项目已包含 IDL 文件(例如, Project.idl ),建议在该文件中声明任何新的运行时类。 在 IDL 中,声明将在应用外使用或将在 XAML 中使用的任何方法和数据成员。 在更新 IDL 文件后重新生成,在解决方案资源管理器中,选中项目节点,并确保打开“显示所有文件”,查看项目 Generated Files 文件夹中生成的存根文件(.h 和 .cpp )。 将存根文件与项目中已有的文件进行比较,并根据需要添加文件或添加/更新函数签名。 存根文件语法始终是正确的,因此建议使用它来最大程度地减少生成错误。 只要项目中的存根与存根文件中的相匹配,就可以通过移植 C# 代码来实现它们。 |
移植普通类 | 请参阅如果你没有创作运行时类。 |
创作 IDL | Microsoft 接口定义语言 3.0 简介 如果你正在创作要在 XAML UI 中引用的运行时类 使用 XAML 标记中的对象 在 IDL 中定义运行时类 |
移植集合 | 使用 C++/WinRT 的集合 使数据源可供 XAML 标记使用 关联容器 矢量成员访问 |
移植事件 | 作为类成员的事件处理程序委托 撤销事件处理程序委托 |
移植方法 | 从 C# :private async void SampleButton_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) { ... } 到 C++/WinRT .h 文件:fire_and_forget SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&); 到 C++/WinRT .cpp 文件:fire_and_forget OcrFileImage::SampleButton_Tapped(IInspectable const&, RoutedEventArgs const&) {...} |
移植字符串 | C++/WinRT 中的字符串处理 ToString 字符串生成 将字符串装箱和取消装箱 |
类型转换(类型强制转换) | C#:o.ToString() C++/WinRT: to_hstring(static_cast<int>(o)) 另请参阅 ToString。 C#: (Value)o C++/WinRT: unbox_value<Value>(o) 在取消装箱失败时引发。 另请参阅装箱和取消装箱。 C#: o as Value? ?? fallback C++/WinRT: unbox_value_or<Value>(o, fallback) 如果取消装箱失败,则返回回退。 另请参阅装箱和取消装箱。 C#: (Class)o C++/WinRT: o.as<Class>() 在转换失败时引发。 C#: o as Class C++/WinRT: o.try_as<Class>() 如果转换失败,则返回 null。 |
涉及语言投影的更改
类别 | C# | C++/WinRT | 另请参阅 |
---|---|---|---|
非类型化对象 | object 或 System.Object |
Windows::Foundation::IInspectable | 移植 EnableClipboardContentChangedNotifications 方法 |
投影命名空间 | using System; |
using namespace Windows::Foundation; |
|
using System.Collections.Generic; |
using namespace Windows::Foundation::Collections; |
||
集合大小 | collection.Count |
collection.Size() |
移植 BuildClipboardFormatsOutputString 方法 |
典型集合类型 | IList<T>,以及用用于添加元素的“Add”。 | IVector<T>,以及用用于添加元素的“Append”。 如果在任意位置使用 std::vector,则通过 push_back 添加元素 。 | |
只读集合类型 | IReadOnlyList<T> | IVectorView<T> | 移植 BuildClipboardFormatsOutputString 方法 |
作为类成员的事件处理程序委托 | myObject.EventName += Handler; |
token = myObject.EventName({ get_weak(), &Class::Handler }); |
移植 EnableClipboardContentChangedNotifications 方法 |
撤销事件处理程序委托 | myObject.EventName -= Handler; |
myObject.EventName(token); |
移植 EnableClipboardContentChangedNotifications 方法 |
关联容器 | IDictionary<K, V> | IMap<K, V> | |
矢量成员访问 | x = v[i]; v[i] = x; |
x = v.GetAt(i); v.SetAt(i, x); |
注册/撤销事件处理程序
在 C++/WinRT 中,可以使用多个语法选项注册/撤销事件处理程序委托,如在 C++/WinRT 中使用委托处理事件中所述。 另请参阅移植 EnableClipboardContentChangedNotifications 方法。
有时,例如,当事件接收方(处理事件的对象)即将销毁时,你需要撤销事件处理程序,以使事件源(引发事件的对象)不会调用已销毁的对象。 请参阅撤销已注册的委托。 在此类情况下,请为事件处理程序创建一个 event_token 成员变量。 有关示例,请参阅移植 EnableClipboardContentChangedNotifications 方法。
还可以使用 XAML 标记注册事件处理程序。
<Button x:Name="OpenButton" Click="OpenButton_Click" />
在 C# 中,OpenButton_Click 方法可以是专用的,但 XAML 仍然能够将它连接到 OpenButton 引发的 ButtonBase.Click 事件。
在 C++/WinRT 中,OpenButton_Click 方法在你的实现类型中必须是公共的(如果你想要使用 XAML 标记注册该方法)。 如果只在命令性代码中注册事件处理程序,则该事件处理程序不需要是公共的。
namespace winrt::MyProject::implementation
{
struct MyPage : MyPageT<MyPage>
{
void OpenButton_Click(
winrt::Windows:Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
}
};
也可将注册 XAML 页设置为你的实现类型的友元,并将 OpenButton_Click 设置为专用。
namespace winrt::MyProject::implementation
{
struct MyPage : MyPageT<MyPage>
{
private:
friend MyPageT;
void OpenButton_Click(
winrt::Windows:Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
}
};
最后一种方案是你要移植的 C# 项目绑定到标记中的事件处理程序(有关该方案的更多背景信息,请参阅 x:Bind 中的函数)。
<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />
只需将该标记更改为更简单的 Click="OpenButton_Click"
即可。 如果希望,也可将该标记保留原样。 要支持此操作,你只需在 IDL 中声明事件处理程序即可。
void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);
注意
将函数声明为 void
,即使你将它作为发后不理 (Fire and forget) 来实现也是如此。
涉及语言语法的更改
类别 | C# | C++/WinRT | 另请参阅 |
---|---|---|---|
访问修饰符 | public \<member\> |
public: \<member\> |
移植 Button_Click 方法 |
访问数据成员 | this.variable |
this->variable |
|
异步行为 | async Task ... |
IAsyncAction ... |
IAsyncAction 接口,使用 C++/WinRT 执行并发和异步操作 |
异步操作 | async Task<T> ... |
IAsyncOperation<T> ... |
IAsyncOperation 接口,使用 C++/WinRT 执行并发和异步操作 |
“发后不理”方法(意味着异步) | async void ... |
winrt::fire_and_forget ... |
移植 CopyButton_Click 方法,发后不理 |
访问枚举常量 | E.Value |
E::Value |
移植 DisplayChangedFormats 方法 |
配合地等待 | await ... |
co_await ... |
移植 CopyButton_Click 方法 |
作为私有字段的投影类型集合 | private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); |
std::vector <MyNamespace::MyRuntimeClass> m_myRuntimeClasses; |
|
GUID 构造 | private static readonly Guid myGuid = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); |
winrt::guid myGuid{ 0xC380465D, 0x2271, 0x428C, { 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1} }; |
|
命名空间分隔符 | A.B.T |
A::B::T |
|
Null | null |
nullptr |
移植 UpdateStatus 方法 |
获取类型对象 | typeof(MyType) |
winrt::xaml_typename<MyType>() |
移植 Scenarios 属性 |
方法的参数声明 | MyType |
MyType const& |
参数传递 |
异步方法的参数声明 | MyType |
MyType |
参数传递 |
调用静态方法 | T.Method() |
T::Method() |
|
字符串 | string 或 System.String |
winrt::hstring | C++/WinRT 中的字符串处理 |
字符串文本 | "a string literal" |
L"a string literal" |
移植构造函数 Current 和 FEATURE_NAME |
推断(或推导)的类型 | var |
auto |
移植 BuildClipboardFormatsOutputString 方法 |
Using 指令 | using A.B.C; |
using namespace A::B::C; |
移植构造函数 Current 和 FEATURE_NAME |
原义/原始字符串文本 | @"verbatim string literal" |
LR"(raw string literal)" |
移植 DisplayToast 方法 |
注意
如果头文件没有包含用于给定命名空间的 using namespace
指令,则必须完全限定该命名空间的所有类型名称;或者至少对它们进行充分限定,以使编译器可以找到它们。 有关示例,请参阅移植 DisplayToast 方法。
移植类和成员
对于每种 C# 类型,都需要确定是将它移植到 Windows 运行时类型,还是移植到常规 C++ 类/结构/枚举。 有关详细信息以及演示如何做出这些决策的详细示例,请参阅将 Clipboard 示例从 C# 移植到 C++/WinRT。
一个 C# 属性通常会成为一个访问器函数、一个赋值函数和一个支持数据成员。 有关详细信息和示例,请参阅移植 IsClipboardContentChangedEnabled 属性。
对于非静态字段,请使其成为你的实现类型的数据成员。
C# 静态字段会成为 C++/WinRT 静态访问器和/或赋值函数。 有关详细信息和示例,请参阅移植构造函数 Current 和 FEATURE_NAME。
对于成员函数,你也需要为每个成员函数决定其是否属于 IDL,或者它是实现类型的公共成员函数还是私有成员函数。 有关详细信息以及如何决定的示例,请参阅 MainPage 类型的 IDL。
移植 XAML 标记和资产文件
在将 Clipboard 示例从 C# 移植到 C++/WinRT的案例中,我们能够在 C# 和 C++/WINRT 项目中使用相同的 XAML 标记(包括资源)和资产文件。 在某些情况下,需要编辑标记才能实现此目的。 请参阅复制完成移植 MainPage 所需的 XAML 和样式。
涉及语言内过程的更改
类别 | C# | C++/WinRT | 另请参阅 |
---|---|---|---|
异步方法中的生存期管理 | N/A | auto lifetime{ get_strong() }; 或auto lifetime = get_strong(); |
移植 CopyButton_Click 方法 |
处置 | using (var t = v) |
auto t{ v }; t.Close(); // or let wrapper destructor do the work |
移植 CopyImage 方法 |
构造对象 | new MyType(args) |
MyType{ args } 或MyType(args) |
移植 Scenarios 属性 |
创建未初始化的引用 | MyType myObject; |
MyType myObject{ nullptr }; 或MyType myObject = nullptr; |
移植构造函数 Current 和 FEATURE_NAME |
使用参数将对象构造到变量中 | var myObject = new MyType(args); |
auto myObject{ MyType{ args } }; 或 auto myObject{ MyType(args) }; 或 auto myObject = MyType{ args }; 或 auto myObject = MyType(args); 或 MyType myObject{ args }; 或 MyType myObject(args); |
移植 Footer_Click 方法 |
在不使用参数的情况下将对象构造到变量中 | var myObject = new T(); |
MyType myObject; |
移植 BuildClipboardFormatsOutputString 方法 |
对象初始化速记 | var p = new FileOpenPicker{ ViewMode = PickerViewMode.List }; |
FileOpenPicker p; p.ViewMode(PickerViewMode::List); |
|
批量矢量操作 | var p = new FileOpenPicker{ FileTypeFilter = { ".png", ".jpg", ".gif" } }; |
FileOpenPicker p; p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" }); |
移植 CopyButton_Click 方法 |
循环访问集合 | foreach (var v in c) |
for (auto&& v : c) |
移植 BuildClipboardFormatsOutputString 方法 |
捕获异常 | catch (Exception ex) |
catch (winrt::hresult_error const& ex) |
移植 PasteButton_Click 方法 |
异常详细信息 | ex.Message |
ex.message() |
移植 PasteButton_Click 方法 |
获取属性值 | myObject.MyProperty |
myObject.MyProperty() |
移植 NotifyUser 方法 |
设置属性值 | myObject.MyProperty = value; |
myObject.MyProperty(value); |
|
递增属性值 | myObject.MyProperty += v; |
myObject.MyProperty(thing.Property() + v); 对于字符串,请切换到生成器 |
|
ToString() | myObject.ToString() |
winrt::to_hstring(myObject) |
ToString() |
语言字符串到 Windows 运行时字符串 | N/A | winrt::hstring{ s } |
|
字符串生成 | StringBuilder builder; builder.Append(...); |
std::wostringstream builder; builder << ...; |
字符串生成 |
字符串内插 | $"{i++}) {s.Title}" |
winrt::to_hstring 和/或 winrt::hstring::operator+ | 移植 OnNavigatedTo 方法 |
用于比较的空字符串 | System.String.Empty | winrt::hstring::empty | 移植 UpdateStatus 方法 |
创建空字符串 | var myEmptyString = String.Empty; |
winrt::hstring myEmptyString{ L"" }; |
|
字典操作 | map[k] = v; // replaces any existing v = map[k]; // throws if not present map.ContainsKey(k) |
map.Insert(k, v); // replaces any existing v = map.Lookup(k); // throws if not present map.HasKey(k) |
|
类型转换(失败时引发) | (MyType)v |
v.as<MyType>() |
移植 Footer_Click 方法 |
类型转换(失败时为 null) | v as MyType |
v.try_as<MyType>() |
移植 PasteButton_Click 方法 |
具有 x:Name 的 XAML 元素是属性 | MyNamedElement |
MyNamedElement() |
移植构造函数 Current 和 FEATURE_NAME |
切换到 UI 线程 | CoreDispatcher.RunAsync | CoreDispatcher.RunAsync 或 winrt::resume_foreground | 移植 NotifyUser 方法以及移植 HistoryAndRoaming 方法 |
XAML 页的强制性代码中的 UI 元素构造 | 请参阅 UI 元素构造 | 请参阅 UI 元素构造 |
以下部分更详细地介绍了该表中的某些项。
UI 元素构造
这些代码示例演示了 XAML 页面的强制性代码中的 UI 元素构造。
var myTextBlock = new TextBlock()
{
Text = "Text",
Style = (Windows.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
winrt::unbox_value<Windows::UI::Xaml::Style>(
Resources().Lookup(
winrt::box_value(L"MyTextBlockStyle")
)
)
);
ToString()
C# 类型提供 Object.ToString 方法。
int i = 2;
var s = i.ToString(); // s is a System.String with value "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# | string result = "hello, " + intValue.ToString(); string result = $"hello, {intValue}"; |
string result = "status: " + status.ToString(); string result = $"status: {status}"; |
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 重载,否则会出现编译器错误。
另请参阅移植 Footer_Click 方法。
字符串生成
C# 有一个内置的 StringBuilder 类型,用于字符串生成。
类别 | C# | C++/WinRT |
---|---|---|
字符串生成 | StringBuilder builder; builder.Append(...); |
std::wostringstream builder; builder << ...; |
追加 Windows 运行时字符串(保留 null 值) | builder.Append(s); |
builder << std::wstring_view{ s }; |
添加新行 | builder.Append(Environment.NewLine); |
builder << std::endl; |
访问结果 | s = builder.ToString(); |
ws = builder.str(); |
另请参阅移植 BuildClipboardFormatsOutputString 方法和移植 DisplayChangedFormats 方法。
在主 UI 线程上运行代码
此示例取自条形码扫描仪示例。
要在 C# 项目的主 UI 线程上工作时,通常使用 CoreDispatcher.RunAsync 方法,如下所示。
private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// Do work on the main UI thread here.
});
}
更简单的是用 C++/WinRT 来表示。 请注意,假设我们希望在第一个暂停点(在本例中是 co_await
)之后访问参数时,我们将按值接受参数。 有关详细信息,请参阅参数传递。
winrt::fire_and_forget Watcher_Added(DeviceWatcher sender, winrt::DeviceInformation args)
{
co_await Dispatcher();
// Do work on the main UI thread here.
}
如果需要以默认优先级以外的优先级执行工作,请参阅 winrt::resume_foreground 函数,该函数包含需要优先处理的重载。 有关演示如何等待调用 winrt::resume_foreground 的代码示例,请参阅编程时仔细考虑线程相关性。
与移植相关且特定于 C++/WinRT 的任务
在 IDL 中定义运行时类
请参阅 MainPage 类型的 IDL 和合并 .idl
文件。
包含所需的 C++/WinRT Windows 命名空间头文件
在 C++/WinRT 中,只要你希望使用来自 Windows 命名空间的类型,都需要包含对应的 C++/WinRT Windows 命名空间头文件。 有关示例,请参阅移植 NotifyUser 方法。
装箱和取消装箱
C# 自动将标量装箱到对象中。 C++/WinRT 要求你显式调用 winrt::box_value 函数。 两种语言都要求你以显式方式取消装箱。 请参阅使用 C++/WinRT 装箱和取消装箱。
在下面的表中,我们将使用这些定义。
C# | C++/WinRT |
---|---|
int i; |
int i; |
string s; |
winrt::hstring s; |
object o; |
IInspectable o; |
操作 | C# | 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# | C++/WinRT |
---|---|---|
取消已知整数的装箱 | i = (int)o; |
i = unbox_value<int>(o); |
如果 o 为 null | System.NullReferenceException |
崩溃 |
如果 o 不是装箱的整数 | System.InvalidCastException |
崩溃 |
取消整数的装箱,在为 null 的情况下使用回退;任何其他情况则崩溃 | i = o != null ? (int)o : fallback; |
i = o ? unbox_value<int>(o) : fallback; |
尽可能取消整数的装箱;在任何其他情况下使用回退 | i = as int? ?? fallback; |
i = unbox_value_or<int>(o, fallback); |
有关示例,请参阅移植 OnNavigatedTo 方法和移植 Footer_Click 方法。
将字符串装箱和取消装箱
字符串在某些情况下是值类型,在另一些情况下是引用类型。 C# 和 C++/WinRT 对待字符串的方式有所不同。
ABI 类型 HSTRING 是一个指向引用计数字符串的指针。 但是,它并非派生自 IInspectable,因此从技术上来说它不是一个对象。 另外,null HSTRING 表示空字符串。 将并非派生自 IInspectable 的项装箱时,需将其包装到 IReference<T> 中,而 Windows 运行时会以 PropertyValue 对象的形式提供标准实现(自定义类型以 PropertyType::OtherType 形式报告)。
C# 将 Windows 运行时字符串表示为引用类型,而 C++/WinRT 则将字符串投影为值类型。 这意味着装箱的 null 字符串可能有不同的表示形式,具体取决于你所采用的方法。
行为 | C# | C++/WinRT |
---|---|---|
声明 | object o; string s; |
IInspectable o; hstring s; |
字符串类型类别 | 引用类型 | 值类型 |
null HSTRING 投影方式 | "" |
hstring{} |
null 和 "" 是否相同? |
否 | 是 |
null 的有效性 | s = null; s.Length 引发 NullReferenceException |
s = hstring{}; s.size() == 0 (有效) |
如果将 null 字符串分配给对象 | o = (string)null; o == null |
o = box_value(hstring{}); o != nullptr |
如果将 "" 分配给对象 |
o = ""; o != null |
o = box_value(hstring{L""}); o != nullptr |
基本装箱和取消装箱。
操作 | C# | C++/WinRT |
---|---|---|
将字符串装箱 | o = s; 空字符串变为非 null 对象。 |
o = box_value(s); 空字符串变为非 null 对象。 |
取消已知字符串的装箱 | s = (string)o; Null 对象变为 null 字符串。 如果不是字符串,则引发 InvalidCastException。 |
s = unbox_value<hstring>(o); Null 对象崩溃。 如果不是字符串,则崩溃。 |
将可能的字符串取消装箱 | s = o as string; Null 对象或非字符串变为 null 字符串。 或者 s = o as string ?? fallback; Null 或非字符串变为 fallback。 空字符串被保留。 |
s = unbox_value_or<hstring>(o, fallback); Null 或非字符串变为 fallback。 空字符串被保留。 |
使类可供 {Binding} 标记扩展使用
若要使用 {Binding} 标记扩展将数据绑定到数据类型,则请参阅使用 {Binding} 绑定声明的对象。
使用 XAML 标记中的对象
在 C# 项目中,可以使用 XAML 标记中的专用成员和命名元素。 但在 C++/WinRT 中,以 XAML {x:Bind} 标记扩展形式使用的所有实体必须在 IDL 中以公开方式公开。
另外,绑定到布尔值时,在 C# 中会显示 true
或 false
,但在 C++/WinRT 中会显示 Windows.Foundation.IReference`1<布尔值>。
有关详细信息和代码示例,请参阅使用标记中的对象。
使数据源可供 XAML 标记使用
在 C++/WinRT 2.0.190530.8 或更高版本中,winrt::single_threaded_observable_vector 创建一个可观测的支持 IObservableVector<T> 和 IObservableVector<IInspectable> 的矢量。 有关示例,请参阅 移植 Scenarios 属性。
你可以创作 Midl 文件 (.idl) ,如下所示(另请参阅将运行时类重构到 Midl 文件 (.idl) 中)。
namespace Bookstore
{
runtimeclass BookSku { ... }
runtimeclass BookstoreViewModel
{
Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
}
runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
{
MainPage();
BookstoreViewModel MainViewModel{ get; };
}
}
实现方式如下。
// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
BookstoreViewModel()
{
m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
}
Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
{
return m_bookSkus;
}
private:
Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...
有关详细信息,请参阅 XAML 项目控件;绑定到 C++/WinRT 集合和使用 C++/WinRT 的集合。
使数据源可供 XAML 标记使用(在 C++/WinRT 2.0.190530.8 之前)
XAML 数据绑定要求项源实现 IIterable<IInspectable> 以及下述接口组合之一。
- IObservableVector<IInspectable>
- IBindableVector 和 INotifyCollectionChanged
- IBindableVector 和 IBindableObservableVector
- IBindableVector 本身(不会响应更改)
- IVector<IInspectable>
- IBindableIterable(会通过迭代将元素保存到专用集合中)
无法在运行时检测 IVector<T> 之类的泛型接口。 每个 IVector<T> 都有不同的接口标识符 (IID),该标识符是 T 的函数。任何开发人员都可以随意扩展 T 集,因此,很明显 XAML 绑定代码不可能知道要查询的完整集。 该限制对 C# 来说不是问题,因为每个实现 IEnumerable<T> 的 CLR 对象都会自动实现 IEnumerable。 在 ABI 级别,这意味着每个实现 IObservableVector<T> 的对象都会自动实现 IObservableVector<IInspectable>。
C++/WinRT 不提供该保证。 如果 C++/WinRT 运行时类实现 IObservableVector<T>,则我们不能假定也会通过某种方式提供 IObservableVector<IInspectable> 的实现。
因此,上一示例应如下所示。
...
runtimeclass BookstoreViewModel
{
// This is really an observable vector of BookSku.
Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}
实现如下。
// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
BookstoreViewModel()
{
m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
}
// This is really an observable vector of BookSku.
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
{
return m_bookSkus;
}
private:
Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...
如需访问 m_bookSkus 中的对象,则需将其 QI 回 Bookstore::BookSku。
Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
for (auto&& obj : m_bookSkus)
{
auto bookSku = obj.as<Bookstore::BookSku>();
if (bookSku.Title() == title) return bookSku;
}
return nullptr;
}
派生类
若要从运行时类派生,基类必须是可组合类。 C# 不需要你执行任何特殊步骤即可将类变为可组合类,但 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>
{
...
}
}