이 항목은 C++/CX 프로젝트의 소스 코드를 C++/WinRT의 동일한 소스 코드로 포팅하는 방법을 설명하는 시리즈의 첫 번째 항목입니다.
프로젝트에서 Windows Runtime C++ 템플릿 라이브러리(WRL) 타입을 사용하는 경우, WRL에서 C++/WinRT로 이동을 참조하세요.
포팅 전략
C++/CX에서 C++/WinRT로 포팅하는 것은 일반적으로 간단하며, PPL(병렬 패턴 라이브러리)에서 코루틴으로 작업을 한 가지 예외입니다. 모델은 다릅니다. 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++/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 코드 사이에서 양방향으로 상호 작용하게 됩니다.
비고
XAML 프로젝트를 점진적으로 포팅합니다.
중요합니다
XAML을 사용하는 프로젝트의 경우 지정된 시간에 모든 XAML 페이지 형식은 전적으로 C++/CX이거나 완전히 C++/WinRT여야 합니다. C++/CX 및 C++/WinRT 동일한 프로젝트 내에서 XAML 페이지 형식의 외부에서 혼합할 수 있습니다(모델 및 뷰모델 및 다른 곳에서).
이 시나리오에서는 새 C++/WinRT 프로젝트를 만들고 C++/CX 프로젝트에서 소스 코드 및 태그를 복사하는 것이 좋습니다. 모든 XAML 페이지 형식이 C++/WinRT인 경우, Project>새 항목 추가...>Visual C++>빈 페이지(C++/WinRT)를 사용하여 새로운 XAML 페이지를 추가할 수 있습니다.
또는 Windows 런타임 구성 요소(WRC)를 사용하여 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는 .xaml
및 *.h
파일 이름에서 *.cpp
을 제거합니다.
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
할 수 있습니다.
를 반환하기 때문에 Windows.Media.Core
네임스페이스도 수정했습니다. item->VideoTracks->GetAt(0)
Windows.Media.Core.VideoTrack. 하지만 우리는 이름 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/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 코드로 포팅할 때 모자를 제거하고 화살표 연산자(->)를 점 연산자(.)로 변경하여 먼 길을 갈 수 있습니다. 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 개체는 일반적으로 모자(^) 참조로 알려진 핸들을 통해 작업할 수 있습니다. 키워드를 통해 새 개체를 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 예제에 대한 자세한 정보
[]
에서의 첨자 연산자는 다음과 같이 작동합니다.
- 키가 맵에 있으면 기존 값(덮어쓸 수 있음)에 대한 참조를 반환합니다.
- 키가 맵에 없는 경우, 키(이동 가능한 경우)에 대해 맵에 새 항목을 만들고, 기본 생성된 값
의 참조를 반환합니다. 이 참조는 나중에 덮어쓸 수 있습니다.
즉, 연산자는 []
항상 맵에 항목을 만듭니다. 이는 C#, Java 및 JavaScript와 다릅니다.
기본 런타임 클래스에서 파생 클래스로 변환
파생 형식 객체를 참조하는 기본 참조를 갖는 것이 일반적입니다. C++/CX에서는 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_cast
를 캡슐화하는 IUnknown::try_as 함수에 대한 호출로 바꿉 니다. 또한 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 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);
자세한 정보 및 옵션은 등록된 대리자 해지을 참조하세요.
박싱 및 언박싱
C++/CX는 스칼라를 개체에 자동으로 상자로 지정합니다. C++/WinRT를 사용하려면 winrt::box_value 함수를 명시적으로 호출해야 합니다. 두 언어 모두 명시적으로 언박싱해야 합니다. C++/WinRT 의 Boxing 및 unboxing을 참조하세요.
다음 표에서는 이러한 정의를 사용합니다.
C++/CX | C++/WinRT |
---|---|
int i; |
int i; |
String^ s; |
winrt::hstring s; |
Object^ o; |
IInspectable o; |
수술 | 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 함수를 사용하세요.
시나리오 | C++/CX | C++/WinRT |
---|---|---|
알려진 정수를 언박스하다 | i = (int)o; |
i = unbox_value<int>(o); |
o가 null인 경우 | Platform::NullReferenceException |
추락 |
o가 박싱된 정수가 아닌 경우 | Platform::InvalidCastException |
추락 |
정수를 언박스, 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); |
문자열을 객체로 변환하기 및 객체에서 문자열로 변환하기
문자열은 어떤 면에서는 값 형식이고 다른 방법으로는 참조 형식입니다. C++/CX 및 C++/WinRT는 문자열을 다르게 처리합니다.
ABI 형식 HSTRING은 참조 횟수가 있는 문자열에 대한 포인터입니다. 그러나
C++/CX는 Windows 런타임 문자열을 참조 형식으로 나타내며, C++/WinRT는 문자열을 값 형식으로 투영합니다. 즉, 박스화된 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 |
기본 박싱 및 언박싱.
수술 | 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 또는 비 문자열이 대체 옵션이 됩니다. 빈 문자열이 유지됩니다. |
동시성 및 비동기 작업
Parallel Patterns Library(병렬 패턴 라이브러리, PPL)(동시성::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<부울>로 표시됩니다.
코드 예제 및 관련 정보는 마크업에서 개체를 사용하는 방법을 참조하세요.
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^ | 포트 Platform::Array^을 참조하세요. |
Platform::Exception^ | winrt::hresult_error |
Platform::잘못된인수예외^ | winrt::hresult_invalid_argument |
Platform::Object^ | winrt::Windows::Foundation::IInspectable |
Platform::String^ | winrt::hstring |
포트 플랫폼::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를 사용할 수 있습니다. 자세한 정보 및 코드 예제는 표준 이니셜라이저 목록 과 표준 배열 및 벡터를 참조하세요.
포트 플랫폼::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 | 오류_취소됨 |
winrt::hresult_changed_state | winrt::hresult_error | E_CHANGED_STATE (변경된 상태) |
winrt::hresult_class_not_available | winrt::hresult_error | 클래스_클래스사용불가 |
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_불법 상태 변경 |
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를 사용하면
auto var{ titleRecord->TitleName->Data() };
C++/WinRT에서 동일한 작업을 수행하려면 hstring::c_str 함수를 사용하여 std::wstring에서와 마찬가지로 null로 끝나는 C 스타일 문자열 버전을 가져올 수 있습니다.
auto var{ titleRecord.TitleName().c_str() };
문자열을 사용하거나 반환하는 API를 구현하는 경우 일반적으로 Platform::String^ 사용하여 winrt::hstring 사용하는 C++/CX 코드를 변경합니다.
다음은 문자열을 사용하는 C++/CX API의 예입니다.
void LogWrapLine(Platform::String^ str);
C++/WinRT의 경우 API를 다음과 같이 MIDL 3.0에서 선언할 수 있습니다.
// 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 지원합니다. 문자열로 변환하고자 하는 모든 추가 형식에 대한 오버로드를 추가해야 합니다.
언어 | 정수 문자열로 변환 | 열거형을 문자열로 변환 |
---|---|---|
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을 사용합니다.
수술 | 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는 그렇게 하지 않습니다.
수술 | 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 |