C++/WinRT와 C++/CX 간의 Interop

이 항목을 읽기 전에 C++/CX에서 C++/WinRT로 이동 항목의 정보가 필요합니다. 이 항목에서는 C++/CX 프로젝트를 C++/WinRT로 이식하기 위한 두 가지 주요 전략 옵션을 소개합니다.

  • 전체 프로젝트를 한 번에 이식합니다. 너무 크지 않은 프로젝트를 위한 가장 간단한 옵션입니다. Windows 런타임 구성 요소 프로젝트가 있으면 이 전략이 유일한 옵션입니다.
  • 프로젝트를 점진적으로 이식합니다(코드베이스의 크기 또는 복잡성으로 인해 필요할 수 있음). 하지만 이 전략은 C++/CX와 C++/WinRT 코드가 동일한 프로젝트에 한동안 나란히 존재하는 이식 프로세스를 따라야 합니다. XAML 프로젝트의 경우, 언제라도, XAML 페이지 유형이 모두 C++/WinRT 또는 모두 C++/CX 중 하나여야 합니다.

이 interop 항목은 프로젝트를 점진적으로 이식해야 하는 경우인 두 번째 전략과 관련이 있습니다. 이 항목에서는 동일한 프로젝트 내에서 C++/CX 개체(및 다른 형식)를 C++/WinRT 개체로(또는 그 반대로) 변환하는 데 사용할 수 있는 다양한 형태의 도우미 함수 쌍을 보여줍니다.

이러한 도우미 함수는 코드를 C++/CX에서 C++/WinRT로 점진적으로 이식할 때 매우 유용합니다. 또는 이식 여부에 관계없이 동일한 프로젝트에서 C++/WinRT 및 C++/CX 언어 프로젝션을 모두 사용하도록 선택하고 도우미 함수를 사용하여 둘 사이를 상호 운용할 수 있습니다.

이 항목을 읽은 후 동일한 프로젝트에서 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 도우미 함수는 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 지원을 추가한 다음, 이식 작업을 수행하기로 결정한 경우 수행할 작업을 설명합니다. 또한, Visual Studio의 C++/WinRT 지원을 참조하세요.

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를 입력하거나 붙여넣고, 검색 결과에서 항목을 선택한 다음, 설치를 클릭하여 해당 프로젝트에 대한 패키지를 설치합니다.

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 지원을 추가할 수 있습니다. 프로젝트 속성에서 공용 속성>C++/WinRT>프로젝트 언어>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.
...

네임스페이스 별칭 namespace cxnamespace winrt를 사용하는 기법에 대해서는 다음 섹션(C++/WinRT 프로젝트를 가져와서 C++/CX 지원 추가)에서 코드 예제를 참조하세요. 이 기법을 사용하면 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 프로젝트

이 섹션에서는 from_cxto_cx 사용 방법을 보여주는 예제 C++/WinRT 프로젝트를 만들 수 있습니다. 또한 C++/WinRT 프로젝션 및 C++/CX 프로젝션 간의 잠재적인 네임스페이스 충돌을 다루기 위해 코드의 다른 격리 영역에 대한 네임스페이스 별칭을 사용하는 방법을 보여 줍니다.

  • a Visual C++>Windows Universal>Core App(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