從 C++/CX 移到 C++/WinRT
這是系列文章中的第一個主題,說明如何將 C++/CX 專案中的原始程式碼移植到其在 C++/WinRT 中的對等項目。
如果您的專案也會使用 Windows 執行階段 C++ 範本庫 (WRL) 類型,請參閱從 WRL 移到 C++/WinRT。
移植策略
值得注意的是,從 C++/CX 移植到 C++/WinRT 通常不難,但從平行模式程式庫 (PPL) 工作移至協同程式則屬例外。 兩者的模型不同。 從 PPL 工作到協同程式之間並沒有自然的一對一對應,且沒有簡單的方式可機械性地移植適用於所有案例的程式碼。 如需這方面的移植協助,以及在這兩個模型之間相互操作的選項,請參閱 C++/WinRT 與 C++/CX 之間的非同步和相互操作。
開發小組會定期報告他們在移植其非同步程式碼方面克服的困難,而其餘的移植工作大多是機械性的。
一次移植
如果您能夠一次移植整個專案,則只需閱讀本主題即可取得所需的資訊 (無須閱讀本主題後續的相互操作主題)。 建議您先使用一個 C++/WinRT 專案範本,在 Visual Studio 中建立新的專案 (請參閱 C++/WinRT 的 Visual Studio 支援)。 然後,將您的原始程式碼檔案移至該新專案,同時將所有 C++/CX 原始程式碼移植到 C++/WinRT。
或者,如果您想要在現有的 C++/CX 專案中執行移植工作,則必須在其中新增 C++/WinRT 支援。 如需該作業所需的步驟,請參閱取用 C++/CX 專案並新增 C++/WinRT 支援。 當您完成移植時,純 C++/CX 專案將會轉換為純 C++/WinRT 專案。
注意
如果您有 Windows 執行階段元件專案,則一次性移植將是您唯一的選項。 以 C++ 撰寫的 Windows 執行階段元件專案所包含的必須完全是 C++/CX 原始程式碼,或完全是 C++/WinRT 原始程式碼。 兩者不可並存於此專案類型中。
逐步移植專案
除了 Windows 執行階段元件專案以外,如先前所述,若因程式碼基底的大小或複雜度而必須逐步移植您的專案,您將需要適當的移植程序,讓 C++/CX 和 C++/WinRT 程式碼能夠在一段時間內並存於相同的專案中。 除了閱讀本主題之外,另請參閱 C++/WinRT 與 C++/CX 之間的相互操作和 C++/WinRT 與 C++/CX 之間的非同步和相互操作。 這些主題所提供的資訊和程式碼範例,會示範如何在兩種語言投影之間相互操作。
若要將專案做好準備以進行逐步移植程序,其中一個選項是將 C++/WinRT 支援新增至您的 C++/CX 專案。 如需該作業所需的步驟,請參閱取用 C++/CX 專案並新增 C++/WinRT 支援。 然後,您即可從該處逐步移植。
另一個選項是使用一個 C++/WinRT 專案範本在 Visual Studio 中建立新的專案 (請參閱 C++/WinRT 的 Visual Studio 支援)。 然後,將 C++/CX 支援新增至該專案。 如需該作業所需的步驟,請參閱取用 C++/WinRT 專案並新增 C++/CX 支援。 如此,您即可開始將原始程式碼移至該程式碼,同時將部分 C++/CX 原始程式碼移植到 C++/WinRT。
無論採用哪個選項,您都可以在C++/WinRT 程式碼與尚未移植的任何 C++/CX 程式碼之間進行相互操作 (雙向)。
注意
C++/CX 以及根命名空間 Windows 的 Windows SDK 宣告類型。 投影到 C++/WinRT 的 Windows 類型有與 Windows 類型相同的完整名稱,但它放在 C++ winrt 命名空間。 這些不同的命名空間,可讓您以自己的速度從 C++/CX 移植至 C++/WinRT。
逐步移植 XAML 專案
重要
對於使用 XAML 的專案,您所有的 XAML 頁面類型無論何時都必須完全是 C++/CX 或完全是 C++/WinRT。 您仍然可以在相同專案內的 XAML 頁面類型以外混用 C++/CX 和 C++/WinRT (在您的模型和 ViewModel 中,以及其他位置)。
在此案例中,我們建議的工作流程是建立新的 C++/WinRT 專案,並從 C++/CX 專案將原始程式碼和標記複製過去。 只要您所有的 XAML 頁面類型都是 C++/WinRT,您就可以使用 [專案]>[新增項目...]>[Visual C++]>[空白頁] 來新增 XAML 頁面。
或者,您可以使用 Windows 執行階段元件 (WRC),在移植 XAML C++/CX 專案時,將程式碼從中分解出來。
- 您可以建立新的 C++/CX WRC 專案,並盡可能將 C++/CX 程式碼移至該專案中,然後將 XAML 專案變更為 C++/WinRT。
- 或者,您可以建立新的 C++/WinRT WRC 專案,並將 XAML 專案保留為 C++/CX,然後開始將 C++/CX 移植到 C++/WinRT,並將產生的程式碼從 XAML 專案移至元件專案中。
- 您也可以在同個解決方案中,一起使用 C++/CX 元件專案與 C++/WinRT 元件專案,從應用程式專案中參照這兩個元件專案,並逐漸從一個專案移植到另一個專案。 同樣地,如需在相同專案中使用兩種語言投影的更多詳細資料,請參閱 C++/WinRT 與 C++/CX 之間的相互操作。
將 C++/CX 專案移植到 C++/WinRT 的首要步驟
無論您的移植策略為何 (一次性移植或逐步移植),首要步驟都是將您要移植的專案準備就緒。 以下將扼要重述我們在移植策略中,針對您首先應處理的專案類型及其設定方式所做的說明。
- 一次移植。 使用一個 C++/WinRT 專案範本,在 Visual Studio 中建立新的專案。 將檔案從 C++/CX 專案移至該新專案,並移植 C++/CX 原始程式碼。
- 逐步移植非 XAML 專案。 您可以選擇將 C++/WinRT 支援新增 C++/CX 專案 (請參閱取用 C++/CX 專案並新增 C++/WinRT 支援),然後逐步移植。 或者,您可以選擇建立新的 C++/WinRT 專案,並為其新增 C++/CX 支援 (請參閱取用 C++/WinRT 專案並新增 C++/CX 支援)、將檔案移過去,然後逐步移植。
- 逐步移植 XAML 專案。 建立新的 C++/WinRT 專案、將檔案移過去,然後逐步移植。 您的 XAML 頁面類型無論何時都必須完全是 C++/WinRT 或完全是 C++/CX。
無論您選擇哪一種移植策略,都適用本主題的其餘內容。 其中包含將原始程式碼從 C++/CX 移植到 C++/WinRT 所牽涉到的技術詳細資料目錄。 如果您要逐步移植,則建議您也參閱 C++/WinRT 與 C++/CX 之間的相互操作和 C++/WinRT 與 C++/CX 之間的非同步和相互操作。
檔案命名慣例
XAML 標記檔案
檔案原點 | C++/CX | C++/WinRT |
---|---|---|
開發人員 XAML 檔案 | MyPage.xaml MyPage.xaml.h MyPage.xaml.cpp |
MyPage.xaml MyPage.h MyPage.cpp MyPage.idl (請參閱下方) |
產生的 XAML 檔案 | MyPage.xaml.g.h MyPage.xaml.g.hpp |
MyPage.xaml.g.h MyPage.xaml.g.hpp MyPage.g.h |
請注意,C++/WinRT 會從 *.h
和 *.cpp
檔案名稱中移除 .xaml
。
C++/WinRT 會新增其他開發人員檔案,即 Midl 檔案 (.idl)。 C++/CX 會在內部自動產生此檔案,並在其中新增每個公開和受保護的成員。 在 C++/WinRT 中,您會自行新增和撰寫檔案。 如需詳細資料、程式碼範例,以及撰寫 IDL 的逐步說明,請參閱 XAML 控制項;繫結至 C++/WinRT 屬性。
執行階段類別
C++/CX 不會在標頭檔的名稱上強加限制;通常會將多個執行階段類別定義放入單一標頭檔中 (尤其適用於小型類別)。 但是 C++/WinRT 會要求每個執行階段類別有自己的標頭檔,並以類別名稱命名。
C++/CX | C++/WinRT |
---|---|
Common.href class A { ... } ref class B { ... } |
Common.idlruntimeclass A { ... } runtimeclass B { ... } |
A.hnamespace implements { struct A { ... }; } |
|
B.hnamespace implements { struct B { ... }; } |
在 C++/CX 中,對 XAML 自訂控制項使用不同命名的標頭檔較不常見 (但仍屬合法)。 您必須將這些標頭檔重新命名,以符合類別名稱。
C++/CX | C++/WinRT |
---|---|
A.xaml<Page x:Class="LongNameForA" ...> |
A.xaml<Page x:Class="LongNameForA" ...> |
A.hpartial ref class LongNameForA { ... } |
LongNameForA.hnamespace implements { struct LongNameForA { ... }; } |
標頭檔需求
C++/Cx 不會要求您包含任何特殊的標頭檔,因為它會從 .winmd
檔案在內部自動產生標頭檔。 在 C++/CX 中,通常會針對依名稱取用的命名空間使用 using
指示詞。
using namespace Windows::Media::Playback;
String^ NameOfFirstVideoTrack(MediaPlaybackItem^ item)
{
return item->VideoTracks->GetAt(0)->Name;
}
using namespace Windows::Media::Playback
指示詞可讓我們在沒有命名空間前置詞的情況下撰寫 MediaPlaybackItem
。 我們也觸及了 Windows.Media.Core
命名空間,因為 item->VideoTracks->GetAt(0)
會傳回 Windows.Media.Core.VideoTrack。 但我們不必在任何地方輸入名稱 VideoTrack,因此不需要 using Windows.Media.Core
指示詞。
但是 C++/WinRT 會要求您包含對應至您用的每個命名空間的標頭檔,即使您未替該檔案命名亦然。
#include <winrt/Windows.Media.Playback.h>
#include <winrt/Windows.Media.Core.h> // !!This is important!!
using namespace winrt;
using namespace Windows::Media::Playback;
winrt::hstring NameOfFirstVideoTrack(MediaPlaybackItem const& item)
{
return item.VideoTracks().GetAt(0).Name();
}
另一方面,即使 MediaPlaybackItem.AudioTracksChanged 事件的類型為 TypedEventHandler<MediaPlaybackItem, Windows.Foundation.Collections.IVectorChangedEventArgs>,我們也不需要包含 winrt/Windows.Foundation.Collections.h
,因為我們不會使用該事件。
C++/WinRT 也會要求您包含 XAML 標記所用命名空間的標頭檔。
<!-- MainPage.xaml -->
<Rectangle Height="400"/>
使用 Rectangle 類別表示您必須新增此 include。
// MainPage.h
#include <winrt/Windows.UI.Xaml.Shapes.h>
如果您忘記標頭檔,則會順利編譯所有項目,但您會因為遺漏 consume_
類別而收到連結器錯誤。
參數傳遞
撰寫 C++/CX 原始程式碼時,會傳遞 C++/CX 類型作為揚抑符 (^) 參考的函式參數。
void LogPresenceRecord(PresenceRecord^ record);
在 C++/WinRT 中,針對同步函式,依預設應該使用 const&
參數。 如此可避免複本以及連鎖額外負荷。 但您的協同程式應該使用透過值傳遞,以確保他們透過值擷取,並避免存留期問題 (如需詳細資訊,請參閱使用 C++/WinRT 進行並行和非同步作業)。
void LogPresenceRecord(PresenceRecord const& record);
IASyncAction LogPresenceRecordAsync(PresenceRecord const record);
C++/WinRT 物件基本上是一個值,用於保留介面指標到支援 Windows 執行階段物件。 當您複製 C++/WinRT 物件時,編譯器會複製封裝的介面指標,遞增其參考次數。 最終複本的破壞會導致參考次數遞減。 因此,只有在必要時才會產生複本的額外負荷。
變數和欄位參考資料
撰寫 C++/CX 原始程式碼時,使用揚抑符(^) 變數參考 Windows 執行階段物件,以及箭頭 (->) 運算子以取值控制帽變數。
IVectorView<User^>^ userList = User::Users;
if (userList != nullptr)
{
for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
...
藉由移除其控制帽,並將箭頭運算子 (->) 變更為點運算子 (.),對於移植到對等項目 C++/WinRT 程式碼有很大的幫助。 C++/WinRT 投影類型為值,而非指標。
IVectorView<User> userList = User::Users();
if (userList != nullptr)
{
for (UINT32 iUser = 0; iUser < userList.Size(); ++iUser)
...
C++/CX hat (^) 參考的預設建構函式會將其初始化為 null。 以下是 C++/CX 程式碼範例,我們在其中建立了一個正確類型的變數/欄位,但尚未初始化。 換句話說,其最初並非是參考到 TextBlock;我們打算稍後再指派一個參考。
TextBlock^ textBlock;
class MyClass
{
TextBlock^ textBlock;
};
如需了解 C++/WinRT 中的對等項目,請參閱延遲初始化。
屬性
C++/CX 語言擴充功能包括了屬性的概念。 當撰寫 C++/CX 原始程式碼時,可以如欄位一般存取其屬性。 標準 C++ 不具屬性的概念,因此在 C++/WinRT 中,您要呼叫 get 和 set 函式。
在接下來的範例中,XboxUserId、UserState、PresenceDeviceRecords,和 Size 都是屬性。
從屬性擷取一個值
以下是如何使用 C++/CX 取得屬性值的方法。
void Sample::LogPresenceRecord(PresenceRecord^ record)
{
auto id = record->XboxUserId;
auto state = record->UserState;
auto size = record->PresenceDeviceRecords->Size;
}
對等項目 C++/WinRT 原始程式碼呼叫與屬性具有相同名稱,但不含任何參數的函式。
void Sample::LogPresenceRecord(PresenceRecord const& record)
{
auto id = record.XboxUserId();
auto state = record.UserState();
auto size = record.PresenceDeviceRecords().Size();
}
請注意,PresenceDeviceRecords 函式傳回 Windows 執行階段物件,其本身有一個 Size 函式。 由於傳回的物件也是 C++/WinRT 投影類型,因此我們使用點運算子取值以呼叫 Size。
將屬性設定為新值
將屬性設定為新值,按照類似的模式。 首先要看 C++/CX。
record->UserState = newValue;
若要執行 C++/WinRT 中的對等項目,請您呼叫具有與屬性相同名稱的函式,並傳遞引數。
record.UserState(newValue);
建立類別的執行個體
透過控制代碼使用 C++/CX 物件,通常稱它為揚抑符 (^) 參考。 透過 ref new
關鍵字建立新的物件,依序呼叫 RoActivateInstance 以啟動新的執行階段類別執行個體。
using namespace Windows::Storage::Streams;
class Sample
{
private:
Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};
C++/WinRT 物件是值;讓您可以在堆疊上配置它,或做為物件的欄位。 您「從不」使用 ref new
(或 new
) 配置 C++/WinRT 物件。 幕後仍持續呼叫 RoActivateInstance。
using namespace winrt::Windows::Storage::Streams;
struct Sample
{
private:
Buffer m_gamerPicBuffer{ MAX_IMAGE_SIZE };
};
如果將資源初始化的成本相當高,則常會延遲初始化,直到有實際需求時再進行。 如先前所述,C++/CX hat (^) 參考的預設建構函式會將其初始化為 null。
using namespace Windows::Storage::Streams;
class Sample
{
public:
void DelayedInit()
{
// Allocate the actual buffer.
m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
}
private:
Buffer^ m_gamerPicBuffer;
};
已將相同的程式碼移植至 C++/WinRT。 請注意 std:: nullptr_t 建構函式的實作。 如需該建構函式的詳細資訊,請參閱延遲初始化。
using namespace winrt::Windows::Storage::Streams;
struct Sample
{
void DelayedInit()
{
// Allocate the actual buffer.
m_gamerPicBuffer = Buffer(MAX_IMAGE_SIZE);
}
private:
Buffer m_gamerPicBuffer{ nullptr };
};
預設建構函式對於集合的影響
C++ 集合類型會使用預設建構函式,這可能導致非預期的物件結構。
案例 | C++/CX | C++/WinRT (不正確) | C++/WinRT (正確) |
---|---|---|---|
本機變數,最初是空的 | TextBox^ textBox; |
TextBox textBox; // Creates a TextBox! |
TextBox textBox{ nullptr }; |
成員變數,最初是空的 | class C { TextBox^ textBox; }; |
class C { TextBox textBox; // Creates a TextBox! }; |
class C { TextBox textbox{ nullptr }; }; |
全域變數,最初是空的 | TextBox^ g_textBox; |
TextBox g_textBox; // Creates a TextBox! |
TextBox g_textBox{ nullptr }; |
空白參考的向量 | std::vector<TextBox^> boxes(10); |
// Creates 10 TextBox objects! std::vector<TextBox> boxes(10); |
std::vector<TextBox> boxes(10, nullptr); |
在對應中設定值 | std::map<int, TextBox^> boxes; boxes[2] = value; |
std::map<int, TextBox> boxes; // Creates a TextBox at 2, // then overwrites it! boxes[2] = value; |
std::map<int, TextBox> boxes; boxes.insert_or_assign(2, value); |
空白參考的陣列 | TextBox^ boxes[2]; |
// Creates 2 TextBox objects! TextBox boxes[2]; |
TextBox boxes[2] = { nullptr, nullptr }; |
配對 | std::pair<TextBox^, String^> p; |
// Creates a TextBox! std::pair<TextBox, String> p; |
std::pair<TextBox, String> p{ nullptr, nullptr }; |
深入了解空白參考的集合
每當您的 C++/CX 中有 Platform::Array^ 時 (請參閱移植 Platform::Array^),您可以選擇將其移植到 C++/WinRT 中的 std::vector (事實上,任何連續的容器皆可),而不將其保留為陣列。 選擇 std::vector 有某些優點。
例如,雖然在建立空白參考固定大小的向量時有速記 (請參閱上表),但在建立空白參考的陣列時則沒有這類速記。 您必須針對陣列中的每個元素重複 nullptr
。 如果您的元素過少,則會有額外的預設建構元素。
針對向量,您可以在初始化時為其填入空白參考 (如上表所示),或者,您可以在初始化後,使用如下列的程式碼填入空白參考。
std::vector<TextBox> boxes(10); // 10 default-constructed TextBoxes.
boxes.resize(10, nullptr); // 10 empty references.
關於 std::map 範例的詳細資訊
std::map 的 []
下標運算子運作方式如下。
- 如果在對應中找到索引鍵,則會傳回現有值的參考 (您可加以覆寫)。
- 如果在對應中找不到索引鍵,則在由索引鍵 (如可移動,則已移動) 和「預設建構值」組成的對應中建立新項目,並傳回此值的參考 (您可接著覆寫)。
換句話說,[]
運算子一律會在對應中建立專案。 這不同於 C#、Java 和 JavaScript。
從基礎執行階段類別轉換至衍生的執行階段類別
讓您所知的 reference-to-base 參考到衍生類型的物件,算是相當常見。 在 C++/CX 中,要使用 dynamic_cast
將 reference-to-base 轉換為 reference-to-derived。 dynamic_cast
實際上只是對 QueryInterface 的隱藏呼叫。 這是一個典型的範例,您在處理相依性屬性變更事件,而且希望從 DependencyObject 轉換回擁有相依性屬性的實際類型。
void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject^ d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };
if (theControl != nullptr)
{
// succeeded ...
}
}
對等項目 C++/WinRT 程式碼藉由呼叫 IUnknown::try_as 函式,取代 dynamic_cast
,該函式會封裝 QueryInterface。 您也可以選擇呼叫 IUnknown::as,如果未傳回查詢所需的介面 (要求類型的預設介面),則會擲回例外狀況。 這是 C++/WinRT 程式碼範例。
void BgLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
if (BgLabelControlApp::BgLabelControl theControl{ d.try_as<BgLabelControlApp::BgLabelControl>() })
{
// succeeded ...
}
try
{
BgLabelControlApp::BgLabelControl theControl{ d.as<BgLabelControlApp::BgLabelControl>() };
// succeeded ...
}
catch (winrt::hresult_no_interface const&)
{
// failed ...
}
}
衍生類別
為了從執行階段類別衍生,基底類別必須「可組合」。 C++/CX 不要求您採取任何特殊步驟,即可讓類別變為可組合,而 C++/WinRT 則會要求您採取步驟。 您可使用未密封的關鍵字,指出您希望類別可作為基底類別使用。
unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
...
}
runtimeclass DerivedPage : BasePage
{
...
}
在實作標頭類別中,您必須先包含基底類別標頭檔,才可包含衍生類別的自動產生標頭。 否則,您會收到錯誤,例如「此類型當作運算式使用並不合法」。
// DerivedPage.h
#include "BasePage.h" // This comes first.
#include "DerivedPage.g.h" // Otherwise this header file will produce an error.
namespace winrt::MyNamespace::implementation
{
struct DerivedPage : DerivedPageT<DerivedPage>
{
...
}
}
使用委派的事件處理
以下是在 C++/CX 中處理事件的一般範例,這種情形下,是使用 lambda 函式做為委派。
auto token = myButton->Click += ref new RoutedEventHandler([=](Platform::Object^ sender, RoutedEventArgs^ args)
{
// Handle the event.
// Note: locals are captured by value, not reference, since this handler is delayed.
});
這是 C++/WinRT 中的對等項目。
auto token = myButton().Click([=](IInspectable const& sender, RoutedEventArgs const& args)
{
// Handle the event.
// Note: locals are captured by value, not reference, since this handler is delayed.
});
您可以選擇實作委派做為可用功能,或做為指標成員函式,而不是 lambda 函式。 如需詳細資訊,請參閱透過使用 C++/WinRT 中的委派來處理事件。
如果您正從在內部使用事件和委派的 C++/CX 程式碼基底進行移植 (並非所有二進位檔案),則 winrt::delegate 可協助您在 C++/WinRT 中複寫該模式。 另請參閱參數化委派、簡單的訊號,以及專案中的回呼。
撤銷委派
您在 C++/CX 中使用 -=
運算子以撤銷前一個事件註冊。
myButton->Click -= token;
這是 C++/WinRT 中的對等項目。
myButton().Click(token);
如需詳細資訊以及選項,請參閱撤銷已註冊的委派。
Box 處理和 Unbox 處理
C++/CX 會自動將純量 Box 處理為物件。 C++/WinRT 會要求您明確地呼叫 winrt::box_value 函式。 這兩種語言都需要您明確地進行 Unbox 處理。 請參閱使用 C++/WinRT 進行 Box 處理和 Unbox 處理。
在後續表格中,我們將使用下列定義。
C++/CX | C++/WinRT |
---|---|
int i; |
int i; |
String^ s; |
winrt::hstring s; |
Object^ o; |
IInspectable o; |
作業 | C++/CX | C++/WinRT |
---|---|---|
Box 處理 | o = 1; o = "string"; |
o = box_value(1); o = box_value(L"string"); |
Unbox 處理 | i = (int)o; s = (String^)o; |
i = unbox_value<int>(o); s = unbox_value<winrt::hstring>(o); |
如果您嘗試將 null 指標 Unbox 處理為某個實值類型,則 C++/CX 和 C# 會引發例外狀況。 C++/WinRT 會將此視為程式設計錯誤,並且毀損。 在 C++/WinRT 中,如果您想處理物件不是您所認為類型的情況,請使用 winrt::unbox_value_or 函式。
案例 | C++/CX | C++/WinRT |
---|---|---|
進行已知整數的 Unbox 處理 | i = (int)o; |
i = unbox_value<int>(o); |
如果 o 為 null | Platform::NullReferenceException |
毀損 |
如果 o 不是已 Box 處理的 int | Platform::InvalidCastException |
毀損 |
進行 int 的 Unbox 處理,若為 null 則使用遞補;若為其他任何項目則會毀損 | i = o ? (int)o : fallback; |
i = o ? unbox_value<int>(o) : fallback; |
可能的話,進行 int 的 Unbox 處理;其他任何項目使用遞補 | auto box = dynamic_cast<IBox<int>^>(o); i = box ? box->Value : fallback; |
i = unbox_value_or<int>(o, fallback); |
進行字串的 Box 處理和 Unbox 處理
字串在某些方面是實值類型,而在其他方面則是參考類型。 C++/CX 和 C++/WinRT 會以不同的方式處理字串。
ABI 類型 HSTRING 是參考計數字串的指標。 但是它並非衍生自 IInspectable,因此在技術上並不是「物件」。 此外, null HSTRING 代表空字串。 將非衍生自 IInspectable 的項目包裝在 IReference<T>內,即可完成 Box 處理,而 Windows 執行階段會以 PropertyValue 物件形式提供標準實作 (自訂類型會回報為 PropertyType::OtherType)。
C++/CX 表示作為參考類型的 Windows 執行階段字串;而 C++/WinRT 會將字串投影為實值類型。 這表示已進行 Box 處理的 null 字串可以有不同的表示法 (取決於您達成的方式)。
此外,C++/CX 允許您對 null String^進行取值,在此情況下其行為就像字串 ""
。
行為 | C++/CX | C++/WinRT |
---|---|---|
宣告 | Object^ o; String^ s; |
IInspectable o; hstring s; |
字串類型類別 | 參考類型 | 值類型 |
null HSTRING 投影為 | (String^)nullptr |
hstring{} |
Null 和 "" 相同嗎? |
Yes | Yes |
Null 的有效性 | s = nullptr; s->Length == 0 (有效) |
s = hstring{}; s.size() == 0 (有效) |
如果將 Null 字串指派給物件 | o = (String^)nullptr; o == nullptr |
o = box_value(hstring{}); o != nullptr |
如果將 "" 指派給物件 |
o = ""; o == nullptr |
o = box_value(hstring{L""}); o != nullptr |
基本 Box 處理和 Unbox 處理。
作業 | C++/CX | C++/WinRT |
---|---|---|
進行字串的 Box 處理 | o = s; 空字串會變成 nullptr。 |
o = box_value(s); 空字串會變成非 Null 物件。 |
進行已知字串的 Unbox 處理 | s = (String^)o; Null 物件會變成空字串。 InvalidCastException (如果不是字串)。 |
s = unbox_value<hstring>(o); Null 物件損毀。 如果不是字串,則會損毀。 |
將可能的字串進行 Unbox 處理 | s = dynamic_cast<String^>(o); Null 物件或非字串會變成空字串。 |
s = unbox_value_or<hstring>(o, fallback); Null 或非字串會變成遞補。 保留空字串。 |
並行和非同步作業
平行模式程式庫 (PPL) (例如 concurrency::task) 已更新為支援 C++/CX hat (^) 參考。
對於 C++/WinRT,您應該改用協同程式和 co_await
。 如需詳細資訊和程式碼範例,請參閱使用 C++/WinRT 的並行和非同步作業。
取用 XAML 標記中的物件
在 C++/CX 專案中,您可以使用來自 XAML 標記的私有成員和具名元素。 但是在 C++/WinRT 中,使用 XAML {x:Bind} 標記延伸 取用的所有實體都必須公開於 IDL 中。
此外,布林值的繫結會在 C++/CX 中顯示 true
或 false
,但是在 C++/WinRT 中顯示 Windows.Foundation.IReference`1<Boolean>。
如需詳細資訊和程式碼範例,請參閱使用標記中的物件。
將 C++/CX 平台類型對應至 C++/WinRT 類型
C++/CX 在平台命名空間中提供幾種資料類型。 這些類型不是標準 C++,因此您只能在啟用 Windows 執行階段語言擴充功能時使用它們 (Visual Studio 專案屬性 C/C++>一般> 使用 Windows 執行階段擴充功能>是 (/ZW))。 下列表格有助於您從平台類型移植至 C++/WinRT 中的對等項目。 完成後,由於 C++/WinRT 是標準的 C++,您可以關閉 /ZW
選項。
C++/CX | C++/WinRT |
---|---|
Platform::Agile^ | winrt::agile_ref |
Platform::Array^ | 請參閱移植 Platform::Array^ |
Platform::Exception^ | winrt::hresult_error |
Platform::InvalidArgumentException^ | winrt::hresult_invalid_argument |
Platform::Object^ | winrt::Windows::Foundation::IInspectable |
Platform::String^ | winrt::hstring |
將 Platform::Agile^ 移植到 winrt::agile_ref
C++/CX 中的 Platform::Agile^ 類型代表可從任何執行緒存取的 Windows 執行階段類別。 C++/WinRT 對等項目是 winrt::agile_ref。
在 C++/CX 中。
Platform::Agile<Windows::UI::Core::CoreWindow> m_window;
在 C++/WinRT 中。
winrt::agile_ref<Windows::UI::Core::CoreWindow> m_window;
移植 Platform::Array^
在 C++/CX 要求您使用陣列的情況下,C++/WinRT 可讓您使用任何連續的容器。 若要了解 std::vector 為何是不錯的選擇,請參閱預設建構函式對於集合的影響。
因此,每當 C++/CX 中有 Platform::Array^ 時,使用初始設定式清單 (std::array 或 std::vector) 都將是您的移植選項之一。 如需詳細資訊以及程式碼範例,請參閱標準初始化清單和標準陣列和向量。
將 Platform::Exception^ 移植到 winrt::hresult_error
Windows 執行階段 API 傳回非 S_OK HRESULT 時,C++/CX 中產生 Platform::Exception^ 類型。 C++/WinRT 對等項目是 winrt::hresult_error。
若要移植至 C++/WinRT,請將所有使用 Platform::Exception^ 的程式碼變更為使用 winrt::hresult_error。
在 C++/CX 中。
catch (Platform::Exception^ ex)
在 C++/WinRT 中。
catch (winrt::hresult_error const& ex)
C++/WinRT 提供這些例外類別。
例外狀況類型 | 基底類別 | HRESULT |
---|---|---|
winrt::hresult_error | 呼叫 hresult_error::to_abi | |
winrt::hresult_access_denied | winrt::hresult_error | E_ACCESSDENIED |
winrt::hresult_canceled | winrt::hresult_error | ERROR_CANCELLED |
winrt::hresult_changed_state | winrt::hresult_error | E_CHANGED_STATE |
winrt::hresult_class_not_available | winrt::hresult_error | CLASS_E_CLASSNOTAVAILABLE |
winrt::hresult_illegal_delegate_assignment | winrt::hresult_error | E_ILLEGAL_DELEGATE_ASSIGNMENT |
winrt::hresult_illegal_method_call | winrt::hresult_error | E_ILLEGAL_METHOD_CALL |
winrt::hresult_illegal_state_change | winrt::hresult_error | E_ILLEGAL_STATE_CHANGE |
winrt::hresult_invalid_argument | winrt::hresult_error | E_INVALIDARG |
winrt::hresult_no_interface | winrt::hresult_error | E_NOINTERFACE |
winrt::hresult_not_implemented | winrt::hresult_error | E_NOTIMPL |
winrt::hresult_out_of_bounds | winrt::hresult_error | E_BOUNDS |
winrt::hresult_wrong_thread | winrt::hresult_error | RPC_E_WRONG_THREAD |
請注意,每個類別 (透過 hresult_error 基底類別) 提供一個 to_abi 函式,傳回錯誤的 HRESULT,以及一個訊息函式,傳回該 HRESULT 的字串表示方法。
以下是在 C++/CX 中擲回例外狀況的範例。
throw ref new Platform::InvalidArgumentException(L"A valid User is required");
以及 C++/WinRT 中的對等項目。
throw winrt::hresult_invalid_argument{ L"A valid User is required" };
將 Platform::Object^ 移植到 winrt::Windows::Foundation::IInspectable
就像所有的 C++/WinRT 類型,winrt::Windows::Foundation::IInspectable 是一種值類型。 以下是您如何將該類型的變數初始化為 null 的方法。
winrt::Windows::Foundation::IInspectable var{ nullptr };
將 Platform::String^ 移植到 winrt::hstring
Platform::String^ 等同於 Windows 執行階段 HSTRING ABI 類型。 對於 C++/WinRT,對等項目是 winrt::hstring。 但使用 C++/WinRT,您可以使用 C++ 標準程式庫寬字串類型 (例如 std::wstring) 來呼叫 Windows 執行階段 API,及/或寬字串常值。 如需詳細資訊和程式碼範例,請參閱 C++/WinRT 中的字串處理。
使用 C++/CX,您可以存取 Platform::String::Data 屬性,來擷取字串作為 C-style const wchar_t* 陣列 (例如,將它傳遞至 std::wcout)。
auto var{ titleRecord->TitleName->Data() };
若要使用 C++/WinRT 進行相同的動作,您可以使用 hstring::c_str 函式,取得 null 終止的 C 式字串版本,就如同您可以從 std::wstring 取得一樣。
auto var{ titleRecord.TitleName().c_str() };
實作採用或傳回字串的 API 時,您通常會變更任何使用 Platform::String^ 來使用 winrt::hstring 的 C++/CX 程式碼。
以下是採用字串的 C++/CX API 範例。
void LogWrapLine(Platform::String^ str);
對於 C++/WinRT,您可以在 MIDL 3.0 中宣告這類 API。
// LogType.idl
void LogWrapLine(String str);
然後,C++/WinRT 工具鏈將為您產生看起來像這樣的原始程式碼。
void LogWrapLine(winrt::hstring const& str);
ToString()
C++/CX 類型會提供 Object::ToString 方法。
int i{ 2 };
auto s{ i.ToString() }; // s is a Platform::String^ with value L"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++/CX | String^ result = "hello, " + intValue.ToString(); |
String^ result = "status: " + status.ToString(); |
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 多載,否則會收到編譯器錯誤。
字串建立
C++/CX 和 C++/WinR 會延遲為標準 std::wstringstream 以便建立字串。
作業 | C++/CX | C++/WinRT |
---|---|---|
附加字串,保留 null | stream.print(s->Data(), s->Length); |
stream << std::wstring_view{ s }; |
附加字串,在第一個 null 停止 | stream << s->Data(); |
stream << s.c_str(); |
擷取結果 | ws = stream.str(); |
ws = stream.str(); |
更多範例
在下列範例中,ws 是 std::wstring類型的變數。 此外,雖然 C++/CX 可以從 8 位元字串建構 Platform::String,但是 C++/WinRT 不會這麼做。
作業 | C++/CX | C++/WinRT |
---|---|---|
從常值建構字串 | String^ s = "hello"; String^ s = L"hello"; |
// winrt::hstring s{ "hello" }; // Doesn't compile winrt::hstring s{ L"hello" }; |
從 std:: wstring 轉換,保留 null | String^ s = ref new String(ws.c_str(), (uint32_t)ws.size()); |
winrt::hstring s{ ws }; s = winrt::hstring(ws); // s = ws; // Doesn't compile |
從 std::wstring 轉換,在第一個 null 停止 | String^ s = ref new String(ws.c_str()); |
winrt::hstring s{ ws.c_str() }; s = winrt::hstring(ws.c_str()); // s = ws.c_str(); // Doesn't compile |
轉換為 std:: wstring,保留 null | std::wstring ws{ s->Data(), s->Length }; ws = std::wstring(s>Data(), s->Length); |
std::wstring ws{ s }; ws = s; |
轉換為 std::wstring,在第一個 null 停止 | std::wstring ws{ s->Data() }; ws = s->Data(); |
std::wstring ws{ s.c_str() }; ws = s.c_str(); |
將常值傳遞至方法 | Method("hello"); Method(L"hello"); |
// Method("hello"); // Doesn't compile Method(L"hello"); |
將 std:: wstring 傳遞至方法 | Method(ref new String(ws.c_str(), (uint32_t)ws.size()); // Stops on first null |
Method(ws); // param::winrt::hstring accepts std::wstring_view |