C#에서 C++/WinRT로 이동Move to C++/WinRT from C#

이 항목에서는 C# 프로젝트의 코드를 C++/WinRT의 해당 코드로 이식하는 방법을 보여 줍니다.This topic shows how to port the code in a C# project to its equivalent in C++/WinRT.

이벤트 처리기 등록Register an event handler

XAML 태그에서 이벤트 처리기를 등록할 수 있습니다.You can register an event handler in XAML markup.

<Button x:Name="OpenButton" Click="OpenButton_Click" />

C#에서 OpenButton_Click 메서드는 private일 수 있으며, XAML은 여전히 OpenButton에서 발생한 ButtonBase.Click 이벤트에 연결할 수 있습니다.In C#, your OpenButton_Click method can be private, and XAML will still be able to connect it to the ButtonBase.Click event raised by OpenButton.

C++/WinRT에서 OpenButton_Click 메서드는 구현 형식에서 public이어야 합니다.In C++/WinRT, your OpenButton_Click method must be public in your implementation type.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

또는 등록 클래스를 구현 클래스의 friend로 만들고 OpenButton_Click을 private으로 만들 수 있습니다.Alternatively, you can make the registering class a friend of your implementation class, and OpenButton_Click private.

namespace winrt::MyProject::implementation
{
    struct MyPage : MyPageT<MyPage>
    {
    private:
        friend MyPageT;
        void OpenButton_Click(
            winrt::Windows:Foundation::IInspectable const& sender,
            winrt::Windows::UI::Xaml::RoutedEventArgs const& args);
    }
};

{Binding} 태그 확장에서 사용할 수 있는 클래스 만들기Making a class available to the {Binding} markup extension

{Binding} 태그 확장을 사용하여 데이터를 데이터 형식에 바인딩하려면 {Binding}을 사용하여 선언된 Binding 개체를 참조하세요.If you intend to use the {Binding} markup extension to data bind to your data type, then see Binding object declared using {Binding}.

XAML 태그에서 데이터 원본을 사용할 수 있도록 만들기Making a data source available to XAML markup

C++/WinRT 버전 2.0.190530.8 이상에서 winrt::single_threaded_observable_vectorIObservableVector<T>IObservableVector<IInspectable> 을 모두 지원하는 관찰 가능한 벡터를 만듭니다.In C++/WinRT version 2.0.190530.8 and higher, winrt::single_threaded_observable_vector creates an observable vector that supports both IObservableVector<T> and IObservableVector<IInspectable>.

다음과 같이 Midl 파일(.idl) 을 작성할 수 있습니다(런타임 클래스를 Midl 파일(.idl)로 팩터링도 참조).You can author your Midl file (.idl) like this (also see Factoring runtime classes into Midl files (.idl)).

namespace Bookstore
{
    runtimeclass BookSku { ... }

    runtimeclass BookstoreViewModel
    {
        Windows.Foundation.Collections.IObservableVector<BookSku> BookSkus{ get; };
    }

    runtimeclass MainPage : Windows.UI.Xaml.Controls.Page
    {
        MainPage();
        BookstoreViewModel MainViewModel{ get; };
    }
}

그리고 다음과 같이 구현합니다.And implement like this.

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Bookstore::BookSku>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
    Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Bookstore::BookSku> m_bookSkus;
};
...

자세한 내용은 XAML 항목 컨트롤, C++/WinRT 컬렉션에 바인딩을 참조하세요.For more info, see XAML items controls; bind to a C++/WinRT collection.

XAML 태그에서 데이터 원본을 사용할 수 있도록 만들기(C++/WinRT 2.0.190530.8 이전)Making a data source available to XAML markup (prior to C++/WinRT 2.0.190530.8)

XAML 데이터 바인딩을 수행하려면 항목 원본에서 IIterable<IInspectable> 뿐만 아니라 다음 인터페이스 조합 중 하나도 구현해야 합니다.XAML data binding requires that an items source implements IIterable<IInspectable>, as well as one of the following combinations of interfaces.

  • IObservableVector<IInspectable>IObservableVector<IInspectable>
  • IBindableVectorINotifyCollectionChangedIBindableVector and INotifyCollectionChanged
  • IBindableVectorIBindableObservableVectorIBindableVector and IBindableObservableVector
  • IBindableVector 자체(변경에 응답하지 않음)IBindableVector by itself (will not respond to changes)
  • IVector<IInspectable>IVector<IInspectable>
  • IBindableIterable(요소를 반복하여 프라이빗 컬렉션에 저장)IBindableIterable (will iterate and save elements into a private collection)

IVector<T> 와 같은 제네릭 인터페이스는 런타임에 검색할 수 없습니다.A generic interface such as IVector<T> can't be detected at runtime. IVector<T> 에는 T의 함수인 다른 IID(인터페이스 식별자)가 있습니다. 모든 개발자는 임의로 T 집합을 확장할 수 있으므로 XAML 바인딩 코드는 쿼리할 전체 집합을 알 수 없습니다.Each IVector<T> has a different interface identifier (IID), which is a function of T. Any developer can expand the set of T arbitrarily, so clearly the XAML binding code can never know the full set to query for. IEnumerable<T> 를 구현하는 모든 CLR 개체에서 IEnumerable을 자동으로 구현하므로 이 제한은 C#에서 문제가 되지 않습니다.That restriction isn't a problem for C# because every CLR object that implements IEnumerable<T> automatically implements IEnumerable. ABI 수준에서는 IObservableVector<T> 를 구현하는 모든 개체에서 IObservableVector<IInspectable> 을 자동으로 구현한다는 것을 의미합니다.At the ABI level, that means that every object that implements IObservableVector<T> automatically implements IObservableVector<IInspectable>.

C++/WinRT는 이러한 보장을 제공하지 않습니다.C++/WinRT doesn't offer that guarantee. C++/WinRT 런타임 클래스에서 IObservableVector<T> 를 구현하는 경우 IObservableVector<IInspectable> 의 구현도 어떻게든 제공된다고 가정할 수 없습니다.If a C++/WinRT runtime class implements IObservableVector<T>, then we can't assume that an implementation of IObservableVector<IInspectable> is somehow also provided.

따라서 이전 예제를 조사해야 하는 방법은 다음과 같습니다.Consequently, here's how the previous example will need to look.

...
runtimeclass BookstoreViewModel
{
    // This is really an observable vector of BookSku.
    Windows.Foundation.Collections.IObservableVector<Object> BookSkus{ get; };
}

그리고 구현은 다음과 같습니다.And the implementation.

// BookstoreViewModel.h
...
struct BookstoreViewModel : BookstoreViewModelT<BookstoreViewModel>
{
    BookstoreViewModel()
    {
        m_bookSkus = winrt::single_threaded_observable_vector<Windows::Foundation::IInspectable>();
        m_bookSkus.Append(winrt::make<Bookstore::implementation::BookSku>(L"To Kill A Mockingbird"));
    }
    
    // This is really an observable vector of BookSku.
    Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> BookSkus();
    {
        return m_bookSkus;
    }

private:
    Windows::Foundation::Collections::IObservableVector<Windows::Foundation::IInspectable> m_bookSkus;
};
...

m_bookSkus의 개체에 액세스해야 하는 경우 해당 개체를 Bookstore::BookSku로 다시 QI해야 합니다.If you need to access objects in m_bookSkus, then you'll need to QI them back to Bookstore::BookSku.

Widget MyPage::BookstoreViewModel(winrt::hstring title)
{
    for (auto&& obj : m_bookSkus)
    {
        auto bookSku = obj.as<Bookstore::BookSku>();
        if (bookSku.Title() == title) return bookSku;
    }
    return nullptr;
}

ToString()ToString()

C# 형식은 Object.ToString 메서드를 제공합니다.C# types provide the Object.ToString method.

int i = 2;
var s = i.ToString(); // s is a System.String with value "2".

C++/WinRT는 이 기능을 직접 제공하지 않지만 대체 기능으로 전환할 수 있습니다.C++/WinRT doesn't directly provide this facility, but you can turn to alternatives.

int i{ 2 };
auto s{ std::to_wstring(i) }; // s is a std::wstring with value L"2".

또한 C++/WinRT는 제한된 수의 형식에 대해 winrt::to_hstring도 지원합니다.C++/WinRT also supports winrt::to_hstring for a limited number of types. 문자열화하려는 추가 형식에 대한 오버로드를 추가해야 합니다.You'll need to add overloads for any additional types you want to stringify.

외국어Language 정수 문자열화Stringify int 열거형 문자열화Stringify enum
C#C# string result = "hello, " + intValue.ToString();
string result = $"hello, {intValue}";
string result = "status: " + status.ToString();
string result = $"status: {status}";
C++/WinRTC++/WinRT hstring result = L"hello, " + to_hstring(intValue); // must define overload (see below)
hstring result = L"status: " + to_hstring(status);

열거형을 문자열화하는 경우 winrt::to_hstring의 구현을 제공해야 합니다.In the case of stringifying an enum, you will need to provide the implementation of 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));
        }
    }
}

이러한 문자열화는 데이터 바인딩에서 암시적으로 사용되는 경우가 많습니다.These stringifications are often consumed implicitly by data binding.

<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을 수행합니다.These bindings will perform winrt::to_hstring of the bound property. 두 번째 예제(StatusEnum)의 경우 winrt::to_hstring에 대한 사용자 고유의 오버로드를 제공해야 합니다. 그렇지 않으면 컴파일러 오류가 발생합니다.In the case of the second example (the StatusEnum), you must provide your own overload of winrt::to_hstring, otherwise you'll get a compiler error.

문자열 작성String-building

문자열 작성의 경우 C#에는 기본 제공 StringBuilder 형식이 있습니다.For string building, C# has a built-in StringBuilder type.

C#C# C++/WinRTC++/WinRT
Builder 클래스Builder class StringBuilder builder; std::wstringstream stream;
문자열 추가, null 유지Append string, preserving nulls builder.Append(s); stream << std::wstring_view{ s };
결과 추출Extract result s = builder.ToString(); ws = stream.str();

boxing 및 unboxingBoxing and unboxing

C#은 자동으로 스칼라를 개체에 boxing합니다.C# automatically boxes scalars into objects. C++/WinRT에서는 winrt::box_value 함수를 명시적으로 호출해야 합니다.C++/WinRT requires you to call the winrt::box_value function explicitly. 두 언어에서 모두 명시적으로 unboxing해야 합니다.Both languages require you to unbox explicitly. C++/WinRT를 사용하여 boxing 및 unboxing을 참조하세요.See Boxing and unboxing with C++/WinRT.

다음 표에서는 이러한 정의를 사용합니다.In the tables that follows, we'll use these definitions.

C#C# C++/WinRTC++/WinRT
int i; int i;
string s; winrt::hstring s;
object o; IInspectable o;
작업Operation C#C# C++/WinRTC++/WinRT
boxingBoxing o = 1;
o = "string";
o = box_value(1);
o = box_value(L"string");
unboxingUnboxing i = (int)o;
s = (string)o;
i = unbox_value<int>(o);
s = unbox_value<winrt::hstring>(o);

C++/CX 및 C#에서는 값 형식에 대한 null 포인터를 unboxing하려고 하면 예외가 발생합니다.C++/CX and C# raise exceptions if you try to unbox a null pointer to a value type. C++/WinRT는 이를 프로그래밍 오류로 간주하여 작동이 중단됩니다.C++/WinRT considers this a programming error, and it crashes. C++/WinRT에서 개체가 예상한 형식이 아닌 경우를 처리하려면 winrt::unbox_value_or 함수를 사용합니다.In C++/WinRT, use the winrt::unbox_value_or function if you want to handle the case where the object is not of the type that you thought it was.

시나리오Scenario C#C# C++/WinRTC++/WinRT
알려진 정수 unboxingUnbox a known integer i = (int)o; i = unbox_value<int>(o);
o가 null인 경우If o is null System.NullReferenceException 작동 중단Crash
o가 boxing된 정수가 아닌 경우If o is not a boxed int System.InvalidCastException 작동 중단Crash
정수 unboxing, null인 경우 대체 사용, 다른 항목이 있는 경우 작동 중단Unbox int, use fallback if null; crash if anything else i = o != null ? (int)o : fallback; i = o ? unbox_value<int>(o) : fallback;
가능한 경우 정수 unboxing, 다른 항목이 있는 경우 대체 사용Unbox int if possible; use fallback for anything else var box = o as int?;
i = box != null ? box.Value : fallback;
i = unbox_value_or<int>(o, fallback);

문자열 boxing 및 unboxingBoxing and unboxing a string

문자열은 어떤 방식에서는 값 형식이고, 다른 방식에서는 참조 형식입니다.A string is in some ways a value type, and in other ways a reference type. C# 및 C++/WinRT는 문자열을 다르게 처리합니다.C# and C++/WinRT treat strings differently.

HSTRING ABI 형식은 참조 횟수가 계산되는 문자열에 대한 포인터입니다.The ABI type HSTRING is a pointer to a reference-counted string. 그러나 IInspectable에서 파생되지 않으므로 기술적으로는 개체가 아닙니다.But it doesn't derive from IInspectable, so it's not technically an object. 또한 null HSTRING은 빈 문자열을 나타냅니다.Furthermore, a null HSTRING represents the empty string. IInspectable에서 파생되지 않은 것에 대한 boxing은 IReference<T> 안에 래핑하여 수행되며, Windows 런타임에서 표준 구현을 PropertyValue 개체 형식으로 제공합니다(사용자 지정 형식은 PropertyType::OtherType으로 보고됨).Boxing of things not derived from IInspectable is done by wrapping them inside an IReference<T>, and the Windows Runtime provides a standard implementation in the form of the PropertyValue object (custom types are reported as PropertyType::OtherType).

C#은 Windows 런타임 문자열을 참조 형식으로 나타내는 반면, C++/WinRT는 문자열을 값 형식으로 프로젝션합니다.C# represents a Windows Runtime string as a reference type; while C++/WinRT projects a string as a value type. 즉, boxing된 null 문자열은 해당 문자열을 가져온 방식에 따라 다르게 표현될 수 있습니다.This means that a boxed null string can have different representations depending how you got there.

작업Operation C#C# C++/WinRTC++/WinRT
문자열 형식 범주String type category 참조 형식Reference type 값 유형Value type
null HSTRING에서 프로젝션하는 형식null HSTRING projects as "" hstring{ nullptr }
null 및 ""가 동일한가요?Are null and "" identical? 아니오No Yes
null의 유효성Validity of null s = null;
s.Length에서 NullReferenceException 발생s.Length raises NullReferenceException
s = nullptr;
s.size() == 0(유효)s.size() == 0 (valid)
문자열 boxingBox a string o = s; o = box_value(s);
snull인 경우If s is null o = (string)null;
o == null
o = box_value(hstring{nullptr});
o != nullptr
s""인 경우If s is "" o = "";
o != null;
o = box_value(hstring{L""});
o != nullptr;
null을 유지하는 문자열 boxingBox a string preserving null o = s; o = s.empty() ? nullptr : box_value(s);
문자열 강제 boxingForce-box a string o = PropertyValue.CreateString(s); o = box_value(s);
알려진 문자열 unboxingUnbox a known string s = (string)o; s = unbox_value<hstring>(o);
o가 null인 경우If o is null s == null; // not equivalent to "" 작동 중단Crash
o가 boxing된 문자열이 아닌 경우If o is not a boxed string System.InvalidCastException 작동 중단Crash
문자열 unboxing, null인 경우 대체 사용, 다른 항목이 있는 경우 작동 중단Unbox string, use fallback if null; crash if anything else s = o != null ? (string)o : fallback; s = o ? unbox_value<hstring>(o) : fallback;
가능한 경우 문자열 unboxing, 다른 항목이 있는 경우 대체 사용Unbox string if possible; use fallback for anything else var s = o as string ?? fallback; s = unbox_value_or<hstring>(o, fallback);

위에 있는 두 개의 대체 사용 unboxing의 경우에서는 null 문자열이 강제 boxing되었을 수 있으며, 이 경우 대체가 사용되지 않습니다.In the two unbox with fallback cases above, it's possible that a null string was force-boxed, in which case the fallback won't be used. 결과 값은 boxing된 문자열이므로 빈 문자열이 됩니다.The resulting value will be an empty string because that is what was in the box.

파생 클래스Derived classes

런타임 클래스에서 파생하려면 기본 클래스를 구성할 수 있어야 합니다.In order to derive from a runtime class, the base class must be composable. C#에서는 클래스를 구성할 수 있게 하는 특별한 단계를 수행할 필요가 없지만, C++/WinRT는 이를 수행합니다.C# doesn't require that you take any special steps to make your classes composable, but C++/WinRT does. unsealed 키워드를 사용하여 클래스를 기본 클래스로 사용할 수 있도록 지정합니다.You use the unsealed keyword to indicate that you want your class to be usable as a base class.

unsealed runtimeclass BasePage : Windows.UI.Xaml.Controls.Page
{
    ...
}
runtimeclass DerivedPage : BasePage
{
    ...
}

구현 헤더 클래스에서 먼저 기본 클래스 헤더 파일을 포함해야 파생 클래스에 대해 자동 생성된 헤더를 포함할 수 있습니다.In your implementation header class, you must include the base class header file before you include the autogenerated header for the derived class. 그렇지 않으면 "이 형식을 식으로 잘못 사용했습니다"와 같은 오류가 발생합니다.Otherwise you'll get errors such as "Illegal use of this type as an expression".

// 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>
    {
        ...
    }
}

XAML 태그에서 개체 사용Consuming objects from XAML markup

C# 프로젝트에서는 XAML 태그에서 프라이빗 멤버 및 명명된 요소를 사용할 수 있습니다.In a C# project, you can consume private members and named elements from XAML markup. 그러나 C++/WinRT에서 XAML {x:Bind} 태그 확장을 통해 사용되는 모든 엔터티는 IDL에 공개적으로 노출되어야 합니다.But in C++/WinRT, all entities consumed by using the XAML {x:Bind} markup extension must be exposed publicly in IDL.

또한 부울에 바인딩하면 C#에서 true 또는 false가 표시되지만, C++/WinRT에서는 Windows.Foundation.IReference`1<Boolean> 이 표시됩니다.Also, binding to a Boolean displays true or false in C#, but it shows Windows.Foundation.IReference`1<Boolean> in C++/WinRT.

자세한 내용과 코드 예제는 태그에서 개체 사용을 참조하세요.For more info, and code examples, see Consuming objects from markup.

중요 APIImportant APIs