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 时,这些帮助程序函数将非常有用。 或者,可以选择在同一项目中同时使用 C++/WinRT 和 C++/CX 语言投影,无论是否移植,并使用这些帮助程序函数在两者之间进行互操作。

阅读本主题后,有关演示如何在同一项目中并行支持 PPL 任务和协同例程的信息和代码示例(例如,从任务链调用协同例程),请参阅更高级的主题 Asynchrony 以及 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帮助程序函数将 C++/CX 对象转换为等效的 C++/WinRT 对象。 该函数将 C++/CX 对象强制转换为其底层 IUnknown 接口指针。 然后,它会在该指针上调用 QueryInterface ,以查询 C++/WinRT 对象的默认接口。 QueryInterface 是与 C++/CX safe_cast 扩展等效Windows 运行时应用程序二进制接口 (ABI)。 此外,winrt::put_abi 函数会获取 C++/WinRT 对象底层 IUnknown 接口指针的地址,以便将其设置为其他值。

to_cx 函数

to_cx帮助程序函数将 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 属性和目标)。 若要安装它,请单击菜单项 Project>管理 NuGet 程序包...>浏览,在搜索框中键入或粘贴 Microsoft.Windows.CppWinRT,在搜索结果中选择该项,然后单击 安装,为该项目安装该程序包。

Important

安装 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 支持。 在project属性中,通用属性>C++/WinRT>Project语言>C++/CX。 这样做会将以下属性添加到 .vcxproj 文件。

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

Important

每当你需要生成项目以将 Midl 文件 (.idl) 的内容处理为存根文件时,都需要将 项目语言 改回 C++/WinRT。 在生成已生成这些存根后,请将项目语言改回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++/CX 支持添加到 C++/WinRT 项目中),其中演示了使用命名空间别名 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 项目(请参阅Visual Studio C++/WinRT 支持)。
  • 启用对 C++/CX 的项目支持。 在项目属性中,C/C++>常规>使用Windows 运行时扩展>是(/ZW)。

一个演示这两个辅助函数用法的 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