從 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 屬性

另請參閱將執行階段類別分解成 Midl 檔案 (.idl)

執行階段類別

C++/CX 不會在標頭檔的名稱上強加限制;通常會將多個執行階段類別定義放入單一標頭檔中 (尤其適用於小型類別)。 但是 C++/WinRT 會要求每個執行階段類別有自己的標頭檔,並以類別名稱命名。

C++/CX C++/WinRT
Common.h
ref class A { ... }
ref class B { ... }
Common.idl
runtimeclass A { ... }
runtimeclass B { ... }
A.h
namespace implements {
  struct A { ... };
}
B.h
namespace implements {
  struct B { ... };
}

在 C++/CX 中,對 XAML 自訂控制項使用不同命名的標頭檔較不常見 (但仍屬合法)。 您必須將這些標頭檔重新命名,以符合類別名稱。

C++/CX C++/WinRT
A.xaml
<Page x:Class="LongNameForA" ...>
A.xaml
<Page x:Class="LongNameForA" ...>
A.h
partial ref class LongNameForA { ... }
LongNameForA.h
namespace 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 函式。

在接下來的範例中,XboxUserIdUserStatePresenceDeviceRecords,和 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 中顯示 truefalse,但是在 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::arraystd::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();

更多範例

在下列範例中,wsstd::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

重要 API