이 항목은 C++/CX 프로젝트의 소스 코드를 C++/WinRT의 동일한 소스 코드로 포팅하는 방법을 설명하는 시리즈의 첫 번째 항목입니다.
프로젝트에서 WINDOWS RUNTIME WRL(템플릿 라이브러리) 형식도 사용하는 경우 WRL에서 C++/WinRT로 이동을 참조하세요.
포팅 전략
PPL(병렬 패턴 라이브러리) 작업에서 코루틴으로 이동하는 것을 제외하고 C++/CX에서 C++/WinRT로 포팅하는 것은 일반적으로 간단합니다. 모델은 다릅니다. PPL 작업에서 코루틴으로의 자연스러운 일대일 매핑은 없으며 모든 경우에 작동하는 코드를 기계적으로 이식하는 간단한 방법은 없습니다. 이러한 포팅의 특정 측면과 두 모델 간의 상호 운용 옵션에 대한 도움말은 비동기 및 C++/WinRT와 C++/CX 간의 interop을 참조하세요.
개발 팀은 비동기 코드 포팅의 장애물을 극복하고 나면 포팅 작업의 나머지 부분도 대체로 기계적이라고 정기적으로 보고합니다.
한 번의 패스로 포팅
전체 프로젝트를 한 번의 패스로 이식할 수 있는 위치에 있는 경우 필요한 정보에 대해 이 항목만 필요합니다(이 항목 다음에 오는 interop 항목은 필요하지 않음). 먼저 C++/WinRT 프로젝트 템플릿 중 하나를 사용하여 Visual Studio 새 프로젝트를 만드는 것이 좋습니다(C++/WinRT에 대한 Visual Studio 지원 참조). 그런 다음, 소스 코드 파일을 새 프로젝트로 이동하고 모든 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 새 프로젝트를 만드는 것입니다(C++/WinRT에 대한 Visual Studio 지원 참조). 그런 다음, 해당 프로젝트에 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 프로젝트 점진적으로 포팅
중요합니다
XAML을 사용하는 프로젝트의 경우 지정된 시간에 모든 XAML 페이지 형식은 전적으로 C++/CX이거나 완전히 C++/WinRT여야 합니다. 동일한 프로젝트 내에서 XAML 페이지 형식 외부에서는(모델, 뷰모델 및 기타 위치에서) 여전히 C++/CX와 C++/WinRT를 혼용할 수 있습니다.
이 시나리오에서는 새 C++/WinRT 프로젝트를 만들고 C++/CX 프로젝트에서 소스 코드 및 태그를 복사하는 것이 좋습니다. 모든 XAML 페이지 형식이 C++/WinRT인 경우 새 XAML 페이지를 Project>새 항목 추가...>로 추가할 수 있습니다.Visual C++>빈 페이지(C++/WinRT).
또는 포팅하는 과정에서 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 간의 Interop 을 참조하세요.
C++/CX 프로젝트를 C++/WinRT로 포팅하는 첫 번째 단계
포팅 전략이 무엇이든(한 번의 패스로 포팅 또는 점진적으로 포팅) 첫 번째 단계는 포팅을 위한 프로젝트를 준비하는 것입니다. 다음은 시작할 프로젝트의 종류와 이를 설정하는 방법에 대한 포팅 전략 에서 설명한 내용을 요약한 것입니다.
- 한 번의 패스로 포팅. C++/WinRT 프로젝트 템플릿 중 하나를 사용하여 Visual Studio 새 프로젝트를 만듭니다. C++/CX 프로젝트에서 해당 새 프로젝트로 파일을 이동하고 C++/CX 소스 코드를 이식합니다.
- XAML이 아닌 프로젝트를 점진적으로 포팅합니다. C++/CX 프로젝트에 C++/WinRT 지원을 추가하고( 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는 이 파일을 내부적으로 자동으로 생성하여 모든 공용 및 보호된 멤버에 추가합니다. C++/WinRT에서는 파일을 직접 추가하고 작성합니다. 자세한 내용, 코드 예제 및 IDL 작성 방법에 대한 단계별 안내는 XAML 컨트롤: C++/WinRT 속성에 바인딩을 참조하세요.
또한 런타임 클래스를 Midl 파일로 팩터링(.idl)을 참조하세요.
런타임 클래스
C++/CX는 헤더 파일의 이름에 제한을 적용하지 않습니다. 특히 작은 클래스의 경우 여러 런타임 클래스 정의를 단일 헤더 파일에 배치하는 것이 일반적입니다. 그러나 C++/WinRT를 사용하려면 각 런타임 클래스에 클래스 이름 이름을 따서 명명된 고유한 헤더 파일이 있어야 합니다.
| C++/CX | C++/WinRT |
|---|---|
Common.href class A { ... }ref class B { ... } |
Common.idlruntimeclass A { ... }runtimeclass B { ... } |
A.hnamespace implements {struct A { ... };} |
|
B.hnamespace implements {struct B { ... };} |
C++/CX에서 덜 일반적(하지만 여전히 합법적임)은 XAML 사용자 지정 컨트롤에 서로 다른 이름의 헤더 파일을 사용하는 것입니다. 클래스 이름과 일치하도록 이러한 헤더 파일의 이름을 바꿔야 합니다.
| C++/CX | C++/WinRT |
|---|---|
A.xaml<Page x:Class="LongNameForA" ...> |
A.xaml<Page x:Class="LongNameForA" ...> |
A.hpartial ref class LongNameForA { ... } |
LongNameForA.hnamespace 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 클래스를 사용하면 이 포함을 추가해야 합니다.
// MainPage.h
#include <winrt/Microsoft.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 코드로 포팅할 때 모자를 제거하고 화살표 연산자(->)를 점 연산자(.)로 변경하여 먼 길을 갈 수 있습니다. 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, PresenceDeviceRecords 및 Size 가 모두 속성입니다.
속성에서 값 검색
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);
클래스 인스턴스 만들기
C++/CX 개체는 일반적으로 hat(^) 참조라고 하는 해당 개체에 대한 핸들을 통해 다룹니다. 키워드를 통해 새 개체를 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++ 컬렉션 형식은 의도하지 않은 개체 생성을 초래할 수 있는 기본 생성자를 사용합니다.
| Scenario | 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 }; |
| Pair | 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에서는 기본 클래스에 대한 참조를 파생 클래스에 대한 참조로 cast하는 데 dynamic_cast를 사용합니다. 실제로 dynamic_cast에 대한 숨겨진 호출입니다. 다음은 종속성 속성 변경 이벤트를 처리하고 DependencyObject 에서 종속성 속성을 소유하는 실제 형식으로 다시 캐스팅하려는 일반적인 예제입니다.
void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject^ d, Microsoft::UI::Xaml::DependencyPropertyChangedEventArgs^ e)
{
BgLabelControl^ theControl{ dynamic_cast<BgLabelControl^>(d) };
if (theControl != nullptr)
{
// succeeded ...
}
}
해당 C++/WinRT 코드는 QueryInterface를 캡슐화하는 IUnknown::try_as 함수 호출로 dynamic_cast를 대체합니다. 또는 IUnknown::as를 대신 호출할 수도 있으며, 이 경우 필요한 인터페이스(요청하는 형식의 기본 인터페이스)를 쿼리한 결과가 반환되지 않으면 예외를 발생시킵니다. 다음은 C++/WinRT 코드 예제입니다.
void BgLabelControl::OnLabelChanged(Microsoft::UI::Xaml::DependencyObject const& d, Microsoft::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 runtimeclass BasePage : Microsoft.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);
자세한 정보 및 옵션은 등록된 대리자 해지(Revoke)를 참조하세요.
박싱 및 언박싱
C++/CX는 스칼라를 자동으로 객체로 박싱합니다. C++/WinRT를 사용하려면 winrt::box_value 함수를 명시적으로 호출해야 합니다. 두 언어 모두 명시적으로 언박싱해야 합니다. C++/WinRT의 박싱 및 언박싱을 참조하세요.
다음 표에서는 이러한 정의를 사용합니다.
| C++/CX | C++/WinRT |
|---|---|
int i; |
int i; |
String^ s; |
winrt::hstring s; |
Object^ o; |
IInspectable o; |
| Operation | C++/CX | C++/WinRT |
|---|---|---|
| 복싱 | o = 1;o = "string"; |
o = box_value(1);o = box_value(L"string"); |
| 제품 개봉 | i = (int)o;s = (String^)o; |
i = unbox_value<int>(o);s = unbox_value<winrt::hstring>(o); |
값 형식에 대한 null 포인터를 언박스 해제하려고 하면 C++/CX 및 C#에서 예외가 발생합니다. C++/WinRT는 이를 프로그래밍 오류로 간주하고 충돌합니다. C++/WinRT에서 개체가 생각한 형식이 아닌 경우 처리하려면 winrt::unbox_value_or 함수를 사용합니다.
| Scenario | C++/CX | C++/WinRT |
|---|---|---|
| 알려진 정수 언박싱 | i = (int)o; |
i = unbox_value<int>(o); |
| o가 null인 경우 | Platform::NullReferenceException |
추락 |
| o가 박싱된 int가 아닌 경우 | Platform::InvalidCastException |
추락 |
| int 값을 언박싱하고, null이면 대체값을 사용하며, 그 외의 경우에는 크래시 발생 | i = o ? (int)o : fallback; |
i = o ? unbox_value<int>(o) : fallback; |
| 가능하면 int를 언박싱하고, 그 외의 경우에는 폴백을 사용합니다. | 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는 문자열을 다르게 처리합니다.
ABI 형식 HSTRING 은 참조 계산 문자열에 대한 포인터입니다. 그러나 IInspectable에서 파생되지 않으므로 기술적으로 개체가 아닙니다. 또한 null HSTRING 은 빈 문자열을 나타냅니다. IInspectable에서 파생되지 않은 항목을 박싱하는 작업은 IReference<T> 내부에 래핑하는 방식으로 이루어지며, Windows 런타임은 PropertyValue 개체 형태의 표준 구현을 제공합니다(사용자 지정 형식은 PropertyType::OtherType으로 보고됩니다).
C++/CX는 Windows 런타임 문자열을 참조 형식으로 나타내고 C++/WinRT는 문자열을 값 형식으로 투영합니다. 즉, 박싱된 null 문자열은 어떤 방식으로 그 상태에 이르렀는지에 따라 서로 다른 표현을 가질 수 있습니다.
또한 C++/CX를 사용하면 null String^을 역참조할 수 있습니다. 이 경우 문자열 ""처럼 동작합니다.
| Behavior | C++/CX | C++/WinRT |
|---|---|---|
| 선언 | Object^ o;String^ s; |
IInspectable o;hstring s; |
| 문자열 형식 범주 | 참조 유형 | 값 형식 |
| null HSTRING로 투영됩니다 | (String^)nullptr |
hstring{} |
null이고 "" 동일합니까? |
Yes | Yes |
| 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 |
기본 박싱 및 언박싱.
| Operation | C++/CX | C++/WinRT |
|---|---|---|
| 문자열 상자 | o = s;빈 문자열은 nullptr이 됩니다. |
o = box_value(s);빈 문자열은 null이 아닌 개체가 됩니다. |
| 알려진 문자열의 박싱을 해제 | s = (String^)o;Null 개체가 빈 문자열이 됩니다. 문자열이 아닌 경우 InvalidCastException입니다. |
s = unbox_value<hstring>(o);Null 개체가 충돌합니다. 문자열이 아니면 충돌합니다. |
| 가능한 문자열 값 언박싱 | 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에 공개적으로 노출되어야 합니다.
또한 Boolean에 바인딩하면 C++/CX에서는 true 또는 false가 표시되지만, C++/WinRT에서는 Windows.Foundation.IReference`1<Boolean>가 표시됩니다.
자세한 내용 및 코드 예제는 마크업에서 개체 사용을(를) 참조하세요.
C++/CX 플랫폼 형식을 C++/WinRT 형식에 매핑
C++/CX는 플랫폼 네임스페이스에 여러 데이터 형식을 제공합니다. 이러한 형식은 표준 C++가 아니므로 Windows 런타임 언어 확장을 사용하도록 설정한 경우에만 사용할 수 있습니다(Visual Studio 프로젝트 속성 C/C++>일반>사용 Windows 런타임 확장>예(/ZW)). 아래 표는 플랫폼 형식에서 C++/WinRT의 해당 형식으로 포트하는 데 도움이 됩니다. 그렇게 하고 나면 C++/WinRT는 표준 C++이므로 /ZW 옵션을 해제할 수 있습니다.
| C++/CX | C++/WinRT |
|---|---|
| Platform::Agile^ | winrt::agile_ref |
| Platform::Array^ | 포트 플랫폼::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에서(WinUI 3에서는 CoreWindow 대신 Microsoft::UI::Xaml::Window 사용).
winrt::agile_ref<Microsoft::UI::Xaml::Window> m_window;
포트 플랫폼::Array^
C++/CX에서 배열을 사용해야 하는 경우 C++/WinRT를 사용하면 연속 컨테이너를 사용할 수 있습니다. std::vector가 적합한 이유로 기본 생성자가 컬렉션에 미치는 영향을 확인합니다.
따라서 C++/CX에 Platform::Array^ 가 있을 때마다 포팅 옵션에는 이니셜라이저 목록, std::array 또는 std::vector 사용이 포함됩니다. 자세한 정보 및 코드 예제는 표준 이니셜라이저 목록 과 표준 배열 및 벡터를 참조하세요.
Platform::Exception^를 winrt::hresult_error로 이식합니다
Platform::Exception^ 형식은 Windows 런타임 API가 S_OK 않은 HRESULT를 반환할 때 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의 문자열 표현을 반환하는 메시지 함수를 제공합니다.
다음은 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::D ata 속성에 액세스하여 문자열을 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 | int를 문자열로 변환 | 열거형을 문자열로 변환 |
|---|---|---|
| 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>
이러한 바인딩은 바인딩된 속성의 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(); |
추가 예제
아래 예제에서 ws 는 std::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 compilewinrt::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 compileMethod(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
관련 항목
메모
많은 C++/WinRT 항목이 UWP 설명서에서 이 섹션으로 마이그레이션되는 중입니다. 마이그레이션이 완료될 때까지 아래 목록의 링크는 UWP 문서 섹션으로 이어질 수 있습니다. C++/WinRT 언어 프로젝션은 UWP 및 WinUI 3 앱 모두에 대해 동일하므로 콘텐츠가 두 컨텍스트에서 모두 적용됩니다. UWP 관련 패턴(예: 앱 수명 주기 또는 Windows.UI 네임스페이스 API)은 해당 문서에서 명시적으로 설명합니다.
Windows developer