小窍门
如果你之前读过本主题,并且现在是带着某个特定任务返回查看,那么你可以直接跳到本主题中的 根据你正在执行的任务查找内容 部分。
本主题全面编录了 将 C# 项目中的源代码移植到其等效 C ++/WinRT 中所涉及的技术详细信息。
有关移植其中一个通用 Windows 平台 (UWP)应用示例的案例研究,请参阅从 C# 将剪贴板示例移植到 C++/WinRT 的配套主题。 你可以通过跟着该演练指南一步步操作,并在过程中亲自移植该示例,来获得移植方面的实践和经验。
如何准备,以及预期内容
案例研究 将剪贴板示例从 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.MyPropertymyObject.MyProperty())。 其他人需要更深入的更改(例如,将涉及 使用 System.Text.StringBuilder 的过程移植到涉及 使用 std::wostringstream 的过程)。 -
C++/WinRT 特有的移植相关任务 Windows 运行时的某些细节在后台由 C# 隐式处理。 这些详细信息在 C++/WinRT 中显式完成。 例如,使用
.idl文件定义运行时类。
在后面的基于任务的索引之后,本主题的其余部分根据上述分类进行结构化。
根据所执行的任务查找内容
| 任务 | Content |
|---|---|
| 编写 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中移植,请参阅 Fire-and-forget 方法。 |
| 移植一个类 | 首先,确定类是否需要为运行时类,或者它是否可以是普通类。 要帮助你作出这一判断,请参阅 使用 C++/WinRT 编写 API 的最开头。 然后,请参阅下面的三行。 |
| 移植运行时类 | 在 C++ 应用之外共享功能的类,或 XAML 数据绑定中使用的类。 请参阅如果你正在 Windows 运行时 组件中编写运行时类,或如果你正在编写供 XAML UI 引用的运行时类。 这些链接更详细地描述了这一点,但必须在 IDL 中声明运行时类。 如果你的项目已包含 IDL 文件(例如, Project.idl),那么我们建议在该文件中声明任何新的运行时类。 在 IDL 中,声明任何将在应用外部使用或将在 XAML 中使用的方法和数据成员。 更新 IDL 文件后,重新生成并查看项目.h文件夹中生成的存根文件(.cpp以及Generated Files)(在解决方案资源管理器中,选中项目节点后,请确保打开“显示所有文件”)。 将存根文件与项目中已有的文件进行比较,根据需要添加文件或添加/更新函数签名。 存根文件语法始终正确,因此我们建议使用它来最大程度地减少生成错误。 项目存根与存根文件中的存根匹配后,可以通过移植 C# 代码来实现它们。 |
| 移植普通类 | 查看 是否 未 创作运行时类。 |
| 作者 IDL |
Microsoft接口定义语言 3.0 简介 如果要在 XAML UI 中编写要引用的运行时类 在 XAML 标记中使用对象 在 IDL 中定义运行时类 |
| 移植集合项 |
使用 C++/WinRT 的集合 使数据源可用于 XAML 标记 关联容器 向量成员访问 |
| 迁移事件 |
事件处理程序委托作为类成员 撤销事件处理程序委托 |
| 移植一个方法 | 从 C# : private async void SampleButton_Tapped(object sender, Microsoft.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)oC++/WinRT: unbox_value<Value>(o)如果拆箱失败,则抛出异常。 另请参阅 装箱和拆箱。 C#: o as Value? ?? fallbackC++/WinRT: unbox_value_or<Value>(o, fallback)如果拆箱失败,则返回回退值。 另请参阅 装箱和拆箱。 C#: (Class)oC++/WinRT: o.as<Class>()如果转换失败,将引发。 C#: o as ClassC++/WinRT: o.try_as<Class>()如果转换失败,则返回 null。 |
涉及语言映射的更改
| Category | 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 中,如果要在 XAML 标记中注册 OpenButton_Click 方法,则它在你的 实现类型 中必须为 public。 如果仅在命令性代码中注册事件处理程序,则事件处理程序不需要是公共的。
namespace winrt::MyProject::implementation
{
struct MyPage : MyPageT<MyPage>
{
void OpenButton_Click(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Microsoft::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::Microsoft::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, Microsoft.UI.Xaml.RoutedEventArgs e);
注释
将函数声明为 void 即使将函数 实现 为 Fire 并忘记。
涉及语言语法的更改
| Category | 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 的并发和异步操作 |
| Fire-and-forget 方法(表示异步) | 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" |
移植构造函数、 当前和 FEATURE_NAME |
| 推断(或推导得出)类型 | var |
auto |
移植 BuildClipboardFormatsOutputString 方法 |
| Using-directive | using A.B.C; |
using namespace A::B::C; |
移植构造函数、 当前和 FEATURE_NAME |
| 逐字/原始字符串文本 | @"verbatim string literal" |
LR"(raw string literal)" |
移植 DisplayToast 方法 |
注释
如果头文件不包含 using namespace 给定命名空间的指令,则必须完全限定该命名空间的所有类型名称;或者至少对它们进行足够限定,以便编译器找到它们。 有关示例,请参阅 移植 DisplayToast 方法。
移植类和成员
你需要决定,对于每个 C# 类型,是将其移植为 Windows 运行时 类型,还是移植为普通的 C++ 类、结构或枚举。 有关详细信息,以及演示如何做出这些决策的详细示例,请参阅 将剪贴板示例从 C# 移植到 C++/WinRT。
C# 属性通常会转换为访问器函数、修改器函数和后备数据成员。 有关详细信息和示例,请参阅 移植 IsClipboardContentChangedEnabled 属性。
对于非静态字段,将它们设为 实现类型的数据成员。
C# 静态字段会变为 C++/WinRT 中的静态访问器和/或设值器函数。 有关详细信息和示例,请参阅 移植构造函数、 当前和 FEATURE_NAME。
对于成员函数,同样,你需要对每一个成员函数判断它是否应在 IDL 中定义,还是应作为你的实现类型的公有成员函数或私有成员函数。 有关详细信息以及如何决定的示例,请参阅 MainPage 类型的 IDL。
移植 XAML 标记和资源文件
在将 剪贴板示例从 C# 移植到 C++/WinRT 的情况下, 我们能够跨 C # 和 C++/WinRT 项目使用相同的 XAML 标记(包括资源)和资产文件。 在某些情况下,必须对标记进行编辑才能实现此目的。 请参阅复制完成 MainPage 移植所需的 XAML 和样式。
涉及语言内部流程的变更
| Category | 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; |
移植构造函数、 当前和 FEATURE_NAME |
| 使用 args 将对象构造为变量 | 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 existingv = map[k]; // throws if not presentmap.ContainsKey(k) |
map.Insert(k, v); // replaces any existingv = map.Lookup(k); // throws if not presentmap.HasKey(k) |
|
| 类型转换(失败时引发) | (MyType)v |
v.as<MyType>() |
移植 Footer_Click 方法 |
| 类型转换 (失败时为 null) | v as MyType |
v.try_as<MyType>() |
移植 PasteButton_Click 方法 |
| 具有 x:Name 的 XAML 元素是属性 | MyNamedElement |
MyNamedElement() |
移植构造函数、 当前和 FEATURE_NAME |
| 切换到 UI 线程 | CoreDispatcher.RunAsync | DispatcherQueue.TryEnqueue 或 winrt::resume_foreground | 移植 NotifyUser 方法和移植 HistoryAndRoaming 方法 |
| XAML 页面中命令性代码中的 UI 元素构造 | 请参阅 UI 元素构造 | 请参阅 UI 元素构造 |
以下各节详细介绍了表中的某些项。
UI 元素构造
这些代码示例演示 XAML 页面命令性代码中 UI 元素的构造。
var myTextBlock = new TextBlock()
{
Text = "Text",
Style = (Microsoft.UI.Xaml.Style)this.Resources["MyTextBlockStyle"]
};
TextBlock myTextBlock;
myTextBlock.Text(L"Text");
myTextBlock.Style(
winrt::unbox_value<Microsoft::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。 你需要为任何想要转换为字符串的其他类型添加重载。
| 语言 | 将 int 转换为字符串 | 将枚举转换为字符串 |
|---|---|---|
| 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 类型。
| Category | C# | C++/WinRT |
|---|---|---|
| 构建字符串 | StringBuilder builder;builder.Append(...); |
std::wostringstream builder;builder << ...; |
| 追加 Windows 运行时 字符串,保留空字符 | 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 线程上执行工作,通常使用 DispatcherQueue.TryEnqueue 方法(或 UWP 中的较旧的 CoreDispatcher.RunAsync )。 下面是 C# 中的模式。
private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
DispatcherQueue.TryEnqueue(() =>
{
// 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 DispatcherQueue();
// 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; |
| Operation | 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); |
如果尝试将空指针拆箱为值类型,C++/CX 和 C# 会引发异常。 C++/WinRT 将此视为编程错误,并且它崩溃。 在 C++/WinRT 中,如果要处理对象不是你认为的类型的情况,请使用 winrt::unbox_value_or 函数。
| Scenario | C# | C++/WinRT |
|---|---|---|
| 对已知整数进行拆箱 | i = (int)o; |
i = unbox_value<int>(o); |
| 如果 o 为 null | System.NullReferenceException |
崩溃 |
| 如果 o 不是装箱的 int | System.InvalidCastException |
崩溃 |
| 将 int 拆箱;如果为 null,则使用后备值;否则崩溃 | i = o != null ? (int)o : fallback; |
i = o ? unbox_value<int>(o) : fallback; |
| 如果可能,拆箱 int;其余情况则使用后备方案 | 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 字符串可能会有不同的表示形式,具体取决于你是通过哪种方式得到它的。
| Behavior | C# | C++/WinRT |
|---|---|---|
| 声明 | object o;string s; |
IInspectable o;hstring s; |
| 字符串类型类别 | 引用类型 | 值类型 |
| null HSTRING 映射为 | "" |
hstring{} |
null 和 "" 相同? |
No | Yes |
| 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 |
基本装箱和拆箱。
| Operation | C# | C++/WinRT |
|---|---|---|
| 将字符串装箱 | o = s;空字符串变为非 null 对象。 |
o = box_value(s);空字符串变为非 null 对象。 |
| 对已知字符串进行拆箱 | s = (string)o;Null 对象变为 null 字符串。 如果不是字符串类型,则会引发 InvalidCastException。 |
s = unbox_value<hstring>(o);空对象崩溃。 如果不是字符串,则崩溃。 |
| 取消装箱可能的字符串 | s = o as string;null 对象或非字符串会变为 null 字符串。 OR s = o as string ?? fallback;null 值或非字符串值将使用回退值。 保留空字符串。 |
s = unbox_value_or<hstring>(o, fallback);Null 或非字符串变为回退。 保留空字符串。 |
使类可供 {Binding} 标记扩展使用
如果打算使用 {Binding} 标记扩展将数据绑定到数据类型,请参阅 使用 {Binding} 声明的 Binding 对象。
在 XAML 标记中使用对象
在 C# 项目中,可以访问 XAML 标记中的私有成员和命名元素。 但在 C++/WinRT 中,使用 XAML {x:Bind} 标记扩展 使用的所有实体都必须在 IDL 中公开。
此外,在 C# 中,绑定到布尔值时会显示 true 或 false,而在 C++/WinRT 中则会显示 Windows.Foundation.IReference`1<Boolean>。
有关更多信息和代码示例,请参阅 从标记中使用对象。
使数据源可用于 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 : Microsoft.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 : 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>
{
...
}
}