C++/WinRT 與 C++/CX 之間的互通性

閱讀本主題之前,請先參閱從 C++/CX 移至 C++/WinRT 主題中的資訊。 該主題介紹兩個主要策略選項,可將您的 C++/CX 專案移植到 C++/WinRT

  • 一次移植整個專案。 就不太大的專案而言,這是最簡單的選項。 如果您有 Windows 執行階段元件專案,則此策略是您唯一的選項。
  • 逐步移植專案 (可能因程式碼基底的大小或複雜度而必須採用此選項)。 但在採用此策略時,您所執行的移植程序必須在一段時間內讓 C++/CX 和 C++/WinRT 程式碼並存於相同的專案中。 針對 XAML 專案,您的 XAML 頁面類型無論何時都必須完全是 C++/WinRT 完全是 C++/CX。

此相互操作主題適用於第二種策略—也就是您需要逐步移植專案的案例。 本主題說明各種形式的協助程式函式,可用來將 C++/CX 物件 (和其他類型)轉換成相同專案中的 C++/WinRT 物件 (反之亦然)。

當您從 C++/CX 逐步將程式碼移植到 C++/WinRT 時,這些 Helper 函式會非常有用。 或者,您也可以選擇在相同的專案中同時使用 C++/WinRT 和 C++/CX 語言投影 (無論您是否要移植),然後使用這些 Helper 函式在兩者之間相互操作。

在閱讀本主題之後,如需如何在相同專案中支援並存的 PPL 工作和協同程式 (例如,從工作鏈結呼叫協同程式) 的說明資訊和程式碼範例,請參閱更進階的主題 C++/WinRT 與 C++/CX 之間的非同步和相互操作

from_cxto_cx 函式

以下是名為 interop_helpers.h 之標頭檔的原始程式碼清單,其中包含多個轉換協助程式函式。 當您逐步移植專案時,會有部分仍留在 C++/CX 中,有部分已移植到 C++/WinRT。 您可以使用這些協助程式函式,在這兩個部分之間的界限點上,於 C++/CX 和 C++/WinRT 之間雙向轉換專案中的物件 (及其他類型)。

在所列程式碼後面的各節會說明這協助程式函式,以及如何在專案中建立和使用標頭檔。

// interop_helpers.h
#pragma once

template <typename T>
T from_cx(Platform::Object^ from)
{
    T to{ nullptr };

    if (from != nullptr)
    {
        winrt::check_hresult(reinterpret_cast<::IUnknown*>(from)
            ->QueryInterface(winrt::guid_of<T>(), winrt::put_abi(to)));
    }

    return to;
}

template <typename T>
T^ to_cx(winrt::Windows::Foundation::IUnknown const& from)
{
    return safe_cast<T^>(reinterpret_cast<Platform::Object^>(winrt::get_abi(from)));
}

inline winrt::hstring from_cx(Platform::String^ const& from)
{
    return reinterpret_cast<winrt::hstring&>(const_cast<Platform::String^&>(from));
}

inline Platform::String^ to_cx(winrt::hstring const& from)
{
    return reinterpret_cast<Platform::String^&>(const_cast<winrt::hstring&>(from));
}

inline winrt::guid from_cx(Platform::Guid const& from)
{
    return reinterpret_cast<winrt::guid&>(const_cast<Platform::Guid&>(from));
}

inline Platform::Guid to_cx(winrt::guid const& from)
{
    return reinterpret_cast<Platform::Guid&>(const_cast<winrt::guid&>(from));
}

from_cx 函式

from_cx Helper 函式會將 C++/CX 物件轉換為對等的 C++/WinRT 物件。 函式將 C++/CX 物件轉換為其基礎 IUnknown 介面指標。 然後它在該指標上呼叫 QueryInterface,以查詢 C++/WinRT 物件的預設介面。 QueryInterface 是 Windows 執行階段應用程式二進位介面 (ABI),相當於 C++/CX safe_cast 擴充功能。 而且,winrt::put_abi 函式會擷取 C++/WinRT 物件之基礎 IUnknown 介面指標的位址,使其可設為另一個值。

to_cx 函式

to_cx Helper 函式會將 C++/WinRT 物件轉換為對等的 C++/CX 物件。 winrt::get_abi 函式將指標擷取至 C++/WinRT 物件的基礎 IUnknown 介面。 此函式會將該指標轉換為 C++/CX 物件,然後使用 C++/CX safe_cast 擴充功能來查詢要求的 C++/CX 類型。

interop_helpers.h 標頭檔

若要在您的專案中使用協助程式函式,請遵循下列步驟。

  • 將新的標頭檔 (.h) 項目新增至您的專案,並將其命名為 interop_helpers.h
  • interop_helpers.h 的內容取代為上方的程式碼清單。
  • 將這些包含項目新增至 pch.h
// pch.h
...
#include <unknwn.h>
// Include C++/WinRT projected Windows API headers here.
...
#include <interop_helpers.h>

取用 C++/CX 專案並新增 C++/WinRT 支援

本節說明您決定要取用現有的 C++/CX 專案、在其中新增 C++/WinRT 支援,並在該處執行移植時,所應執行的動作。 另請參閱 C++/WinRT 的 Visual Studio 支援

若要在 C++/CX 專案中混用 C++/CX 和 C++/WinRT—包括在專案中使用 from_cxto_cx 協助程式函式—您必須手動將 C++/WinRT 支援新增至專案。

首先,請在 Visual Studio 中開啟 C++/CX 專案,並確認專案屬性 [一般]>[目標平台版本] 設為 10.0.17134.0 (Windows 10,1803 版) 或更高版本。

安裝 C++/WinRT NuGet 套件

Microsoft.Windows.CppWinRT NuGet 套件提供 C++/WinRT 組建支援 (MSBuild 屬性和目標)。 若要安裝,請按一下 專案>管理 NuGet 套件...>瀏覽,在搜尋方塊中輸入或貼上 Microsoft.Windows.CppWinRT ,選取搜尋結果中的項目,然後按一下安裝以安裝該專案的套件。

重要

安裝 C++/WinRT NuGet 套件會導致在專案中關閉 C++/CX 的支援。 如果您要進行一次性移植,最好讓該支援關閉,讓組建訊息協助您尋找 (及移植) C++/CX 上的所有相依性 (最終將純 C++/CX 專案轉換為純 C++/WinRT 專案)。 但請參閱下一節,以取得重新加以開啟的相關資訊。

重新開啟 C++/CX 支援

如果您要進行一次性移植,則不需要這麼做。 但是,如果您需要逐步移植,此時您必須在專案中重新開啟 C++/CX 支援。 在專案屬性中:[C/C++]> [一般]> [使用 Windows 執行階段擴充功能]> [是 \(/ZW\)\]

或者,如果除此之外還有 XAML 專案,您可以在 Visual Studio 中使用 C++/WinRT 專案屬性頁來新增 C++/CX 支援。 在專案屬性中,瀏覽至 [通用屬性]>[C++/WinRT]>[專案語言]>[C++/CX]。 這麼做會將下列屬性新增至您的 .vcxproj 檔案。

  <PropertyGroup Label="Globals">
    <CppWinRTProjectLanguage>C++/CX</CppWinRTProjectLanguage>
  </PropertyGroup>

重要

每當您需要透過建置將 Midl 檔案 (.idl) 的內容處理為 Stub 檔案時,您就必須將 [專案語言] 變更回 [C++/WinRT]。 在組建產生了這些 Stub 之後,請將 [專案語言] 變更回 [C++/CX]

如需類似自訂選項 (可微調 cppwinrt.exe 工具的行為) 清單,請參閱 Microsoft.Windows.CppWinRT NuGet 套件讀我檔案

包含 C++/WinRT 標頭檔

您至少應在先行編譯的標頭檔 (通常是 pch.h) 中包含 winrt/base.h,如下所示。

// pch.h
...
#include <winrt/base.h>
...

但在絕大多數的情況下,您都需要 winrt::Windows::Foundation 命名空間中的類型。 而且,您可能已經知道您所需的其他命名空間。 因此,請包含對應至這些命名空間的 C++/WinRT 投影 Windows API 標頭 (此時無須明確包含 winrt/base.h,因為其會自動包含在內)。

// pch.h
...
#include <winrt/Windows.Foundation.h>
// Include any other C++/WinRT projected Windows API headers here.
...

另請參閱下一節中的程式碼範例 (取用 C++/WinRT 專案並新增 C++/CX 支援),以了解使用命名空間別名 namespace cxnamespace winrt 的技術。 該技術也可讓您處理 C++/WinRT 投影與 C++/CX 投影之間可能產生的命名空間衝突。

interop_helpers.h 新增至專案

現在,您可以將 from_cxto_cx 函式新增至您的 C++/CX 專案。 如需其操作方式的指示,請參閱前述的 from_cxto_cx 函式一節。

取用 C++/WinRT 專案並新增 C++/CX 支援

本節說明您決定要建立新的 C++/WinRT 專案並在該處執行移植時所應執行的動作。

若要在 C++/WinRT 專案中混用 C++/WinRT 和 C++/CX—包括在專案中使用 from_cxto_cx 協助程式函式—您必須手動將 C++/CX 支援新增至專案。

  • 使用一個 C++/WinRT 專案範本,在 Visual Studio 中建立新的 C++/WinRT 專案 (請參閱 C++/WinRT 的 Visual Studio 支援)。
  • 開啟 C++/CX 的專案支援。 在專案屬性中:[C/C++]> [一般]> [使用 Windows 執行階段擴充功能]> [是 \(/ZW\)\]

顯示使用中的兩個 Helper 函式的範例 C++/WinRT 專案

在本節中,您可以建立範例 C++/WinRT 專案,以示範如何使用 from_cxto_cx。 該專案也說明您可以如何對不同的程式碼孤立區使用命名空間別名,以處理 C++/WinRT 投影與 C++/CX 投影之間可能產生的命名空間衝突。

  • 建立 Visual C++>Windows 通用>核心應用程式 (C++/WinRT) 專案。
  • 在專案屬性中:[C/C++]> [一般]> [使用 Windows 執行階段擴充功能]> [是 \(/ZW\)\]
  • interop_helpers.h 新增至專案。 如需其操作方式的指示,請參閱前述的 from_cxto_cx 函式一節。
  • App.cpp 的內容取代為下方的程式碼清單。
  • 建置並執行。

WINRT_ASSERT 是巨集定義,而且會發展為 _ASSERTE

// App.cpp
#include "pch.h"
#include <sstream>

namespace cx
{
    using namespace Windows::Foundation;
}

namespace winrt
{
    using namespace Windows;
    using namespace Windows::ApplicationModel::Core;
    using namespace Windows::Foundation;
    using namespace Windows::Foundation::Numerics;
    using namespace Windows::UI;
    using namespace Windows::UI::Core;
    using namespace Windows::UI::Composition;
}

struct App : winrt::implements<App, winrt::IFrameworkViewSource, winrt::IFrameworkView>
{
    winrt::CompositionTarget m_target{ nullptr };
    winrt::VisualCollection m_visuals{ nullptr };
    winrt::Visual m_selected{ nullptr };
    winrt::float2 m_offset{};

    winrt::IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(winrt::CoreApplicationView const &)
    {
    }

    void Load(winrt::hstring const&)
    {
    }

    void Uninitialize()
    {
    }

    void Run()
    {
        winrt::CoreWindow window = winrt::CoreWindow::GetForCurrentThread();
        window.Activate();

        winrt::CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(winrt::CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(winrt::CoreWindow const & window)
    {
        winrt::Compositor compositor;
        winrt::ContainerVisual root = compositor.CreateContainerVisual();
        m_target = compositor.CreateTargetForCurrentView();
        m_target.Root(root);
        m_visuals = root.Children();

        window.PointerPressed({ this, &App::OnPointerPressed });
        window.PointerMoved({ this, &App::OnPointerMoved });

        window.PointerReleased([&](auto && ...)
        {
            m_selected = nullptr;
        });
    }

    void OnPointerPressed(IInspectable const &, winrt::PointerEventArgs const & args)
    {
        winrt::float2 const point = args.CurrentPoint().Position();

        for (winrt::Visual visual : m_visuals)
        {
            winrt::float3 const offset = visual.Offset();
            winrt::float2 const size = visual.Size();

            if (point.x >= offset.x &&
                point.x < offset.x + size.x &&
                point.y >= offset.y &&
                point.y < offset.y + size.y)
            {
                m_selected = visual;
                m_offset.x = offset.x - point.x;
                m_offset.y = offset.y - point.y;
            }
        }

        if (m_selected)
        {
            m_visuals.Remove(m_selected);
            m_visuals.InsertAtTop(m_selected);
        }
        else
        {
            AddVisual(point);
        }
    }

    void OnPointerMoved(IInspectable const &, winrt::PointerEventArgs const & args)
    {
        if (m_selected)
        {
            winrt::float2 const point = args.CurrentPoint().Position();

            m_selected.Offset(
            {
                point.x + m_offset.x,
                point.y + m_offset.y,
                0.0f
            });
        }
    }

    void AddVisual(winrt::float2 const point)
    {
        winrt::Compositor compositor = m_visuals.Compositor();
        winrt::SpriteVisual visual = compositor.CreateSpriteVisual();

        static winrt::Color colors[] =
        {
            { 0xDC, 0x5B, 0x9B, 0xD5 },
            { 0xDC, 0xED, 0x7D, 0x31 },
            { 0xDC, 0x70, 0xAD, 0x47 },
            { 0xDC, 0xFF, 0xC0, 0x00 }
        };

        static unsigned last = 0;
        unsigned const next = ++last % _countof(colors);
        visual.Brush(compositor.CreateColorBrush(colors[next]));

        float const BlockSize = 100.0f;

        visual.Size(
        {
            BlockSize,
            BlockSize
        });

        visual.Offset(
        {
            point.x - BlockSize / 2.0f,
            point.y - BlockSize / 2.0f,
            0.0f,
        });

        m_visuals.InsertAtTop(visual);

        m_selected = visual;
        m_offset.x = -BlockSize / 2.0f;
        m_offset.y = -BlockSize / 2.0f;
    }
};

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    winrt::init_apartment();

    winrt::Uri uri(L"http://aka.ms/cppwinrt");
    std::wstringstream wstringstream;
    wstringstream << L"C++/WinRT: " << uri.Domain().c_str() << std::endl;

    // Convert from a C++/WinRT type to a C++/CX type.
    cx::Uri^ cx = to_cx<cx::Uri>(uri);
    wstringstream << L"C++/CX: " << cx->Domain->Data() << std::endl;
    ::OutputDebugString(wstringstream.str().c_str());

    // Convert from a C++/CX type to a C++/WinRT type.
    winrt::Uri uri_from_cx = from_cx<winrt::Uri>(cx);
    WINRT_ASSERT(uri.Domain() == uri_from_cx.Domain());
    WINRT_ASSERT(uri == uri_from_cx);

    winrt::CoreApplication::Run(winrt::make<App>());
}

重要 API