C++/CX에서 C++/WinRT로 이동

이 항목은 C++/CX 프로젝트의 소스 코드를 C++/WinRT의 해당 항목으로 이식하는 방법을 설명하는 시리즈의 첫 번째 항목입니다.

프로젝트가 Windows 런타임 C++ 템플릿 라이브러리(WRL) 형식도 사용하는 경우 WRL에서 C++/WinRT로 이동을 참조하세요.

이식 전략

C++/CX에서 C++/WinRT로 이식하는 작업은 PPL(병렬 패턴 라이브러리) 작업에서 코루틴으로 이동하는 한 가지를 제외하고 일반적으로 간단합니다. 모델이 서로 다릅니다. PPL 작업에서 코루틴으로 자연스러운 일대일 매핑은 없으며 모든 경우에 작동하는 코드를 기계적으로 이식하는 간단한 방법도 없습니다. 이식의 특정 측면 및 두 모델 간의 상호 운용성 옵션에 대한 도움말은 비동시성 및 C++/WinRT와 C++/CX 간의 상호 운용성을 참조하세요.

개발 팀이 비동기식 코드 이식의 어려움을 극복하고 나면 이식 작업의 나머지는 대체로 기계적인 부분이라고 보고하는 경우가 많습니다.

한 번에 이식

전체 프로젝트를 한 번에 이식할 수 있는 위치에 있는 경우 필요한 정보에 대해서는 이 항목만 필요합니다. (이 다음에 나오는 interop 항목은 필요하지 않습니다.) C++/WinRT 프로젝트 템플릿 중 하나를 사용하여 Visual Studio에서 새 프로젝트를 생성하여 시작하는 것이 좋습니다(Visual Studio의 C++/WinRT 지원 참조). 그런 다음, 소스 코드 파일을 새 프로젝트로 이동하고 모든 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++/CX 프로젝트에 C++/WinRT 지원을 추가하는 것입니다. 그렇게 하기 위해 수행하는 단계는 C++/CX 프로젝트를 가져와서 C++/WinRT 지원 추가에 설명되어 있습니다. 이 단계를 수행한 다음, 점진적으로 이식할 수 있습니다.

또 다른 옵션은 C++/WinRT 프로젝트 템플릿 중 하나를 사용하여 Visual Studio에서 새 프로젝트를 만드는 것입니다(Visual Studio의 C++/WinRT 지원 참조). 그런 다음, 프로젝트에 C++/CX 지원을 추가합니다. 이렇게 하기 위해 수행하는 단계는 C++/WinRT 프로젝트를 가져와서 C++/CX 지원 추가에 설명되어 있습니다. 그런 다음, 소스 코드를 여기로 옮기고 C++/CX 소스 코드의 일부를 C++/WinRT로 이식할 수 있습니다.

두 경우 모두 C++/WinRT 코드와 아직 이식하지 않은 C++/CX 코드 간에 상호 운용(양 방향)됩니다.

참고 항목

C++/CX 및 Windows SDK는 둘 다 루트 네임스페이스인 Windows에서 형식을 선언합니다. C++/WinRT에 프로젝션된 Windows 형식은 Windows 형식과 동일한 정규화된 이름을 사용하지만 C++ winrt 네임스페이스에 배치됩니다. 이렇게 네임스페이스를 구별하여 원하는 대로 C++/CX에서 C++/WinRT로 이식할 수 있습니다.

점진적으로 XAML 프로젝트 이식

Important

XAML을 사용하는 프로젝트의 경우, 언제든지 모든 XAML 페이지 형식이 완전히 C++/CX이거나 완전히 C++/WinRT여야 합니다. (models 및 viewmodels 등) 동일한 프로젝트 내에서 XAML 페이지 형식 외부의 C++/CX와 C++/WinRT를 혼합할 수도 있습니다.

이 시나리오에서 권장되는 워크플로는 새 C++/WinRT 프로젝트를 만들고 C++/CX 프로젝트에서 소스 코드 및 태그를 복사하는 것입니다. 모든 XAML 페이지 형식이 C++/WinRT이면 프로젝트>새 항목 추가...>Visual C++>빈 페이지(C++/WinRT)를 통해 새 XAML 페이지를 추가할 수 있습니다.

또는 WRC(Windows 런타임 구성 요소)를 사용하여 이식할 때 XAML C++/CX 프로젝트에서 코드를 팩터링할 수 있습니다.

  • 새 C++/CX WRC 프로젝트를 만들고, C++/CX 코드를 이 프로젝트로 최대한 많이 옮긴 다음, XAML 프로젝트를 C++/WinRT로 변경합니다.
  • 또는 새 C++/WinRT WRC 프로젝트를 만들고, XAML 프로젝트를 C++/CX로 두고, C++/CX를 C++/WinRT로 이식을 시작하여 결과 코드를 XAML 프로젝트에서 구성 요소 프로젝트로 옮길 수 있습니다.
  • 또한 동일한 솔루션 내에 C++/WinRT 구성 요소 프로젝트와 함께 C++/CX 구성 요소 프로젝트를 포함하고, 애플리케이션 프로젝트에서 두 프로젝트를 모두 참조하고, 점진적으로 서로 이식할 수 있습니다. 이 경우도, 동일한 프로젝트에서 두 언어 프로젝션을 사용하는 방법에 대한 자세한 내용은 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는 이 파일을 내부적으로 자동 생성하여 모든 public 및 protected 멤버를 추가합니다. 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을 작성할 수 있습니다. item->VideoTracks->GetAt(0)에서 Windows.Media.Core.VideoTrack을 반환하므로 Windows.Media.Core 네임스페이스도 수정했습니다. 그러나 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 형식을 hat(^) 참조로 함수 매개 변수로 전달합니다.

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 소스 코드를 작성할 때 hat(^) 변수를 사용하여 Windows 런타임 개체를 참조하고, 화살표(->) 연산자를 사용하여 hat 변수를 역참조합니다.

IVectorView<User^>^ userList = User::Users;

if (userList != nullptr)
{
    for (UINT32 iUser = 0; iUser < userList->Size; ++iUser)
    ...

해당 C++/WinRT 코드에 이식할 때 먼저 hat을 제거하고 화살표 연산자(->)를 점 연산자(.)로 변경합니다. 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 함수를 호출합니다.

다음 예제에서 XboxUserId, UserState, PresenceDeviceRecordsSize는 모두 속성입니다.

속성에서 값 검색

여기서 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 함수는 자체에 Size 함수를 가지는 Windows 런타임 개체를 반환합니다. 반환된 개체가 C++/WinRT 프로젝션된 형식이기도 하므로 점 연산자를 사용하여 역참조하여 Size를 호출합니다.

속성을 새 값으로 설정

속성을 새 값으로 설정하는 것은 비슷한 패턴을 따릅니다. 먼저 C++/CX의 경우 다음과 같습니다.

record->UserState = newValue;

C++/WinRT에서 해당하는 작업을 수행하려면, 속성과 동일한 이름으로 함수를 호출하고 인수를 전달합니다.

record.UserState(newValue);

클래스 인스턴스 만들기

일반적으로 hat(^) 참조로 알려진 이에 대한 핸들을 통해 C++/CX 개체를 작업합니다. ref new 키워드를 통해 새로운 개체를 만들면 RoActivateInstance를 호출하여 런타임 클래스의 새로운 인스턴스를 활성화합니다.

using namespace Windows::Storage::Streams;

class Sample
{
private:
    Buffer^ m_gamerPicBuffer = ref new Buffer(MAX_IMAGE_SIZE);
};

C++/WinRT 개체는 값입니다. 따라서 스택에 또는 개체의 필드로 이를 할당할 수 있습니다. C++/WinRT 개체를 할당하기 위해 ‘절대’ ref new(또는 new)를 사용해서는 안 됩니다. 백그라운드에서 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와 다릅니다.

기본 런타임 클래스에서 파생된 항목 클래스로 변환

파생된 형식의 개체를 참조하는 기본에 대한 참조를 포함하는 것이 일반적입니다. C++/CX에서는 dynamic_cast를 사용하여 기본에 대한 참조를 파생에 대한 참조로 ‘캐스팅’합니다. 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 코드는 dynamic_castIUnknown::try_as 함수 호출(QueryInterface를 캡슐화함)로 바꿉니다. 필수 인터페이스(요청하는 형식의 기본 인터페이스)에 대한 쿼리가 반환되지 않는 경우 대신에 예외를 throw하는 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 키워드를 사용하여 클래스를 기본 클래스로 사용할 수 있도록 지정합니다.

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에서 이벤트를 처리하는 일반적인 예제가 나와 있습니다.

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.
});

람다 함수 대신 대리자를 자유 함수 또는 멤버 포인터 함수로 구현할 수 있습니다. 자세한 내용은 C++/WinRT의 대리자를 사용한 이벤트 처리를 참조하세요.

이벤트 및 대리자가 내부적으로 사용되는(이진 전체에서가 아니라) C++/CX 코드베이스에서 이식하는 경우 winrt::delegate는 C++/WinRT에서 해당 패턴을 복제하는 데 도움이 됩니다. 프로젝트 내의 매개 변수가 있는 대리자, 단순 신호 및 콜백을 참조하세요.

대리자 취소

C++/CX에서-= 연산자를 사용하여 이전 이벤트 등록을 취소합니다.

myButton->Click -= token;

C++/WinRT에서와 동일합니다.

myButton().Click(token);

자세한 내용과 옵션은 등록된 대리자 취소를 참조하세요.

boxing 및 unboxing

C++/CX는 자동으로 스칼라를 개체에 boxing합니다. C++/WinRT에서는 winrt::box_value 함수를 명시적으로 호출해야 합니다. 두 언어에서 모두 명시적으로 unboxing해야 합니다. C++/WinRT를 사용하여 boxing 및 unboxing을 참조하세요.

다음 표에서는 이러한 정의를 사용합니다.

C++/CX C++/WinRT
int i; int i;
String^ s; winrt::hstring s;
Object^ o; IInspectable o;
Operation C++/CX C++/WinRT
boxing o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
unboxing i = (int)o;
s = (String^)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX 및 C#에서는 값 형식에 대한 null 포인터를 unboxing하려고 하면 예외가 발생합니다. C++/WinRT는 이를 프로그래밍 오류로 간주하여 작동이 중단됩니다. C++/WinRT에서 개체가 예상한 형식이 아닌 경우를 처리하려면 winrt::unbox_value_or 함수를 사용합니다.

시나리오 C++/CX C++/WinRT
알려진 정수 unboxing i = (int)o; i = unbox_value<int>(o);
o가 null인 경우 Platform::NullReferenceException 작동 중단
o가 boxing된 정수가 아닌 경우 Platform::InvalidCastException 작동 중단
정수 unboxing, null인 경우 대체 사용, 다른 항목이 있는 경우 작동 중단 i = o ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
가능한 경우 정수 unboxing, 다른 항목이 있는 경우 대체 사용 auto box = dynamic_cast<IBox<int>^>(o);
i = box ? box->Value : fallback;
i = unbox_value_or<int>(o, fallback);

문자열 boxing 및 unboxing

문자열은 어떤 방식에서는 값 형식이고, 다른 방식에서는 참조 형식입니다. C++/CX 및 C++/WinRT는 문자열을 다르게 처리합니다.

HSTRING ABI 형식은 참조 횟수가 계산되는 문자열에 대한 포인터입니다. 그러나 IInspectable에서 파생되지 않으므로 기술적으로는 개체가 아닙니다. 또한 null HSTRING은 빈 문자열을 나타냅니다. IInspectable에서 파생되지 않은 것에 대한 boxing은 IReference<T> 안에 래핑하여 수행되며, Windows 런타임에서 표준 구현을 PropertyValue 개체 형식으로 제공합니다(사용자 지정 형식은 PropertyType::OtherType으로 보고됨).

C++/CX는 Windows 런타임 문자열을 참조 형식으로 나타내는 반면, C++/WinRT는 문자열을 값 형식으로 프로젝션합니다. 즉, boxing된 null 문자열은 해당 문자열을 가져온 방식에 따라 다르게 표현될 수 있습니다.

또한 C++/CX를 사용하면 null String^을 역참조할 수 있으며, 이 경우 "" 문자열처럼 작동합니다.

동작 C++/CX C++/WinRT
선언 Object^ o;
String^ s;
IInspectable o;
hstring s;
문자열 형식 범주 참조 형식 값 유형
null HSTRING에서 프로젝션하는 형식 (String^)nullptr hstring{}
null 및 ""가 동일한가요?
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

기본 boxing 및 unboxing.

Operation C++/CX C++/WinRT
문자열 boxing o = s;
빈 문자열은 nullptr이 됩니다.
o = box_value(s);
빈 문자열은 null이 아닌 개체가 됩니다.
알려진 문자열 unboxing 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에서 true 또는 false가 표시되지만, C++/WinRT에서는 Windows.Foundation.IReference`1<Boolean>이 표시됩니다.

자세한 내용과 코드 예제는 태그에서 개체 사용을 참조하세요.

C++/WinRT 형식에 C++/CX Platform 형식 매핑

C++/CX는 Platform 네임스페이스에서 몇 가지 데이터 형식을 제공합니다. 이 형식은 표준 C++가 아니므로 Windows 런타임 언어 확장을 활성화한 경우에만 이를 사용할 수 있습니다(Visual Studio 프로젝트 속성 C/C++>일반>Windows 런타임 확장 사용>예(/ZW)). 아래 표는 Platform 형식에서 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::array 또는 std::vector를 사용하는 것이 포함됩니다. 자세한 내용과 코드 예제는 표준 이니셜라이저 목록표준 배열 및 벡터를 참조하세요.

Platform::Exception^winrt::hresult_error로 이식

Windows 런타임 API가 비- S_OK HRESULT를 반환하면 Platform::Exception^ 유형이 C++/CX에서 생성됩니다. 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 기본 클래스를 통해)는 오류의 HRESULT를 반환하는 to_abi 함수와 해당 HRESULT의 문자열 표현을 반환하는 message 함수를 제공합니다.

C++/CX에서 예외를 throw하는 예제는 다음과 같습니다.

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에서는 std::wstring 같은 C++ 표준 라이브러리 와이드 문자열 형식 및/또는 와이드 문자열 리터럴을 사용해 Windows 런타임 API를 호출할 수 있습니다. 자세한 내용과 코드 예제는 C++/WinRT의 문자열 처리를 참조하세요.

C++/CX를 사용하면 Platform::String::Data 속성에 액세스하여 문자열을 C 스타일 const wchar_t* 배열로 검색할 수 있습니다(예: std::wcout에 전달).

auto var{ titleRecord->TitleName->Data() };

C++/WinRT에서 같은 작업을 수행하려면 hstring::c_str 함수를 이용하여 std::wstring에서와 마찬가지로 null 종료 C 스타일 문자열 버전을 가져옵니다.

auto var{ titleRecord.TitleName().c_str() };

문자열을 가져오거나 반환하는 API 구현에서는 일반적으로 Platform::String*을 사용하는 모든 C++/CX 코드를 변경하여 winrt::hstring을 대신 사용합니다.

문자열을 가져오는 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도 지원합니다. 문자열화하려는 추가 형식에 대한 오버로드를 추가해야 합니다.

Language 정수 문자열화 열거형 문자열화
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>

이러한 바인딩은 bound 속성의 winrt::to_hstring을 수행합니다. 두 번째 예제(StatusEnum)의 경우 winrt::to_hstring에 대한 사용자 고유의 오버로드를 제공해야 합니다. 그렇지 않으면 컴파일러 오류가 발생합니다.

문자열 작성

C++/CX 및 C++/WinRT는 문자열 작성을 위해 표준 std::wstringstream을 따릅니다.

Operation 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는 이 작업을 수행하지 않습니다.

Operation 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