实现 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 任务和协同程序(例如,从任务链调用协同程序)的信息和代码示例,请参阅更高级的主题实现 C++/WinRT 与 C++/CX 之间的异步和互操作。
from_cx 和 to_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 safe_cast
扩展查询请求的 C++/CX 类型之前,此函数会将该指针强制转换为 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 支持并在该项目中进行移植工作时要执行的操作。 另请参阅 Visual Studio 对 C++/WinRT 的支持。
若要在 C++/CX 项目中混合使用 C++/CX 和 C++/WinRT,包括在项目中使用 from_cx 和 to_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) 的内容处理为存根文件时,都需要将“项目语言”改回为“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++/WinRT 项目并添加 C++/CX 支持)中的代码示例,了解使用命名空间别名 namespace cx
和 namespace winrt
的方法。 此方法可用于处理 C++/WinRT 投影和 C++/CX 投影之间潜在的命名空间冲突。
将 interop_helpers.h
添加到项目中
现在,可以将 from_cx 和 to_cx 函数添加到 C++/CX 项目中。 有关如何执行此操作的说明,请参阅上面的 from_cx 和 to_cx 函数部分。
采用 C++/WinRT 项目并添加 C++/CX 支持
本部分介绍在你决定创建新的 C++/WinRT 项目并在该项目中进行移植工作时要执行的操作。
若要在 C++/WinRT 项目中混合使用 C++/WinRT 和 C++/CX,包括在项目中使用 from_cx 和 to_cx 帮助函数,需要手动向项目添加 C++/CX 支持。
- 使用 C++/WinRT 项目模板之一在 Visual Studio 中创建一个新的 C++/WinRT 项目(请参阅 Visual Studio 对 C++/WinRT 的支持)。
- 启用对 C++/CX 的项目支持。 在项目属性中,“C/C++”>“常规”>“使用 Windows 运行时扩展”>“是(/ZW)”。
演示正在使用的两个帮助程序函数的示例 C++/WinRT 项目
在本部分中,可以创建一个示例 C++/WinRT 项目来演示如何使用 from_cx 和 to_cx。 它还说明如何使用不同代码岛的命名空间别名来处理 C++/WinRT 投影与 C++/CX 投影之间潜在的命名空间冲突。
- 创建“Visual C++”>“Windows 通用”>“核心应用(C++/WinRT)”项目。
- 在项目属性中,“C/C++”>“常规”>“使用 Windows 运行时扩展”>“是(/ZW)”。
- 将
interop_helpers.h
添加到项目。 有关如何执行此操作的说明,请参阅上面的 from_cx 和 to_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>());
}