從 C# 移到 C++/WinRT

Tip

如果您之前已閱讀過此主題,而這次回來是為了執行特定任務,則可以直接跳到本主題中的 依據您正在執行的任務尋找內容 章節。

本主題全面整理了將 C# 專案原始碼移植至其 C++/WinRT 等效程式的技術細節。

關於移植 Universal Windows Platform (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 移植,請參閱 發後不理方法
移植類別 首先,判斷該類別是否需要是執行時類別,還是可以是普通類別。 為了幫助你判斷,可以參考 C++/WinRT 中 Author API 的最初階段。 接著,請參考下方接下來的三行。
移植執行階段類別 一個在 C++ 應用程式之外共享功能,或是用於 XAML 資料綁定的類別。 看看你是在 Windows 執行階段 元件中撰寫執行時類別,還是在 XAML UI 中撰寫要引用的執行階段類別

這些連結對此有更詳細的說明,但執行時類別必須在 IDL 中宣告。 如果你的 project 已經包含 IDL 檔案(例如 Project.idl),我們建議你在該檔案中宣告任何新的執行時類別。 在 IDL 中,請宣告任何會在應用程式外使用,或在 XAML 中使用的方法與資料成員。 更新 IDL 檔案後,請重新建置,然後查看專案的 Generated Files 資料夾中產生的存根檔案(.h.cpp)(在 方案總管 中選取專案節點後,請確認已啟用 Show All 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)o
C++/WinRT: unbox_value<Value>(o)
如果開箱失敗,就丟出去。 另請參閱 Boxing 和 Unboxing

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。

涉及語言投影的變更

Category C# C++/WinRT 另請參閱
未型別物件 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 方法
典型的集合類型 伊利斯主義者<T>,然後 加法 來加入一個元素。 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 方法,則該方法在您的 實作類型中必須為公用。 如果你只在命令式程式碼中註冊事件處理器,那麼事件處理器就不需要公開。

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);

Note

即使你將它實作Fire and forget,也要將函式宣告為 void

涉及語言語法的變更

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" 移植建構函式、CurrentFEATURE_NAME
推斷型(或推導型) var auto 移植 BuildClipboardFormatsOutputString 方法
使用指令 using A.B.C; using namespace A::B::C; 移植建構函式、CurrentFEATURE_NAME
字面/原始字串字面 @"verbatim string literal" LR"(raw string literal)" DisplayToast 方法的移植

Note

如果標頭檔沒有包含 using namespace 某個命名空間的指令,那你就必須對該命名空間的所有型別名稱進行完整限定;或者至少要足夠限定,讓編譯器能找到它們。 舉例來說,請參見 Porting the DisplayToast 方法

移植類別與成員

你需要決定,針對每個 C# 類型,是移植到 Windows 執行階段 類型,還是一般的 C++ 類別/結構體/枚舉。 如需更多資訊,以及說明如何做出這些決策的詳細範例,請參閱 將 Clipboard 範例從 C# 移植到 C++/WinRT

C# 屬性通常會成為存取者函式、變異函式及支援資料成員。 更多資訊和範例請參見 Porting the IsClipboardContentChangedEnabled 屬性

對於非靜態欄位,讓它們成為你 實作類型的資料成員。

C# 靜態欄位會變成 C++/WinRT 的靜態存取器和/或變異器函式。 更多資訊和範例,請參見「Porting the constructor」、「Current」和FEATURE_NAME」。

對於成員函式,你同樣需要逐一判斷每一個函式是否應定義在 IDL 中,或者是否屬於你實作型別的公開或私有成員函式。 更多資訊及決策範例,請參閱 類型的 IDL

移植 XAML 標記和資產檔案

將剪貼簿範例從 C# 移植到 C++/WinRT 為例,我們能在 C# 和 C++/WinRT 專案中使用 相同的 XAML 標記(包括資源)和資產檔案。 在某些情況下,為了達成這個目標,必須對標記進行修改。 請參閱複製 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;
移植建構函式、CurrentFEATURE_NAME
用 arg 將物件建構成變數 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 方法
將物件建構成變數,無需 args 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() 移植建構函式、CurrentFEATURE_NAME
切換到使用者介面執行緒 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 型別。 你需要為任何想要轉成字串的額外類型新增多載。

語言 將整數轉換為字串 串化列舉
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 執行階段 字串,並保留 Null 字元 builder.Append(s); builder << std::wstring_view{ s };
新增換行符號 builder.Append(Environment.NewLine); builder << std::endl;
查看結果 s = builder.ToString(); ws = builder.str();

另請參閱 移植 BuildClipboardFormatsOutputString 方法,以及 移植 DisplayChangedFormats 方法

在主介面執行緒上執行程式碼

此範例取自 條碼掃描器範例

當你想在 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 呼叫的程式碼範例,請參見 「以執行緒親和性為念的程式設計」。

在 IDL 中定義你的執行時類別

請參閱 IDL 以了解 主頁 類型,並 整合您的 .idl 檔案

請包含你需要的 C++/WinRT Windows 命名空間標頭檔案

在 C++/WinRT 中,每當你想使用來自 Windows 命名空間的型別時,就必須包含對應的 C++/WinRT Windows 命名空間標頭檔。 舉例來說,請參見 Portifying the 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 為零 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,因此技術上它不是一個 物件。 此外,空 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
空值的有效性 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;
空字串變成非空物件。
o = box_value(s);
空字串變成非空物件。
開箱一個已知字串 s = (string)o;
空物件變成空字串。
如果不是字串,則使用 InvalidCastException。
s = unbox_value<hstring>(o);
Null 物件當機。
如果不是字串,則會當機。
拆箱一個可能的字串 s = o as string;
空物件或非字串會變成空字串。

OR

s = o as string ?? fallback;
null 或非字串時,將使用後備值。
空弦保存完好。
s = unbox_value_or<hstring>(o, fallback);
null 值或非字串值會使用後備值。
空弦保存完好。

讓類別可供 {Binding} 標記延伸使用

如果你打算使用 {Binding} 標記擴充功能將資料繫結至你的資料類型,請參閱 使用 {Binding} 宣告的繫結物件

從 XAML 標記中消費物件

在 C# 專案中,你可以從 XAML 標記中取得私有成員和命名元素。 但在 C++/WinRT 中,所有由 XAML {x:Bind} 標記延伸模組 使用的實體,都必須在 IDL 中公開。

另外,繫結至布林值時,在 C# 中會顯示 truefalse,但在 C++/WinRT 中則會顯示 Windows.Foundation.IReference`1<Boolean>

如需更多資訊和程式碼範例,請參閱 從標記使用物件

讓資料來源可供 XAML 標記 使用

在 C++/WinRT 2.0.190530.8 或更新版本中, winrt::single_threaded_observable_vector 建立一個可觀察向量,該向量同時支援 IObservableVector<T>IObservableVector<IInspectable>。 舉例來說,請參見「場景屬性的移植」。

你可以這樣編寫你的 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>
  • IBindableVectorINotifyCollectionChanged
  • IBindableVectorIBindableObservableVector
  • 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>
    {
        ...
    }
}

重要 API