从 C# 移动到 C++/WinRTMove to C++/WinRT from C#

本主题对 C# 项目中的源代码移植到 C++/WinRT 项目中的等效项时所涉及的技术细节进行了全面分类。This topic comprehensively catalogs the technical details involved in porting the source code in a C# project to its equivalent in C++/WinRT.

有关移植某一通用 Windows 平台 (UWP) 应用示例的案例研究,请参阅对应的主题将 Clipboard 示例从 C# 移植到 C++/WinRTFor a case study of porting one of the Universal Windows Platform (UWP) app samples, see the companion topic Porting the Clipboard sample to C++/WinRT from C#. 可以通过按照该演练操作并在操作时为自己移植示例,来获取移植实践和体验。You can gain porting practice and experience by following along with that walkthrough, and porting the sample for yourself as you go.

如何准备以及预期的结果How to prepare, and what to expect

案例研究将 Clipboard 示例从 C# 移植到 C++/WinRT 阐释了在将项目迁移到 C++/WinRT 时要做出的软件设计决策类型的示例。The case study Porting the Clipboard sample to C++/WinRT from C# illustrates examples of the kinds of software design decisions that you'll make while porting a project to C++/WinRT. 因此,最好是通过深入了解现有代码的工作原理来准备迁移。So, it's a good idea to prepare for porting by gaining a solid understanding of how the existing code works. 这样,可以大致了解应用的功能和代码的结构,然后,你做出的决策就会始终引领你向正确的方向前进。That way, you'll get a good overview of the app's functionality, and the code's structure, and then the decisions that you make will always take you forward, and in the right direction.

在所需的移植更改的类型方面,可以将其分组为四个类别。In terms of what kinds of porting changes to expect, you could group them into four categories.

  • 移植语言投影Port the language projection. Windows 运行时 (WinRT) 已投影到各种编程语言。The Windows Runtime (WinRT) is projected into various programming languages. 其中的每个语言投影均已设计为符合所涉及的编程语言的语言习惯。Each of those language projections is designed to feel idiomatic to the programming language in question. 对于 C#,某些 Windows 运行时类型被投影为 .NET 类型。For C#, some Windows Runtime types are projected as .NET types. 因此例如,你会将 System.Collections.Generic.IReadOnlyList<T> 转换回 Windows.Foundation.Collections.IVectorView<T>So for example you'll be translating System.Collections.Generic.IReadOnlyList<T> back to Windows.Foundation.Collections.IVectorView<T>. 此外,在 C# 中,某些 Windows 运行时操作也被投影为方便的 C# 语言功能。Also in C#, some Windows Runtime operations are projected as convenient C# language features. 例如,在 C# 中,可以使用 += 运算符语法来注册事件处理委托。An example is that in C# you use the += operator syntax to register an event-handling delegate. 因此,你将转换语言功能,例如,转换回要执行的基本操作(在本示例中为事件注册)。So you'll be translating language features such as that back to the fundamental operation that's being performed (event registration, in this example).
  • 移植语言语法Port language syntax. 这些更改中的许多更改都是简单的机械转换(将一个符号替换为另一个符号)。Many of these changes are simple mechanical transforms, replacing one symbol for another. 例如,将点 (.) 更改为双冒号 (::)。For example, changing dot (.) to double-colon (::).
  • 移植语言过程Port language procedure. 其中的一些可能是简单、重复的更改(例如 myObject.MyPropertymyObject.MyProperty())。Some of these can be simple, repetitive changes (such as myObject.MyProperty to myObject.MyProperty()). 其他移植则需要更深层次的更改(例如,将涉及使用 System.Text.StringBuilder 的过程迁移到涉及使用 std::wostringstream 的过程)。Others need deeper changes (for example, porting a procedure that involves the use of System.Text.StringBuilder to one that involves the use of std::wostringstream).
  • 与移植相关且特定于 C++/WinRT 的任务Porting-related tasks that are specific to C++/WinRT. Windows 运行时的某些详细信息会在幕后由 C# 隐式处理。Certain details of the Windows Runtime are taken care of impliclicly by C#, behind the scenes. 这些详细信息在 C++/WinRT 中是显式处理的。Those details are done explicitly in C++/WinRT. 例如,使用 .idl 文件来定义运行时类。An example is that you use an .idl file to define your runtime classes.

本主题的其余部分根据该分类进行组织。The rest of this topic is structured according to that taxonomy.

涉及语言投影的更改Changes that involve the language projection

类别Category C#C# C++/WinRTC++/WinRT 另请参阅See also
非类型化对象Untyped object objectSystem.Objectobject, or System.Object Windows::Foundation::IInspectableWindows::Foundation::IInspectable 移植 EnableClipboardContentChangedNotifications 方法Porting the EnableClipboardContentChangedNotifications method
投影命名空间Projection namespaces using System; using namespace Windows::Foundation;
using System.Collections.Generic; using namespace Windows::Foundation::Collections;
集合大小Size of a collection collection.Count collection.Size() 移植 BuildClipboardFormatsOutputString 方法Porting the BuildClipboardFormatsOutputString method
典型集合类型Typical collection type IList<T>,以及用用于添加元素的 Add 。IList<T>, and Add to add an element. IVector<T>,以及用于添加元素的 Append 。IVector<T>, and Append to add an element. 如果在任意位置使用 std::vector,则通过 push_back 添加元素 。If you use a std::vector anywhere, then push_back to add an element.
只读集合类型Read-only collection type IReadOnlyList<T> IReadOnlyList<T> IVectorView<T> IVectorView<T> 移植 BuildClipboardFormatsOutputString 方法Porting the BuildClipboardFormatsOutputString method
作为类成员的事件处理程序委托Event handler delegate as class member myObject.EventName += Handler; token = myObject.EventName({ get_weak(), &Class::Handler }); 移植 EnableClipboardContentChangedNotifications 方法Porting the EnableClipboardContentChangedNotifications method
撤销事件处理程序委托Revoke event handler delegate myObject.EventName -= Handler; myObject.EventName(token); 移植 EnableClipboardContentChangedNotifications 方法Porting the EnableClipboardContentChangedNotifications method
关联容器Associative container IDictionary<K, V> IDictionary<K, V> IMap<K, V> IMap<K, V>
矢量成员访问Vector member access x = v[i];
v[i] = x;
x = v.GetAt(i);
v.SetAt(i, x);

注册/撤销事件处理程序Register/revoke an event handler

在 C++/WinRT 中,可以使用多个语法选项注册/撤销事件处理程序委托,如在 C++/WinRT 中使用委托处理事件中所述。In C++/WinRT, you have several syntactic options to register/revoke an event handler delegate, as described in Handle events by using delegates in C++/WinRT. 另请参阅移植 EnableClipboardContentChangedNotifications 方法Also see Porting the EnableClipboardContentChangedNotifications method.

有时,例如,当事件接收方(处理事件的对象)即将销毁时,你需要撤销事件处理程序,以使事件源(引发事件的对象)不会调用已销毁的对象。Sometimes, for example when an event recipient (an object handling an event) is about to be destroyed, you'll want to revoke an event handler so that the event source (the object raising the event) doesn't call into a destroyed object. 请参阅撤销已注册的委托See Revoke a registered delegate. 在此类情况下,请为事件处理程序创建一个 event_token 成员变量。In cases like that, create an event_token member variable for your event handlers. 有关示例,请参阅移植 EnableClipboardContentChangedNotifications 方法For an example, see Porting the EnableClipboardContentChangedNotifications method.

还可以使用 XAML 标记注册事件处理程序。You can also register an event handler in XAML markup.

<Button x:Name="OpenButton" Click="OpenButton_Click" />

在 C# 中,OpenButton_Click 方法可以是专用的,但 XAML 仍然能够将它连接到 OpenButton 引发的 ButtonBase.Click 事件。In C#, your OpenButton_Click method can be private, and XAML will still be able to connect it to the ButtonBase.Click event raised by OpenButton.

在 C++/WinRT 中,OpenButton_Click 方法在你的实现类型中必须是公共的(如果你想要使用 XAML 标记注册该方法)。In C++/WinRT, your OpenButton_Click method must be public in your implementation type if you want to register it in XAML markup. 如果只在命令性代码中注册事件处理程序,则该事件处理程序不需要是公共的。If you register an event handler only in imperative code, then the event handler doesn't need to be public.

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 设置为专用。Alternatively, you can make the registering XAML page a friend of your implementation type, and OpenButton_Click private.

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 中的函数)。One final scenario is where the C# project that you're porting binds to the event handler from markup (for more background on that scenario, see Functions in x:Bind).

<Button x:Name="OpenButton" Click="{x:Bind OpenButton_Click}" />

只需将该标记更改为更简单的 Click="OpenButton_Click" 即可。You could just change that markup to the more simple Click="OpenButton_Click". 如果希望,也可将该标记保留原样。Or, if you prefer, you can keep that markup as it is. 要支持此操作,你只需在 IDL 中声明事件处理程序即可。All you have to do to support it is to declare the event handler in IDL.

void OpenButton_Click(Object sender, Windows.UI.Xaml.RoutedEventArgs e);

备注

将函数声明为 void,即使你将它作为发后不理 (Fire and forget) 来实现也是如此。Declare the function as void even if you implement it as Fire and forget.

涉及语言语法的更改Changes that involve the language syntax

类别Category C#C# C++/WinRTC++/WinRT 另请参阅See also
访问修饰符Access modifiers public \<member\> public:
    \<member\>
移植 Button_Click 方法Porting the Button_Click method
访问数据成员Access a data member this.variable this->variable
异步行为Async action async Task ... IAsyncAction ...
异步操作Async operation async Task<T> ... IAsyncOperation<T> ...
“发后不理”方法(意味着异步)Fire-and-forget method (implies async) async void ... winrt::fire_and_forget ... 移植 CopyButton_Click 方法Porting the CopyButton_Click method
访问枚举常量Access an enumerated constant E.Value E::Value 移植 DisplayChangedFormats 方法Porting the DisplayChangedFormats method
配合地等待Cooperatively wait await ... co_await ... 移植 CopyButton_Click 方法Porting the CopyButton_Click method
作为私有字段的投影类型集合Collection of projected types as a private field private List<MyRuntimeClass> myRuntimeClasses = new List<MyRuntimeClass>(); std::vector
<MyNamespace::MyRuntimeClass>
m_myRuntimeClasses;
GUID 构造GUID construction 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} };
命名空间分隔符Namespace separator A.B.T A::B::T
NullNull null nullptr 移植 UpdateStatus 方法Porting the UpdateStatus method
获取类型对象Obtain a type object typeof(MyType) winrt::xaml_typename<MyType>() 移植 Scenarios 属性Porting the Scenarios property
方法的参数声明Parameter declaration for a method MyType MyType const& 参数传递Parameter-passing
异步方法的参数声明Parameter declaration for an async method MyType MyType 参数传递Parameter-passing
调用静态方法Call a static method T.Method() T::Method()
字符串Strings stringSystem.Stringstring, or System.String winrt::hstringwinrt::hstring C++/WinRT 中的字符串处理String handling in C++/WinRT
字符串文本String literal "a string literal" L"a string literal" 移植构造函数 CurrentFEATURE_NAMEPorting the constructor, Current, and FEATURE_NAME
推断(或推导)的类型Inferred (or deduced) type var auto 移植 BuildClipboardFormatsOutputString 方法Porting the BuildClipboardFormatsOutputString method
Using 指令Using-directive using A.B.C; using namespace A::B::C; 移植构造函数 CurrentFEATURE_NAMEPorting the constructor, Current, and FEATURE_NAME
原义/原始字符串文本Verbatim/raw string literal @"verbatim string literal" LR"(raw string literal)" 移植 DisplayToast 方法Porting the DisplayToast method

备注

如果头文件没有包含用于给定命名空间的 using namespace 指令,则必须完全限定该命名空间的所有类型名称;或者至少对它们进行充分限定,以使编译器可以找到它们。If a header file doesn't contain a using namespace directive for a given namespace, then you'll have to fully-qualify all type names for that namespace; or at least qualify them sufficiently for the compiler to find them. 有关示例,请参阅移植 DisplayToast 方法For an example, see Porting the DisplayToast method.

移植类和成员Porting classes and members

对于每种 C# 类型,都需要确定是将它移植到 Windows 运行时类型,还是移植到常规 C++ 类/结构/枚举。You'll need to decide, for each C# type, whether to port it to a Windows Runtime type, or to a regular C++ class/struct/enumeration. 有关详细信息以及演示如何做出这些决策的详细示例,请参阅将 Clipboard 示例从 C# 移植到 C++/WinRTFor more info, and detailed examples illustrating how to make those decisions, see Porting the Clipboard sample to C++/WinRT from C#.

一个 C# 属性通常会成为一个访问器函数、一个赋值函数和一个支持数据成员。A C# property typically becomes an accessor function, a mutator function, and a backing data member. 有关详细信息和示例,请参阅移植 IsClipboardContentChangedEnabled 属性For more info, and an example, see Porting the IsClipboardContentChangedEnabled property.

对于非静态字段,请使其成为你的实现类型的数据成员。For non-static fields, make them data members of your implementation type.

C# 静态字段会成为 C++/WinRT 静态访问器和/或赋值函数。A C# static field becomes a C++/WinRT static accessor and/or mutator function. 有关详细信息和示例,请参阅移植构造函数 CurrentFEATURE_NAMEFor more info, and an example, see Porting the constructor, Current, and FEATURE_NAME.

对于成员函数,你也需要为每个成员函数决定其是否属于 IDL,或者它是实现类型的公共成员函数还是私有成员函数。For member functions, again, you'll need to decide for each one whether or not it belongs in the IDL, or whether it's a public or private member function of your implementation type. 有关详细信息以及如何决定的示例,请参阅 MainPage 类型的 IDLFor more info, and examples of how to decide, see IDL for the MainPage type.

移植 XAML 标记和资产文件Porting XAML markup, and asset files

将 Clipboard 示例从 C# 移植到 C++/WinRT的案例中,我们能够在 C# 和 C++/WINRT 项目中使用相同的 XAML 标记(包括资源)和资产文件。In the case of Porting the Clipboard sample to C++/WinRT from C#, we were able to use the same XAML markup (including resources) and asset files across the C# and the C++/WinRT project. 在某些情况下,需要编辑标记才能实现此目的。In some cases, edits to markup will be necessary to achieve that. 请参阅复制完成移植 MainPage 所需的 XAML 和样式See Copy the XAML and styles necessary to finish up porting MainPage.

涉及语言内过程的更改Changes that involve procedures within the language

类别Category C#C# C++/WinRTC++/WinRT 另请参阅See also
异步方法中的生存期管理Lifetime management in an async method N/AN/A auto lifetime{ get_strong() };auto lifetime{ get_strong() }; or
auto lifetime = get_strong();
移植 CopyButton_Click 方法Porting the CopyButton_Click method
处置Disposal using (var t = v) auto t{ v };
t.Close(); // or let wrapper destructor do the work
移植 CopyImage 方法Porting the CopyImage method
构造对象Construct object new MyType(args) MyType{ args }MyType{ args } or
MyType(args)
移植 Scenarios 属性Porting the Scenarios property
创建未初始化的引用Create uninitialized reference MyType myObject; MyType myObject{ nullptr };MyType myObject{ nullptr }; or
MyType myObject = nullptr;
移植构造函数 CurrentFEATURE_NAMEPorting the constructor, Current, and FEATURE_NAME
使用参数将对象构造到变量中Construct object into variable with args var myObject = new MyType(args); auto myObject{ MyType{ args } };auto myObject{ MyType{ args } }; or
auto myObject{ MyType(args) };auto myObject{ MyType(args) }; or
auto myObject = MyType{ args };auto myObject = MyType{ args }; or
auto myObject = MyType(args);auto myObject = MyType(args); or
MyType myObject{ args };MyType myObject{ args }; or
MyType myObject(args);
移植 Footer_Click 方法Porting the Footer_Click method
在不使用参数的情况下将对象构造到变量中Construct object into variable without args var myObject = new T(); MyType myObject; 移植 BuildClipboardFormatsOutputString 方法Porting the BuildClipboardFormatsOutputString method
对象初始化速记Object initialization shorthand var p = new FileOpenPicker{
    ViewMode = PickerViewMode.List
};
FileOpenPicker p;
p.ViewMode(PickerViewMode::List);
批量矢量操作Bulk vector operation var p = new FileOpenPicker{
    FileTypeFilter = { ".png", ".jpg", ".gif" }
};
FileOpenPicker p;
p.FileTypeFilter().ReplaceAll({ L".png", L".jpg", L".gif" });
移植 CopyButton_Click 方法Porting the CopyButton_Click method
循环访问集合Iterate over collection foreach (var v in c) for (auto&& v : c) 移植 BuildClipboardFormatsOutputString 方法Porting the BuildClipboardFormatsOutputString method
捕获异常Catch an exception catch (Exception ex) catch (winrt::hresult_error const& ex) 移植 PasteButton_Click 方法Porting the PasteButton_Click method
异常详细信息Exception details ex.Message ex.message() 移植 PasteButton_Click 方法Porting the PasteButton_Click method
获取属性值Get a property value myObject.MyProperty myObject.MyProperty() 移植 NotifyUser 方法Porting the NotifyUser method
设置属性值Set a property value myObject.MyProperty = value; myObject.MyProperty(value);
递增属性值Increment a property value myObject.MyProperty += v; myObject.MyProperty(thing.Property() + v);
对于字符串,请切换到生成器For strings, switch to a builder
ToString()ToString() myObject.ToString() winrt::to_hstring(myObject) ToString()ToString()
语言字符串到 Windows 运行时字符串Language string to Windows Runtime string N/AN/A winrt::hstring{ s }
字符串生成String-building StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
字符串生成String-building
字符串内插String interpolation $"{i++}) {s.Title}" winrt::to_hstring 和/或 winrt::hstring::operator+ winrt::to_hstring, and/or winrt::hstring::operator+ 移植 OnNavigatedTo 方法Porting the OnNavigatedTo method
用于比较的空字符串Empty string for comparison System.String.EmptySystem.String.Empty winrt::hstring::emptywinrt::hstring::empty 移植 UpdateStatus 方法Porting the UpdateStatus method
创建空字符串Create empty string var myEmptyString = String.Empty; winrt::hstring myEmptyString{ L"" };
字典操作Dictionary operations 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)
类型转换(失败时引发)Type conversion (throw on failure) (MyType)v v.as<MyType>() 移植 Footer_Click 方法Porting the Footer_Click method
类型转换(失败时为 null)Type conversion (null on failure) v as MyType v.try_as<MyType>() 移植 PasteButton_Click 方法Porting the PasteButton_Click method
具有 x:Name 的 XAML 元素是属性XAML elements with x:Name are properties MyNamedElement MyNamedElement() 移植构造函数 CurrentFEATURE_NAMEPorting the constructor, Current, and FEATURE_NAME
切换到 UI 线程Switch to the UI thread CoreDispatcher.RunAsyncCoreDispatcher.RunAsync CoreDispatcher.RunAsyncwinrt::resume_foregroundCoreDispatcher.RunAsync, or winrt::resume_foreground 移植 NotifyUser 方法以及移植 HistoryAndRoaming 方法Porting the NotifyUser method, and Porting the HistoryAndRoaming method
XAML 页的强制性代码中的 UI 元素构造UI element construction in imperative code in a XAML page 请参阅 UI 元素构造See UI element construction 请参阅 UI 元素构造See UI element construction

以下部分更详细地介绍了该表中的某些项。The following sections go into more detail regarding some of the items in the table.

UI 元素构造UI element construction

这些代码示例演示了 XAML 页面的强制性代码中的 UI 元素构造。These code examples show the construction of a UI element in the imperative code of a XAML page.

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()ToString()

C# 类型提供 Object.ToString 方法。C# types provide the Object.ToString method.

int i = 2;
var s = i.ToString(); // s is a System.String with value "2".

C++/ WinRT 不直接提供此工具,不过可以转为使用替代方法。C++/WinRT doesn't directly provide this facility, but you can turn to alternatives.

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

C++/WinRT 也支持 winrt::to_hstring,但仅限数目有限的一些类型。C++/WinRT also supports winrt::to_hstring for a limited number of types. 对于任何其他需要字符串化的类型,你需要添加重载。You'll need to add overloads for any additional types you want to stringify.

LanguageLanguage 将整数字符串化Stringify int 将枚举字符串化Stringify enum
C#C# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
C++/WinRTC++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

如果将枚举字符串化,则需提供 winrt::to_hstring 的实现。In the case of stringifying an enum, you will need to provide the implementation of 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));
        }
    }
}

这些字符串化通常通过数据绑定来隐式使用。These stringifications are often consumed implicitly by data binding.

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

这些绑定会对被绑定属性执行 winrt::to_hstringThese bindings will perform winrt::to_hstring of the bound property. 至于第二个示例 (StatusEnum),则必须提供你自己的 winrt::to_hstring 重载,否则会出现编译器错误。In the case of the second example (the StatusEnum), you must provide your own overload of winrt::to_hstring, otherwise you'll get a compiler error.

另请参阅移植 Footer_Click 方法Also see Porting the Footer_Click method.

字符串生成String-building

C# 有一个内置的 StringBuilder 类型,用于字符串生成。For string building, C# has a built-in StringBuilder type.

类别Category C#C# C++/WinRTC++/WinRT
字符串生成String-building StringBuilder builder;
builder.Append(...);
std::wostringstream builder;
builder << ...;
追加 Windows 运行时字符串(保留 null 值)Append a Windows Runtime string, preserving nulls builder.Append(s); builder << std::wstring_view{ s };
添加新行Add a newline builder.Append(Environment.NewLine); builder << std::endl;
访问结果Access the result s = builder.ToString(); ws = builder.str();

另请参阅移植 BuildClipboardFormatsOutputString 方法移植 DisplayChangedFormats 方法Also see Porting the BuildClipboardFormatsOutputString method, and Porting the DisplayChangedFormats method.

在主 UI 线程上运行代码Running code on the main UI thread

此示例取自条形码扫描仪示例This example is taken from the Barcode scanner sample.

要在 C# 项目的主 UI 线程上工作时,通常使用 CoreDispatcher.RunAsync 方法,如下所示。When you want to do work on the main UI thread in a C# project, you typically use the CoreDispatcher.RunAsync method, like this.

private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        // Do work on the main UI thread here.
    });
}

更简单的是用 C++/WinRT 来表示。It's much simpler to express that in C++/WinRT. 请注意,假设我们希望在第一个暂停点(在本例中是 co_await)之后访问参数时,我们将按值接受参数。Notice that we're accepting parameters by value on the assumption we'll want to access them after the first suspension point (the co_await, in this case). 有关详细信息,请参阅参数传递For more info, see Parameter-passing.

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 函数,该函数包含需要优先处理的重载。If you need to do the work at a priority other than the default, then see the winrt::resume_foreground function, which has an overload that takes a priority. 有关演示如何等待调用 winrt::resume_foreground 的代码示例,请参阅编程时仔细考虑线程相关性For code examples showing how to await a call to winrt::resume_foreground, see Programming with thread affinity in mind.

在 IDL 中定义运行时类Define your runtime classes in IDL

请参阅 MainPage 类型的 IDL合并 .idl 文件See IDL for the MainPage type, and Consolidate your .idl files.

包含所需的 C++/WinRT Windows 命名空间头文件Include the C++/WinRT Windows namespace header files that you need

在 C++/WinRT 中,只要你希望使用来自 Windows 命名空间的类型,都需要包含对应的 C++/WinRT Windows 命名空间头文件。In C++/WinRT, whenever you want to use a type from a Windows namespaces, you need to include the corresponding C++/WinRT Windows namespace header file. 有关示例,请参阅移植 NotifyUser 方法For an example, see Porting the NotifyUser method.

装箱和取消装箱Boxing and unboxing

C# 自动将标量装箱到对象中。C# automatically boxes scalars into objects. C++/WinRT 要求你显式调用 winrt::box_value 函数。C++/WinRT requires you to call the winrt::box_value function explicitly. 两种语言都要求你以显式方式取消装箱。Both languages require you to unbox explicitly. 请参阅使用 C++/WinRT 装箱和取消装箱See Boxing and unboxing with C++/WinRT.

在下面的表中,我们将使用这些定义。In the tables that follows, we'll use these definitions.

C#C# C++/WinRTC++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
操作Operation C#C# C++/WinRTC++/WinRT
装箱Boxing o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
取消装箱Unboxing i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

如果尝试取消值类型的 null 指针的装箱,C++/CX 和 C# 会引发异常。C++/CX and C# raise exceptions if you try to unbox a null pointer to a value type. C++/WinRT 将其视为编程错误,因此会崩溃。C++/WinRT considers this a programming error, and it crashes. 在 C++/WinRT 中,请使用 winrt::unbox_value_or 函数来处理对象类型不符合预期的情况。In C++/WinRT, use the winrt::unbox_value_or function if you want to handle the case where the object is not of the type that you thought it was.

方案Scenario C#C# C++/WinRTC++/WinRT
取消已知整数的装箱Unbox a known integer i = (int)o; i = unbox_value<int>(o);
如果 o 为 nullIf o is null System.NullReferenceException 崩溃Crash
如果 o 不是装箱的整数If o is not a boxed int System.InvalidCastException 崩溃Crash
取消整数的装箱,在为 null 的情况下使用回退;任何其他情况则崩溃Unbox int, use fallback if null; crash if anything else i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
尽可能取消整数的装箱;在任何其他情况下使用回退Unbox int if possible; use fallback for anything else i = as int? ?? fallback; i = unbox_value_or<int>(o, fallback);

有关示例,请参阅移植 OnNavigatedTo 方法移植 Footer_Click 方法For an example, see Porting the OnNavigatedTo method, and Porting the Footer_Click method.

将字符串装箱和取消装箱Boxing and unboxing a string

字符串在某些情况下是值类型,在另一些情况下是引用类型。A string is in some ways a value type, and in other ways a reference type. C# 和 C++/WinRT 对待字符串的方式有所不同。C# and C++/WinRT treat strings differently.

ABI 类型 HSTRING 是一个指向引用计数字符串的指针。The ABI type HSTRING is a pointer to a reference-counted string. 但是,它并非派生自 IInspectable,因此从技术上来说它不是一个对象。But it doesn't derive from IInspectable, so it's not technically an object. 另外,null HSTRING 表示空字符串。Furthermore, a null HSTRING represents the empty string. 将并非派生自 IInspectable 的项装箱时,需将其包装到 IReference<T> 中,而 Windows 运行时会以 PropertyValue 对象的形式提供标准实现(自定义类型以 PropertyType::OtherType 形式报告) 。Boxing of things not derived from IInspectable is done by wrapping them inside an IReference<T>, and the Windows Runtime provides a standard implementation in the form of the PropertyValue object (custom types are reported as PropertyType::OtherType).

C# 将 Windows 运行时字符串表示为引用类型,而 C++/WinRT 则将字符串投影为值类型。C# represents a Windows Runtime string as a reference type; while C++/WinRT projects a string as a value type. 这意味着装箱的 null 字符串可能有不同的表示形式,具体取决于你所采用的方法。This means that a boxed null string can have different representations depending how you got there.

行为Behavior C#C# C++/WinRTC++/WinRT
声明Declarations object o;
string s;
IInspectable o;
hstring s;
字符串类型类别String type category 引用类型Reference type 值类型Value type
null HSTRING 投影方式null HSTRING projects as "" hstring{}
null 和 "" 是否相同?Are null and "" identical? No Yes
null 的有效性Validity of null s = null;
s.Length 引发 NullReferenceExceptions.Length raises NullReferenceException
s = hstring{};
s.size() == 0(有效)s.size() == 0 (valid)
如果将 null 字符串分配给对象If you assign null string to object o = (string)null;
o == null
o = box_value(hstring{});
o != nullptr
如果将 "" 分配给对象If you assign "" to object o = "";
o != null
o = box_value(hstring{L""});
o != nullptr

基本装箱和取消装箱。Basic boxing and unboxing.

操作Operation C#C# C++/WinRTC++/WinRT
将字符串装箱Box a string o = s;
空字符串变为非 null 对象。Empty string becomes non-null object.
o = box_value(s);
空字符串变为非 null 对象。Empty string becomes non-null object.
取消已知字符串的装箱Unbox a known string s = (string)o;
Null 对象变为 null 字符串。Null object becomes null string.
如果不是字符串,则引发 InvalidCastException。InvalidCastException if not a string.
s = unbox_value<hstring>(o);
Null 对象崩溃。Null object crashes.
如果不是字符串,则崩溃。Crash if not a string.
将可能的字符串取消装箱Unbox a possible string s = o as string;
Null 对象或非字符串变为 null 字符串。Null object or non-string becomes null string.

或者OR

s = o as string ?? fallback;
Null 或非字符串变为 fallback。Null or non-string becomes fallback.
空字符串被保留。Empty string preserved.
s = unbox_value_or<hstring>(o, fallback);
Null 或非字符串变为 fallback。Null or non-string becomes fallback.
空字符串被保留。Empty string preserved.

使类可供 {Binding} 标记扩展使用Making a class available to the {Binding} markup extension

若要使用 {Binding} 标记扩展将数据绑定到数据类型,则请参阅使用 {Binding} 绑定声明的对象If you intend to use the {Binding} markup extension to data bind to your data type, then see Binding object declared using {Binding}.

使用 XAML 标记中的对象Consuming objects from XAML markup

在 C# 项目中,可以使用 XAML 标记中的专用成员和命名元素。In a C# project, you can consume private members and named elements from XAML markup. 但在 C++/WinRT 中,以 XAML {x:Bind} 标记扩展形式使用的所有实体必须在 IDL 中以公开方式公开。But in C++/WinRT, all entities consumed by using the XAML {x:Bind} markup extension must be exposed publicly in IDL.

另外,绑定到布尔值时,在 C# 中会显示 truefalse,但在 C++/WinRT 中会显示 Windows.Foundation.IReference`1<Boolean>。Also, binding to a Boolean displays true or false in C#, but it shows Windows.Foundation.IReference`1<Boolean> in C++/WinRT.

有关详细信息和代码示例,请参阅使用标记中的对象For more info, and code examples, see Consuming objects from markup.

使数据源可供 XAML 标记使用Making a data source available to XAML markup

在 C++/WinRT 2.0.190530.8 及更高版本中,winrt::single_threaded_observable_vector 创建一个可观测的支持 IObservableVector<T> 和 IObservableVector<IInspectable> 的矢量 。In C++/WinRT version 2.0.190530.8 and higher, winrt::single_threaded_observable_vector creates an observable vector that supports both IObservableVector<T> and IObservableVector<IInspectable>. 有关示例,请参阅 移植 Scenarios 属性For an example, see Porting the Scenarios property.

你可以创作 Midl 文件 (.idl) ,如下所示(另请参阅将运行时类重构到 Midl 文件 (.idl) 中)。You can author your Midl file (.idl) like this (also see Factoring runtime classes into Midl files (.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; };
    }
}

实现方式如下。And implement like this.

// 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 的集合For more info, see XAML items controls; bind to a C++/WinRT collection, and Collections with C++/WinRT.

使数据源可供 XAML 标记使用(在 C++/WinRT 2.0.190530.8 之前)Making a data source available to XAML markup (prior to C++/WinRT 2.0.190530.8)

XAML 数据绑定要求项源实现 IIterable<IInspectable> 以及下述接口组合之一。XAML data binding requires that an items source implements IIterable<IInspectable>, as well as one of the following combinations of interfaces.

  • IObservableVector<IInspectable>IObservableVector<IInspectable>
  • IBindableVectorINotifyCollectionChangedIBindableVector and INotifyCollectionChanged
  • IBindableVectorIBindableObservableVectorIBindableVector and IBindableObservableVector
  • IBindableVector 本身(不会响应更改)IBindableVector by itself (will not respond to changes)
  • IVector<IInspectable>IVector<IInspectable>
  • IBindableIterable(会通过迭代将元素保存到专用集合中)IBindableIterable (will iterate and save elements into a private collection)

无法在运行时检测到泛型接口(例如 IVector<T>)。A generic interface such as IVector<T> can't be detected at runtime. 每个 IVector<T> 都有不同的接口标识符 (IID),它是 T 的函数 。任何开发人员都可以随意扩展 T 集,因此,很明显 XAML 绑定代码不可能知道要查询的完整集。Each IVector<T> has a different interface identifier (IID), which is a function of T. Any developer can expand the set of T arbitrarily, so clearly the XAML binding code can never know the full set to query for. 该限制对 C# 来说不成问题,因为每个实现 IEnumerable<T> 的 CLR 对象都会自动实现 IEnumerable 。That restriction isn't a problem for C# because every CLR object that implements IEnumerable<T> automatically implements IEnumerable. 在 ABI 级别,这意味着每个实现 IObservableVector<T> 的对象都会自动实现 IObservableVector<IInspectable> 。At the ABI level, that means that every object that implements IObservableVector<T> automatically implements IObservableVector<IInspectable>.

C++/WinRT 不提供该保证。C++/WinRT doesn't offer that guarantee. 如果 C++/WinRT 运行时类会实现 IObservableVector<T>则我们无法假定还将以某种方式提供 IObservableVector<IInspectable> 的实现 。If a C++/WinRT runtime class implements IObservableVector<T>, then we can't assume that an implementation of IObservableVector<IInspectable> is somehow also provided.

因此,上一示例应如下所示。Consequently, here's how the previous example will need to look.

...
runtimeclass BookstoreViewModel
{
    // This is really an observable vector of BookSku.
    Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}

实现如下。And the implementation.

// 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::BookSkuIf you need to access objects in m_bookSkus, then you'll need to QI them back to 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;
}

派生类Derived classes

若要从运行时类派生,基类必须是可组合类。In order to derive from a runtime class, the base class must be composable. C# 不需要你执行任何特殊步骤即可将类变为可组合类,但 C++/WinRT 需要。C# doesn't require that you take any special steps to make your classes composable, but C++/WinRT does. 请使用 unsealed 关键字来指示你希望将类用作基类。You use the unsealed keyword to indicate that you want your class to be usable as a base class.

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

实现类型的头文件中,必须先包含基类头文件,然后再包含为派生类自动生成的头文件。In the header file for your implementation type, you must include the base class header file before you include the autogenerated header for the derived class. 否则会出现“将此类型用作表达式非法”之类的错误。Otherwise you'll get errors such as "Illegal use of this type as an expression".

// 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>
    {
        ...
    }
}

重要的 APIImportant APIs