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.MyProperty到myObject.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)oC++/WinRT: unbox_value<Value>(o)如果開箱失敗,就丟出去。 另請參閱 Boxing 和 Unboxing。 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,或 系統.物件 |
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" |
移植建構函式、Current 和 FEATURE_NAME |
| 推斷型(或推導型) | var |
auto |
移植 BuildClipboardFormatsOutputString 方法 |
| 使用指令 | using A.B.C; |
using namespace A::B::C; |
移植建構函式、Current 和 FEATURE_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; |
移植建構函式、Current 和 FEATURE_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 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() |
移植建構函式、Current 和 FEATURE_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 呼叫的程式碼範例,請參見 「以執行緒親和性為念的程式設計」。
與 C++/WinRT 相關的移植相關任務
在 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# 中會顯示 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>。 舉例來說,請參見「場景屬性的移植」。
你可以這樣編寫你的 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>
{
...
}
}