C++/WinRT를 사용하여 API 작성Author APIs with C++/WinRT

winrt::implements 기본 구조체를 직접 또는 간접적으로 사용하여 C++/WinRT API를 작성하는 방법을 보여줍니다.This topic shows how to author C++/WinRT APIs by using the winrt::implements base struct, either directly or indirectly. 이 맥락에서 사용되는 작성이라는 표현은 생성 또는 구현과 동의어입니다.Synonyms for author in this context are produce, or implement. 이 토픽에서는 다음 시나리오의 순서대로 C++/WinRT 형식으로 API를 구현하는 방법을 설명합니다.This topic covers the following scenarios for implementing APIs on a C++/WinRT type, in this order.

참고

이 항목에서는 Windows 런타임 구성 요소에 대해 다루지만 C++/WinRT 컨텍스트 내에서만 설명합니다.This topic touches on the subject of Windows Runtime components, but only in the context of C++/WinRT. 모든 Windows 런타임 언어를 포함하는 Windows 런타임 구성 요소에 대한 콘텐츠를 원하는 경우 Windows 런타임 구성 요소를 참조하세요.If you're looking for content about Windows Runtime components that covers all Windows Runtime languages, then see Windows Runtime components.

  • Windows 런타임 클래스(이하 런타임 클래스)를 작성하지 않습니다. 단지 앱 내에서 로컬로 사용할 수 있도록 Windows 런타임 인터페이스를 하나 이상 구현할 것입니다.You're not authoring a Windows Runtime class (runtime class); you just want to implement one or more Windows Runtime interfaces for local consumption within your app. 이 예제의 winrt::implements에서 직접 파생시켜 함수를 구현합니다.You derive directly from winrt::implements in this case, and implement functions.
  • 런타임 클래스를 작성할 것입니다.You are authoring a runtime class. 앱에서 사용할 구성 요소를 작성할 수도 있습니다.You might be authoring a component to be consumed from an app. 혹은 XAML UI(사용자 인터페이스)에서 사용할 형식을 작성할 수도 있습니다. 이 경우에는 동일한 컴파일 단위 내에서 런타임 클래스를 구현하고 사용하게 됩니다.Or you might be authoring a type to be consumed from XAML user interface (UI), and in that case you're both implementing and consuming a runtime class within the same compilation unit. 어쨌든 두 경우 모두 도구를 사용해 winrt::implements에서 파생되는 클래스를 생성할 수 있습니다.In these cases, you let the tools generate classes for you that derive from winrt::implements.

위의 두 시나리오에서 C++/WinRT API를 구현하는 형식을 구현 형식이라고 부릅니다.In both cases, the type that implements your C++/WinRT APIs is called the implementation type.

중요

구현 형식과 프로젝션된 형식의 개념을 구분할 수 있어야 합니다.It's important to distinguish the concept of an implementation type from that of a projected type. 프로젝션된 형식은 C++/WinRT를 통한 API 사용에 설명되어 있습니다.The projected type is described in Consume APIs with C++/WinRT.

런타임 클래스를 작성하지 않는 경우If you're not authoring a runtime class

가장 간단한 시나리오는 로컬에서 사용할 목적으로 Windows 런타임 인터페이스를 구현하는 경우입니다.The simplest scenario is where you're implementing a Windows Runtime interface for local consumption. 이 경우 런타임 클래스는 필요 없고 일반적인 C++ 클래스만 있으면 됩니다.You don't need a runtime class; just an ordinary C++ class. 예를 들어 CoreApplication을 기반으로 앱을 개발할 수 있습니다.For example, you might be writing an app based around CoreApplication.

참고

프로젝트 템플릿 및 빌드 지원을 함께 제공하는 C++/WinRT VSIX(Visual Studio Extension) 및 NuGet 패키지를 설치하고 사용하는 방법에 대한 자세한 내용은 Visual Studio의 C++/WinRT 지원을 참조하세요.For info about installing and using the C++/WinRT Visual Studio Extension (VSIX) and the NuGet package (which together provide project template and build support), see Visual Studio support for C++/WinRT.

Visual Studio에서 Visual C++ > Windows 유니버설 > Core App(C++/WinRT) 프로젝트 템플릿은 CoreApplication 패턴을 보여줍니다.In Visual Studio, the Visual C++ > Windows Universal > Core App (C++/WinRT) project template illustrates the CoreApplication pattern. 이 패턴은 Windows::ApplicationModel::Core::IFrameworkViewSource 구현을 CoreApplication::Run으로 전달하는 것부터 시작됩니다.The pattern begins with passing an implementation of Windows::ApplicationModel::Core::IFrameworkViewSource to CoreApplication::Run.

using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    IFrameworkViewSource source = ...
    CoreApplication::Run(source);
}

CoreApplication은 인터페이스를 사용하여 앱의 첫 번째 보기를 만듭니다.CoreApplication uses the interface to create the app's first view. 개념적으로 IFrameworkViewSource는 다음과 같이 표시됩니다.Conceptually, IFrameworkViewSource looks like this.

struct IFrameworkViewSource : IInspectable
{
    IFrameworkView CreateView();
};

역시 개념적으로 CoreApplication::Run 구현은 다음과 같이 표시됩니다.Again conceptually, the implementation of CoreApplication::Run does this.

void Run(IFrameworkViewSource viewSource) const
{
    IFrameworkView view = viewSource.CreateView();
    ...
}

따라서 개발자는 IFrameworkViewSource 인터페이스를 구현합니다.So you, as the developer, implement the IFrameworkViewSource interface. C++/WinRT에는 기본 구조체 템플릿인 winrt::implements가 있기 때문에 COM 스타일 프로그래밍을 이용하지 않고도 하나 또는 여러 인터페이스를 쉽게 구현할 수 있습니다.C++/WinRT has the base struct template winrt::implements to make it easy to implement an interface (or several) without resorting to COM-style programming. 간단하게 implements에서 형식을 파생시킨 후 인터페이스의 함수를 구현하면 됩니다.You just derive your type from implements, and then implement the interface's functions. 다음과 같이 하세요.Here's how.

// App.cpp
...
struct App : implements<App, IFrameworkViewSource>
{
    IFrameworkView CreateView()
    {
        return ...
    }
}
...

IFrameworkViewSource는 처리되었습니다.That's taken care of IFrameworkViewSource. 다음 단계는 IFrameworkView 인터페이스를 구현하는 개체를 반환하는 것입니다.The next step is to return an object that implements the IFrameworkView interface. 마찬가지로 App에서 인터페이스를 구현하도록 선택할 수 있습니다.You can choose to implement that interface on App, too. 다음 코드 예제는 적어도 데스크톱에서 앱을 실행할 최소한의 앱을 나타냅니다.This next code example represents a minimal app that will at least get a window up and running on the desktop.

// App.cpp
...
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
    IFrameworkView CreateView()
    {
        return *this;
    }

    void Initialize(CoreApplicationView const &) {}

    void Load(hstring const&) {}

    void Run()
    {
        CoreWindow window = CoreWindow::GetForCurrentThread();
        window.Activate();

        CoreDispatcher dispatcher = window.Dispatcher();
        dispatcher.ProcessEvents(CoreProcessEventsOption::ProcessUntilQuit);
    }

    void SetWindow(CoreWindow const & window)
    {
        // Prepare app visuals here
    }

    void Uninitialize() {}
};
...

App 형식 IFrameworkViewSource이므로 하나만 Run으로 전달할 수 있습니다.Since your App type is an IFrameworkViewSource, you can just pass one to Run.

using namespace Windows::ApplicationModel::Core;
int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    CoreApplication::Run(winrt::make<App>());
}

Windows 런타임 구성 요소에서 런타임 클래스를 작성하는 경우If you're authoring a runtime class in a Windows Runtime component

형식이 애플리케이션에서 사용할 수 있도록 Windows 런타임 구성 요소에 패키지 형태로 존재하는 경우에는 런타임 클래스 형식이어야 합니다.If your type is packaged in a Windows Runtime component for consumption from an application, then it needs to be a runtime class. Microsoft IDL(인터페이스 정의 언어)(.idl) 파일에서 런타임 클래스를 선언합니다(런타임 클래스를 Midl 파일(.idl)로 팩터링 참조).You declare a runtime class in a Microsoft Interface Definition Language (IDL) (.idl) file (see Factoring runtime classes into Midl files (.idl)).

각 IDL 파일에 따라 .winmd 파일이 만들어지고, Visual Studio는 이러한 모든 파일을 루트 네임스페이스와 동일한 이름의 단일 파일로 병합합니다.Each IDL file results in a .winmd file, and Visual Studio merges all of those into a single file with the same name as your root namespace. 이 최종 .winmd 파일을 구성 요소 소비자가 참조할 것입니다.That final .winmd file will be the one that the consumers of your component will reference.

다음은 IDL 파일에서 런타임 클래스를 선언하는 예제입니다.Here's an example of declaring a runtime class in an IDL file.

// MyRuntimeClass.idl
namespace MyProject
{
    runtimeclass MyRuntimeClass
    {
        // Declaring a constructor (or constructors) in the IDL causes the runtime class to be
        // activatable from outside the compilation unit.
        MyRuntimeClass();
        String Name;
    }
}

이 IDL은 Windows 런타임(이하 런타임) 클래스를 선언합니다.This IDL declares a Windows Runtime (runtime) class. 런타임 클래스는 일반적으로 실행 가능한 경계를 넘어서 최신 COM 인터페이스를 통해 활성화 및 사용할 수 있는 형식입니다.A runtime class is a type that can be activated and consumed via modern COM interfaces, typically across executable boundaries. IDL 파일을 프로젝트에 추가하여 빌드할 때 C++/WinRT 도구 체인(midl.execppwinrt.exe)이 사용자를 대신하여 구현 형식을 생성합니다.When you add an IDL file to your project, and build, the C++/WinRT toolchain (midl.exe and cppwinrt.exe) generate an implementation type for you. IDL 파일 워크플로 작업 예제는 XAML 컨트롤, C++/WinRT 속성에 바인딩을 참조하세요.For an example of the IDL file workflow in action, see XAML controls; bind to a C++/WinRT property.

위의 IDL 예제를 보면 이름이 \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.hMyRuntimeClass.cpp인 소스 코드 파일에서 winrt::MyProject::implementation::MyRuntimeClass라는 이름의 C++ 구조체 스텁이 구현 형식에 해당합니다.Using the example IDL above, the implementation type is a C++ struct stub named winrt::MyProject::implementation::MyRuntimeClass in source code files named \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h and MyRuntimeClass.cpp.

구현 형식은 다음과 같은 모습입니다.The implementation type looks like this.

// MyRuntimeClass.h
...
namespace winrt::MyProject::implementation
{
    struct MyRuntimeClass : MyRuntimeClassT<MyRuntimeClass>
    {
        MyRuntimeClass() = default;

        winrt::hstring Name();
        void Name(winrt::hstring const& value);
    };
}

// winrt::MyProject::factory_implementation::MyRuntimeClass is here, too.

사용되는 F-바인딩 다형성 패턴을 잘 보세요. MyRuntimeClass는 자신을 기본 클래스인 MyRuntimeClassT에 대한 템플릿 인수로 사용합니다.Note the F-bound polymorphism pattern being used (MyRuntimeClass uses itself as a template argument to its base, MyRuntimeClassT). 이 패턴을 CRTP(Curiously Recurring Template Pattern)이라고도 합니다.This is also called the curiously recurring template pattern (CRTP). 상향식 상속 체인을 따르는 경우에는 MyRuntimeClass_base에 이르게 됩니다.If you follow the inheritance chain upwards, you'll come across MyRuntimeClass_base.

template <typename D, typename... I>
struct MyRuntimeClass_base : implements<D, MyProject::IMyRuntimeClass, I...>

따라서 이 시나리오에서는 상속 계층의 루트에 winrt::implements 기본 구조체 템플릿이 다시 한 번 위치합니다.So, in this scenario, at the root of the inheritance hierarchy is the winrt::implements base struct template once again.

Windows 런타임 구성 요소의 API 작성에 대한 자세한 내용과 코드 및 연습은 C++/WinRT의 이벤트 작성을 참조하세요.For more details, code, and a walkthrough of authoring APIs in a Windows Runtime component, see Author events in C++/WinRT.

XAML UI에서 참조할 런타임 클래스를 작성하는 경우If you're authoring a runtime class to be referenced in your XAML UI

형식을 XAML UI에서 참조하는 경우에는 XAML과 동일한 프로젝트에 위치하더라도 런타임 클래스 형식이어야 합니다.If your type is referenced by your XAML UI, then it needs to be a runtime class, even though it's in the same project as the XAML. 일반적으로 실행 가능한 경계에서 활성화되기는 하지만, 대신에 구현되는 컴파일 단위 내에서 런타임 클래스를 사용할 수도 있습니다.Although they are typically activated across executable boundaries, a runtime class can instead be used within the compilation unit that implements it.

이 시나리오에서는 API를 작성하고 사용합니다.In this scenario, you're both authoring and consuming the APIs. 런타임 클래스를 구현하는 절차는 기본적으로 Windows 런타임 구성 요소의 절차와 동일합니다.The procedure for implementing your runtime class is essentially the same as that for a Windows Runtime component. 따라서 이전 섹션—Windows 런타임 구성 요소에서 런타임 클래스를 작성하는 경우를 참조하세요.So, see the previous section—If you're authoring a runtime class in a Windows Runtime component. 유일하게 다른 점이라고 한다면 C++/WinRT 도구 체인이 IDL에서 구현 형식뿐 아니라 프로젝션된 형식도 생성한다는 것입니다.The only detail that differs is that, from the IDL, the C++/WinRT toolchain generates not only an implementation type but also a projected type. 이 시나리오에서 "MyRuntimeClass"만 언급하여 혼란스러울 수 있겠지만, 이는 종류가 다르면서 이름만 같은 엔터티가 다수 있기 때문입니다.It's important to appreciate that saying only "MyRuntimeClass" in this scenario may be ambiguous; there are several entities with that name, of different kinds.

  • MyRuntimeClass는 런타임 클래스의 이름입니다.MyRuntimeClass is the name of a runtime class. 하지만 이는 IDL에서 선언되고 일부 프로그래밍 언어에서 구현되는 추상화입니다.But this is really an abstraction: declared in IDL, and implemented in some programming language.
  • MyRuntimeClass는 C++ 구조체이자 런타임 클래스의 C++/WinRT 구현이기도 한 winrt::MyProject::implementation::MyRuntimeClass의 이름입니다.MyRuntimeClass is the name of the C++ struct winrt::MyProject::implementation::MyRuntimeClass, which is the C++/WinRT implementation of the runtime class. 앞서 설명했듯이, 구현하는 프로젝트와 사용하는 프로젝트가 따로 있다면 이 구조체는 구현하는 프로젝트에만 존재합니다.As we've seen, if there are separate implementing and consuming projects, then this struct exists only in the implementing project. 이것이 구현 형식 또는 구현입니다.This is the implementation type, or the implementation. 이 형식은 \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h 파일과 MyRuntimeClass.cpp 파일에서 cppwinrt.exe 도구를 통해 생성됩니다.This type is generated (by the cppwinrt.exe tool) in the files \MyProject\MyProject\Generated Files\sources\MyRuntimeClass.h and MyRuntimeClass.cpp.
  • MyRuntimeClass는 C++ 구조체 winrt::MyProject::MyRuntimeClass의 형태로 프로젝션된 형식의 이름입니다.MyRuntimeClass is the name of the projected type in the form of the C++ struct winrt::MyProject::MyRuntimeClass. 구현하는 프로젝트와 사용하는 프로젝트가 따로 있다면 이 구조체는 사용하는 프로젝트에만 존재합니다.If there are separate implementing and consuming projects, then this struct exists only in the consuming project. 이것이 프로젝션된 형식 또는 프로젝션입니다.This is the projected type, or the projection. 이 형식은 \MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h 파일에서 cppwinrt.exe를 통해 생성됩니다.This type is generated (by cppwinrt.exe) in the file \MyProject\MyProject\Generated Files\winrt\impl\MyProject.2.h.

프로젝션된 형식 및 구현 형식

다음은 이 토픽과 관련된 프로젝션된 형식의 일부입니다.Here are the parts of the projected type that are relevant to this topic.

// MyProject.2.h
...
namespace winrt::MyProject
{
    struct MyRuntimeClass : MyProject::IMyRuntimeClass
    {
        MyRuntimeClass(std::nullptr_t) noexcept {}
        MyRuntimeClass();
    };
}

런타임 클래스에서 INotifyPropertyChanged 인터페이스를 구현하는 연습 예제는 XAML 컨트롤, C++/WinRT 속성 바인딩을 참조하세요.For an example walkthrough of implementing the INotifyPropertyChanged interface on a runtime class, see XAML controls; bind to a C++/WinRT property.

이 시나리오에서 런타임 클래스를 사용하는 절차는 C++/WinRT를 통한 API 사용에 설명되어 있습니다.The procedure for consuming your runtime class in this scenario is described in Consume APIs with C++/WinRT.

런타임 클래스를 Midl 파일(.idl)로 팩터링Factoring runtime classes into Midl files (.idl)

Visual Studio 프로젝트 및 항목 템플릿은 각 런타임 클래스에 대해 별도의 IDL 파일을 생성합니다.The Visual Studio project and item templates produce a separate IDL file for each runtime class. 그러면 IDL 파일과 생성된 해당 소스 코드 파일이 논리적으로 일치합니다.That gives a logical correspondence between an IDL file and its generated source code files.

그러나 프로젝트의 모든 런타임 클래스를 단일 IDL 파일로 통합하면 빌드 시간이 크게 향상될 수 있습니다.However, if you consolidate all of your project's runtime classes into a single IDL file, then that can significantly improve build time. 그렇지 않고 이러한 클래스 간에 복잡한(또는 순환) import 종속성이 있는 경우 실제로 통합이 필요할 수 있습니다.If you would otherwise have complex (or circular) import dependencies among them, then consolidating may actually be necessary. 또한 런타임 클래스가 함께 있으면 이를 작성하고 검토하는 것이 더 쉬울 수 있습니다.And you may find it easier to author and review your runtime classes if they're together.

런타임 클래스 생성자Runtime class constructors

다음은 위에서 언급한 내용 외에 주의해야 할 몇 가지 사항입니다.Here are some points to take away from the listings we've seen above.

  • IDL에서 각 생성자를 생성하면 생성자가 구현 형식과 프로젝션된 형식으로 생성됩니다.Each constructor you declare in your IDL causes a constructor to be generated on both your implementation type and on your projected type. IDL에서 선언하는 생성자는 다른 컴파일 단위에서 런타임 클래스를 이용하는 데 사용됩니다.IDL-declared constructors are used to consume the runtime class from a different compilation unit.
  • IDL에서 생성자를 선언하든 선언하지 않든 상관없이, std::nullptr_t를 가져오는 생성자 오버로드는 프로젝션된 형식으로 생성됩니다.Whether you have IDL-declared constructor(s) or not, a constructor overload that takes std::nullptr_t is generated on your projected type. std::nullptr_t 생성자 호출은 동일한 컴파일 단위의 런타임 클래스를 사용하기 위한 두 단계 중 첫 번째 단계입니다.Calling the std::nullptr_t constructor is the first of two steps in consuming the runtime class from the same compilation unit. 자세한 내용과 코드 예제는 C++/WinRT를 통한 API 사용을 참조하세요.For more details, and a code example, see Consume APIs with C++/WinRT.
  • 동일한 컴파일 단위에서 런타임 클래스를 사용하는 경우에는 비 기본 생성자를 직접 구현 형식(MyRuntimeClass.h)으로 구현할 수도 있습니다.If you're consuming the runtime class from the same compilation unit, then you can also implement non-default constructors directly on the implementation type (which, remember, is in MyRuntimeClass.h).

참고

런타임 클래스가 주로 다른 컴파일 단위에서 사용될 것으로 예상되면 생성자(최소한 기본 생성자)를 IDL에 포함하세요.If you expect your runtime class to be consumed from a different compilation unit (which is common), then include constructor(s) in your IDL (at least a default constructor). 이렇게 하면 구현 형식과 함께 팩터리 구현도 가져오게 됩니다.By doing that, you'll also get a factory implementation alongside your implementation type.

동일한 컴파일 단위에서만 런타임 클래스를 작성하고 사용하려면 IDL에서 생성자를 선언하지 마세요.If you want to author and consume your runtime class only within the same compilation unit, then don't declare any constructor(s) in your IDL. 팩터리 구현도 필요하지 않기 때문에 생성되지 않습니다.You don't need a factory implementation, and one won't be generated. 구현 형식의 기본 생성자가 삭제되지만, 간단하게 편집하여 기본 생성자로 대신 사용할 수 있습니다.Your implementation type's default constructor will be deleted, but you can easily edit it and default it instead.

동일한 컴파일 단위에서만 런타임 클래스를 작성하여 사용할 계획이며, 생성자 매개 변수가 필요한 경우에는 직접 구현 형식으로 필요한 생성자를 작성하세요.If you want to author and consume your runtime class only within the same compilation unit, and you need constructor parameters, then author the constructor(s) that you need directly on your implementation type.

런타임 클래스 메서드, 속성 및 이벤트Runtime class methods, properties, and events

IDL을 사용하여 런타임 클래스 및 해당 멤버를 선언하는 워크플로를 살펴보았으며, 그 다음은 도구에서 프로토타입 및 스텁 구현을 자동으로 생성합니다.We've seen that the workflow is to use IDL to declare your runtime class and its members, and then the tooling generates prototypes and stub implementations for you. 런타임 클래스의 멤버에 대해 자동 생성된 이러한 프로토타입의 경우 IDL에서 선언된 형식과 다른 형식을 전달할 수 있도록 편집할 수 있습니다.As for those autogenerated prototypes for the members of your runtime class, you can edit them so that they pass around different types from the types that you declare in your IDL. 하지만 그렇게 할 수 있는 것은 IDL에서 선언된 형식을 구현된 버전에서 선언된 형식으로 전달할 수 있을 때만 가능합니다.But you can do that only as long as the type that you declare in IDL can be forwarded to the type that you declare in the implemented version.

예를 들면 다음과 같습니다.Here are some examples.

  • 매개 변수 형식을 완화할 수 있습니다.You can relax parameter types. 예를 들어 IDL에서 메서드가 SomeClass를 가져오는 경우 구현에서 이를 IInspectable로 변경하도록 선택할 수 있습니다.For example, if in IDL your method takes a SomeClass, then you could choose to change that to IInspectable in your implementation. 이는 모든 SomeClassIInspectable로 전달할 수 있기 때문에 가능합니다(물론 반대로는 작동하지 않습니다).This works because any SomeClass can be forwarded to IInspectable (the reverse, of course, wouldn't work).
  • 참조 대신 값으로 복사 가능한 매개 변수를 허용할 수 있습니다.You can accept a copyable parameter by value, instead of by reference. 예를 들어 SomeClass const&SomeClass const&로 변경할 수 있습니다.For example, change SomeClass const& to SomeClass const&. 코루틴에 대한 참조를 캡처하지 못하도록 해야 할 때 필요합니다(매개 변수 전달 참조).That's necessary when you need to avoid capturing a reference into a coroutine (see Parameter-passing).
  • 반환 값을 완화할 수 있습니다.You can relax the return value. 예를 들어 voidwinrt::fire_and_forget으로 변경할 수 있습니다.For example, you can change void to winrt::fire_and_forget.

마지막 둘은 비동기 이벤트 처리기를 작성할 때 매우 유용합니다.The last two are very useful when you're writing an asynchronous event handler.

구현 형식과 인터페이스의 인스턴스화 및 반환Instantiating and returning implementation types and interfaces

이 섹션에서는 IStringableIClosable 인터페이스를 구현하는 MyType이라는 이름의 구현 형식을 예로 들겠습니다.For this section, let's take as an example an implementation type named MyType, which implements the IStringable and IClosable interfaces.

MyTypewinrt::implements에서 직접 파생시킬 수 있습니다(런타임 클래스 아님).You can derive MyType directly from winrt::implements (it's not a runtime class).

#include <winrt/Windows.Foundation.h>

using namespace winrt;
using namespace Windows::Foundation;

struct MyType : implements<MyType, IStringable, IClosable>
{
    winrt::hstring ToString(){ ... }
    void Close(){}
};

또는 IDL에서 생성하는 것도 가능합니다(이것은 런타임 클래스).Or you can generate it from IDL (it's a runtime class).

// MyType.idl
namespace MyProject
{
    runtimeclass MyType: Windows.Foundation.IStringable, Windows.Foundation.IClosable
    {
        MyType();
    }    
}

MyType에서 프로젝션 과정 중 사용하거나 반환할 수 있는 IStringable 또는 IClosable 개체로 이동하려면 winrt::make 함수 템플릿을 호출해야 합니다.To go from MyType to an IStringable or IClosable object that you can use or return as part of your projection, you should call the winrt::make function template. make는 구현 형식의 기본 인터페이스를 반환합니다.make returns the implementation type's default interface.

IStringable istringable = winrt::make<MyType>();

참고

하지만 XAML UI에서 형식을 참조하는 경우에는 구현 형식과 프로젝션된 형식이 모두 동일한 프로젝트에 위치합니다.However, if you're referencing your type from your XAML UI, then there will be both an implementation type and a projected type in the same project. 이때는 make가 프로젝션된 형식 인스턴스를 반환합니다.In that case, make returns an instance of the projected type. 해당 시나리오의 코드 예제는 XAML 컨트롤, C++/WinRT 속성 바인딩을 참조하세요.For a code example of that scenario, see XAML controls; bind to a C++/WinRT property.

IStringable 인터페이스의 멤버를 호출할 때는 위 코드 예제의 istringable만 사용할 수 있습니다.We can only use istringable (in the code example above) to call the members of the IStringable interface. 하지만 C++/WinRT 인터페이스(프로젝션된 인터페이스)는 winrt::Windows::Foundation::IUnknown에서 파생됩니다.But a C++/WinRT interface (which is a projected interface) derives from winrt::Windows::Foundation::IUnknown. 따라서 IUnknown::as(또는 IUnknown::try_as)를 호출하여 다른 프로젝션된 형식 또는 인터페이스를 쿼리할 수 있으며, 이렇게 하여 얻은 결과를 사용하거나 반환할 수 있습니다.So, you can call IUnknown::as (or IUnknown::try_as) on it to query for other projected types or interfaces, which you can also either use or return.

istringable.ToString();
IClosable iclosable = istringable.as<IClosable>();
iclosable.Close();

구현의 모든 멤버에 액세스한 후 나중에 인터페이스를 호출자에게 반환해야 한다면 winrt::make_self 함수 템플릿을 사용합니다.If you need to access all of the implementation's members, and then later return an interface to a caller, then use the winrt::make_self function template. make_self는 구현 형식을 래핑하는 winrt::com_ptr을 반환합니다.make_self returns a winrt::com_ptr wrapping the implementation type. 화살표 연산자를 사용하여 모든 인터페이스의 멤버에 액세스하거나, 호출자에게 있는 그대로 반환하거나, as를 호출하여 그에 따른 인터페이스 개체를 호출자에게 반환할 수 있습니다.You can access the members of all of its interfaces (using the arrow operator), you can return it to a caller as-is, or you can call as on it and return the resulting interface object to a caller.

winrt::com_ptr<MyType> myimpl = winrt::make_self<MyType>();
myimpl->ToString();
myimpl->Close();
IClosable iclosable = myimpl.as<IClosable>();
iclosable.Close();

MyType 클래스는 구현이므로 프로젝션에 포함되지 않습니다.The MyType class is not part of the projection; it's the implementation. 하지만 이런 식으로 가상 함수를 호출하는 오버헤드 없이 구현 메서드를 직접 호출할 수 있습니다.But this way you can call its implementation methods directly, without the overhead of a virtual function call. 위의 예제에서는 MyType::ToStringIStringable에서 프로젝션된 메서드와 동일한 서명을 사용하지만, ABI(애플리케이션 이진 인터페이스)를 거치지 않고 비가상 메서드를 직접 호출합니다.In the example above, even though MyType::ToString uses the same signature as the projected method on IStringable, we're calling the non-virtual method directly, without crossing the application binary interface (ABI). com_ptr은 포인터를 MyType 구조체에 고정하기 때문에 사용자가 myimpl 변수 및 화살표 연산자를 통해 MyType의 다른 내부 세부 정보에 액세스할 수 있습니다.The com_ptr simply holds a pointer to the MyType struct, so you can also access any other internal details of MyType via the myimpl variable and the arrow operator.

인터페이스 개체가 있고, 이 개체가 구현의 인터페이스라는 사실을 알고 있는 경우에는 from_abi 함수 템플릿을 사용해 구현으로 돌아갈 수 있습니다.In the case where you have an interface object, and you happen to know that it's an interface on your implementation, then you can get back to the implementation using the winrt::get_self function template. 다시 말하지만, 이는 가상 함수 호출을 피하여 구현에 직접 이를 수 있는 기법입니다.Again, it's a technique that avoids virtual function calls, and lets you get directly at the implementation.

참고

Windows SDK 버전 10.0.17763.0(Windows 10 버전 1809) 이상을 설치하지 않은 경우 winrt::get_self 대신 winrt::from_abi를 호출해야 합니다.If you haven't installed the Windows SDK version 10.0.17763.0 (Windows 10, version 1809), or later, then you need to call winrt::from_abi instead of winrt::get_self.

예를 들면 다음과 같습니다.Here's an example. BgLabelControl 사용자 지정 컨트롤 클래스 구현에 또 다른 예제가 있습니다.There's another example in Implement the BgLabelControl custom control class.

void ImplFromIClosable(IClosable const& from)
{
    MyType* myimpl = winrt::get_self<MyType>(from);
    myimpl->ToString();
    myimpl->Close();
}

하지만 원래 인터페이스 개체만 참조를 계속 유지합니다.But only the original interface object holds on to a reference. 참조를 계속 유지하려면 com_ptr::copy_from을 호출하면 됩니다.If you want to hold on to it, then you can call com_ptr::copy_from.

winrt::com_ptr<MyType> impl;
impl.copy_from(winrt::get_self<MyType>(from));
// com_ptr::copy_from ensures that AddRef is called.

구현 형식 자체는 winrt::Windows::Foundation::IUnknown에서 파생되지 않기 때문에 as 함수가 없습니다.The implementation type itself doesn't derive from winrt::Windows::Foundation::IUnknown, so it has no as function. 그렇더라도 하나를 인스턴스화하여 모든 인터페이스의 멤버에 액세스할 수 있습니다.Even so, you can instantiate one, and access the members of all of its interfaces. 단, 이렇게 할 경우 원시 구현 형식 인스턴스를 호출자에게 반환하지 마세요.But if you do this, then don't return the raw implementation type instance to the caller. 대신 위에서 언급한 기법 중 한 가지를 사용하여 프로젝션된 인터페이스, 즉 com_ptr을 반환하세요.Instead, use one of the techniques shown above and return a projected interface, or a com_ptr.

MyType myimpl;
myimpl.ToString();
myimpl.Close();
IClosable ic1 = myimpl.as<IClosable>(); // error

구현 형식 인스턴스가 있고 이 인스턴스를 해당하는 프로젝션된 형식이 필요한 함수에 전달해야 하는 경우 그렇게 할 수 있습니다.If you have an instance of your implementation type, and you need to pass it to a function that expects the corresponding projected type, then you can do so. 구현 형식에는 이것을 가능하게 하는 변환 연산자가 존재하기 때문입니다(단, 구현 형식이 cppwinrt.exe 도구에서 생성된 경우에 한해).A conversion operator exists on your implementation type (provided that the implementation type was generated by the cppwinrt.exe tool) that makes this possible. 해당하는 프로젝션된 형식의 값이 필요한 메서드에 직접 구현 형식 값을 전달할 수 있습니다.You can pass an implementation type value directly to a method that expects a value of the corresponding projected type. 구현 형식 멤버 함수에서, 해당하는 프로젝션된 형식의 값이 필요한 메서드에 *this를 전달할 수 있습니다.From an implementation type member function, you can pass *this to a method that expects a value of the corresponding projected type.

// MyProject::MyType is the projected type; the implementation type would be MyProject::implementation::MyType.

void MyOtherType::DoWork(MyProject::MyType const&){ ... }

...

void FreeFunction(MyProject::MyOtherType const& ot)
{
    MyType myimpl;
    ot.DoWork(myimpl);
}

...

void MyType::MemberFunction(MyProject::MyOtherType const& ot)
{
    ot.DoWork(*this);
}

기본이 아닌 생성자를 갖고 있는 형식에서 파생Deriving from a type that has a non-default constructor

ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) 는 기본이 아닌 생성자의 예입니다.ToggleButtonAutomationPeer::ToggleButtonAutomationPeer(ToggleButton) is an example of a non-default constructor. 기본 생성자가 없기 때문에 ToggleButtonAutomationPeer를 생성하려면 소유자를 전달해야 합니다.There's no default constructor so, to construct a ToggleButtonAutomationPeer, you need to pass an owner. 결과적으로 ToggleButtonAutomationPeer에서 파생시키는 경우에는 소유자를 가져와 기본 클래스로 전달하는 생성자를 입력해야 합니다.Consequently, if you derive from ToggleButtonAutomationPeer, then you need to provide a constructor that takes an owner and passes it to the base. 실제로 표시되는 모습은 다음과 같습니다.Let's see what that looks like in practice.

// MySpecializedToggleButton.idl
namespace MyNamespace
{
    runtimeclass MySpecializedToggleButton :
        Windows.UI.Xaml.Controls.Primitives.ToggleButton
    {
        ...
    };
}
// MySpecializedToggleButtonAutomationPeer.idl
namespace MyNamespace
{
    runtimeclass MySpecializedToggleButtonAutomationPeer :
        Windows.UI.Xaml.Automation.Peers.ToggleButtonAutomationPeer
    {
        MySpecializedToggleButtonAutomationPeer(MySpecializedToggleButton owner);
    };
}

구현 형식일 때 생성되는 생성자는 다음과 같습니다.The generated constructor for your implementation type looks like this.

// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
    (MyNamespace::MySpecializedToggleButton const& owner)
{
    ...
}
...

여기서 유일하게 빠진 것은 해당하는 생성자 매개 변수를 기본 클래스로 전달해야 한다는 점입니다.The only piece missing is that you need to pass that constructor parameter on to the base class. 위에서 언급한 F-바인딩 다형성 패턴을 기억하시나요?Remember the F-bound polymorphism pattern that we mentioned above? 일단 C++/WinRT에서 사용하는 해당 패턴의 세부 정보에 익숙해지면 기본 클래스의 이름을 알아볼 수 있습니다. 또는 혹은 구현 클래스의 헤더 파일만 살펴봐도 됩니다.Once you're familiar with the details of that pattern as used by C++/WinRT, you can figure out what your base class is called (or you can just look in your implementation class's header file). 다음은 이 상황에서 기본 클래스 생성자를 호출하는 방법입니다.This is how to call the base class constructor in this case.

// MySpecializedToggleButtonAutomationPeer.cpp
...
MySpecializedToggleButtonAutomationPeer::MySpecializedToggleButtonAutomationPeer
    (MyNamespace::MySpecializedToggleButton const& owner) : 
    MySpecializedToggleButtonAutomationPeerT<MySpecializedToggleButtonAutomationPeer>(owner)
{
    ...
}
...

기본 클래스 생성자는 ToggleButton이 필요합니다.The base class constructor expects a ToggleButton. 그리고 MySpecializedToggleButton ToggleButton입니다.And MySpecializedToggleButton is a ToggleButton.

위에서 설명한 대로(생성자 매개 변수를 기본 클래스에게 전달) 편집할 때까지 컴파일러는 생성자를 플래그 처리하고 MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer> 라는 이름의 형식에 사용할 수 있는 기본 생성자가 없다고 알립니다.Until you make the edit described above (to pass that constructor parameter on to the base class), the compiler will flag your constructor and point out that there's no appropriate default constructor available on a type called (in this case) MySpecializedToggleButtonAutomationPeer_base<MySpecializedToggleButtonAutomationPeer>. 하지만 실제로는 이 클래스가 구현 형식의 기본 클래스입니다.That's actually the base class of the bass class of your implementation type.

네임스페이스: 프로젝션된 형식, 구현 형식 및 팩터리Namespaces: projected types, implementation types, and factories

이 항목에서 이전에 살펴본 대로 C++/WinRT 런타임 클래스는 둘 이상의 네임스페이스에 둘 이상의 C++ 클래스 형태로 존재합니다.As you've seen previously in this topic, a C++/WinRT runtime class exists in the form of more than one C++ class in more than one namespace. 따라서 MyRuntimeClass라는 이름은 winrt::MyProject 네임스페이스와 winrt::MyProject::implementation 네임스페이스에서 의미가 서로 다릅니다.So, the name MyRuntimeClass has one meaning in the winrt::MyProject namespace, and a different meaning in the winrt::MyProject::implementation namespace. 현재 컨텍스트에서 사용 중인 네임스페이스를 확인한 다음, 다른 네임스페이스의 이름이 필요하면 네임스페이스 접두사를 사용합니다.Be aware of which namespace you currently have in context, and then use namespace prefixes if you need a name from a different namespace. 해당 네임스페이스에 대해 좀 더 자세히 살펴보겠습니다.Let's look more closely at the namespaces in question.

  • winrt::MyProject.winrt::MyProject. 이 네임스페이스는 프로젝션된 형식을 포함합니다.This namespace contains projected types. 프로젝션된 형식의 개체는 프록시로, 특히 지원 개체를 사용자 프로젝트에서 구현하거나 다른 컴파일 단위에서 구현할 수 있는 경우 해당 지원 개체에 대한 스마트 포인터입니다.An object of a projected type is a proxy; it's essentially a smart pointer to a backing object, where that backing object might be implemented here in your project, or it might be implemented in another compilation unit.
  • winrt::MyProject::implementation.winrt::MyProject::implementation. 이 네임스페이스는 구현 형식을 포함합니다.This namespace contains implementation types. 구현 형식의 개체는 포인터가 아닌 값(전체 C++ 스택 개체)입니다.An object of an implementation type is not a pointer; it's a value—a full C++ stack object. 구현 형식을 직접 생성하지 마세요. 대신, winrt::make를 호출하여 구현 형식을 템플릿 매개 변수로 전달하세요.Don't construct an implementation type directly; instead, call winrt::make, passing your implementation type as the template parameter. 이 항목에서 이전에 winrt::make 작업 예제를 살펴보았으며 다른 예제는 XAML 컨트롤, C++/WinRT 속성에 바인딩에서 찾을 수 있습니다.We've shown examples of winrt::make in action previously in this topic, and there's another example in XAML controls; bind to a C++/WinRT property. 직접 할당 진단도 참조하세요.Also see Diagnosing direct allocations.
  • winrt::MyProject::factory_implementation.winrt::MyProject::factory_implementation. 이 네임스페이스는 팩터리를 포함합니다.This namespace contains factories. 이 네임스페이스의 개체가 IActivationFactory를 지원합니다.An object in this namespace supports IActivationFactory.

다음 표에서는 다른 컨텍스트에서 사용하는 데 필요한 최소 네임스페이스 자격을 보여 줍니다.This table shows the minimum namespace qualification you need to use in different contexts.

컨텍스트의 네임스페이스The namespace that's in context 프로젝션된 형식 지정To specify the projected type 구현 형식 지정To specify the implementation type
winrt::MyProjectwinrt::MyProject MyRuntimeClass implementation::MyRuntimeClass
winrt::MyProject::implementationwinrt::MyProject::implementation MyProject::MyRuntimeClass MyRuntimeClass

중요

프로젝션된 형식을 구현에서 반환하려는 경우 MyRuntimeClass myRuntimeClass;를 작성하여 구현 형식을 인스턴스화하지 않도록 주의해야 합니다.When you want to return a projected type from your implementation, be careful not to instantiate the implementation type by writing MyRuntimeClass myRuntimeClass;. 해당 시나리오에 대한 올바른 기술 및 코드는 이 항목의 구현 형식과 인터페이스의 인스턴스화 및 반환 섹션에 나와 있습니다.The correct techniques and code for that scenario are shown previously in this topic in the section Instantiating and returning implementation types and interfaces.

해당 시나리오에서 MyRuntimeClass myRuntimeClass; 관련 문제는 스택에 winrt::MyProject::implementation::MyRuntimeClass 개체가 생성된다는 것입니다.The problem with MyRuntimeClass myRuntimeClass; in that scenario is that it creates a winrt::MyProject::implementation::MyRuntimeClass object on the stack. 이 개체(구현 형식)는 몇 가지 측면에서 프로젝션된 형식처럼 동작하므로 동일한 방식으로 메서드를 호출할 수 있으며 프로젝션된 형식으로 변환됩니다.That object (of implementation type) behaves like the projected type in some ways—you can invoke methods on it in the same way; and it even converts to a projected type. 하지만 범위를 벗어나면 표준 C++ 규칙에 따라 개체가 소멸합니다.But the object destructs, as per normal C++ rules, when the scope exits. 따라서 해당 개체에 프로젝션된 형식(스마트 포인터)을 반환한 경우 해당 포인터는 이제 현수 포인터입니다.So, if you returned a projected type (a smart pointer) to that object, then that pointer is now dangling.

이러한 메모리 손상 버그 유형은 진단하기 어렵습니다.This memory corruption type of bug is difficult to diagnose. 따라서 디버그 빌드에서 C++WinRT 어설션을 사용하면 스택 감지기를 사용하여 이러한 실수를 잡아내는 데 도움이 됩니다.So, for debug builds, a C++/WinRT assertion helps you catch this mistake, using a stack-detector. 하지만 코루틴이 힙에 할당되므로 코루틴 내에서 실수가 발생하면 잡아낼 수 없습니다.But coroutines are allocated on the heap, so you won't get help with this mistake if you make it inside a coroutine. 자세한 내용은 직접 할당 진단을 참조하세요.For more info, see Diagnosing direct allocations.

다양한 C++/WinRT 기능에서 프로젝션된 형식 및 구현 형식 사용Using projected types and implementation types with various C++/WinRT features

C++/WinRT 기능에서 형식을 사용하는 다양한 위치 및 필요한 형식의 종류(프로젝션된 형식, 구현 형식 또는 둘 다)는 다음과 같습니다.Here are various places where a C++/WinRT features expects a type, and what kind of type it expects (projected type, implementation type, or both).

기능Feature 허용Accepts 참고Notes
T(스마트 포인터를 나타냄)T (representing a smart pointer) 프로젝션Projected 실수로 구현 형식을 사용한 경우에 대해서는 네임스페이스: 프로젝션된 형식, 구현 형식 및 팩터리의 주의 사항을 참조하세요.See the caution in Namespaces: projected types, implementation types, and factories about using the implementation type by mistake.
agile_ref<T> 둘 다Both 구현 형식을 사용할 경우 생성자 인수는 com_ptr<T>여야 합니다.If you use the implementation type, then the constructor argument must be com_ptr<T>.
com_ptr<T> 구현Implementation 프로젝션된 형식을 사용하면 'Release' is not a member of 'T' 오류가 생성됩니다.Using the projected type generates the error: 'Release' is not a member of 'T'.
default_interface<T> 둘 다Both 구현 형식을 사용하면 첫 번째 구현된 인터페이스가 반환됩니다.If you use the implementation type, then the first implemented interface is returned.
get_self<T> 구현Implementation 프로젝션된 형식을 사용하면 '_abi_TrustLevel': is not a member of 'T' 오류가 생성됩니다.Using the projected type generates the error: '_abi_TrustLevel': is not a member of 'T'.
guid_of<T>() 둘 다Both 기본 인터페이스의 GUID를 반환합니다.Returns the GUID of the default interface.
IWinRTTemplateInterface<T>
프로젝션Projected 실수로 구현 형식을 사용하여 컴파일한 경우 네임스페이스: 프로젝션된 형식, 구현 형식 및 팩터리의 주의 사항을 참조하세요.Using the implementation type compiles, but it's a mistake—see the caution in Namespaces: projected types, implementation types, and factories.
make<T> 구현Implementation 프로젝션된 형식을 사용하면 'implements_type': is not a member of any direct or indirect base class of 'T' 오류가 생성됩니다.Using the projected type generates the error: 'implements_type': is not a member of any direct or indirect base class of 'T'
make_agile(T const&amp;) 둘 다Both 구현 형식을 사용할 경우 인수는 com_ptr<T>여야 합니다.If you use the implementation type, then the argument must be com_ptr<T>.
make_self<T> 구현Implementation 프로젝션된 형식을 사용하면 'Release': is not a member of any direct or indirect base class of 'T' 오류가 생성됩니다.Using the projected type generates the error: 'Release': is not a member of any direct or indirect base class of 'T'
name_of<T> 프로젝션Projected 구현 형식을 사용하는 경우 기본 인터페이스의 stringified GUID를 가져올 수 있습니다.If you use the implementation type, then you get the stringified GUID of the default interface.
weak_ref<T> 둘 다Both 구현 형식을 사용할 경우 생성자 인수는 com_ptr<T>여야 합니다.If you use the implementation type, then the constructor argument must be com_ptr<T>.

균일한 생성 및 직접 구현 액세스 옵트인Opt in to uniform construction, and direct implementation access

이 섹션에서는 새 프로젝트에서 기본적으로 사용하도록 설정되어 있지만 옵트인할 수 있는 C++/WinRT 2.0 기능에 대해 설명합니다.This section describes a C++/WinRT 2.0 feature that's opt-in, although it is enabled by default for new projects. 기존 프로젝트의 경우 cppwinrt.exe 도구를 구성하여 옵트인해야 합니다.For an existing project, you'll need to opt in by configuring the cppwinrt.exe tool. Visual Studio에서 프로젝트 속성 공용 속성 > C++/WinRT > 최적화됨로 설정합니다.In Visual Studio, set project property Common Properties > C++/WinRT > Optimized to Yes. 이렇게 하면 <CppWinRTOptimized>true</CppWinRTOptimized>를 프로젝트 파일에 추가하는 효과가 있습니다.That has the effect of adding <CppWinRTOptimized>true</CppWinRTOptimized> to your project file. 그리고 명령줄에서 cppwinrt.exe를 호출할 때 스위치를 추가하는 것과 동일한 효과가 있습니다.And it has the same effect as adding the switch when invoking cppwinrt.exe from the command line.

-opt[imize] 스위치를 사용하면 균일한 생성이 가능한 경우가 많습니다.The -opt[imize] switch enables what's often called uniform construction. 균일한(또는 통합된) 생성에서는 C++/WinRT 언어 프로젝션 자체를 사용하여 로더 문제 없이 효율적으로 구현 형식(애플리케이션에서 사용하도록 구성 요소에서 구현한 형식)을 만들고 사용합니다.With uniform (or unified) construction, you use the C++/WinRT language projection itself to create and use your implmentation types (types implemented by your component, for consumption by applications) efficiently and without any loader difficulties.

기능을 설명하기 전에 먼저 균일한 생성을 사용하지 않는 상황을 살펴보겠습니다.Before describing the feature, let's first show the situation without uniform construction. 설명하기 위해 다음 Windows 런타임 클래스 예제에서 시작하겠습니다.To illustrate, we'll begin with this example Windows Runtime class.

// MyClass.idl
namespace MyProject
{
    runtimeclass MyClass
    {
        MyClass();
        void Method();
        static void StaticMethod();
    }
}

C++/WinRT 라이브러리 사용에 익숙한 C++ 개발자는 다음과 같은 클래스를 사용할 수도 있습니다.As a C++ developer familiar with using the C++/WinRT library, you might want to use the class like this.

using namespace winrt::MyProject;

MyClass c;
c.Method();
MyClass::StaticMethod();

그리고 표시된 사용 코드가 이 클래스를 구현하는 동일한 구성 요소 내에 있지 않으면 이 방법이 완벽하게 합리적입니다.And that would be perfectly reasonable provided that the consuming code shown didn't reside within the same component that implements this class. 언어 프로젝션인 C++/WinRT는 ABI(Windows 런타임에서 정의하는 COM 기반 애플리케이션 이진 인터페이스) 개발자를 보호합니다.As a language projection, C++/WinRT shields you as a developer from the ABI (the COM-based application binary interface that the Windows Runtime defines). C++/WinRT는 구현에 직접 호출하지 않고 ABI를 통해 이동합니다.C++/WinRT doesn't call directly into the implementation; it travels through the ABI.

따라서 MyClass 개체(MyClass c;)를 생성하는 코드 줄에서 C++/WinRT 프로젝션은 RoGetActivationFactory를 호출하여 클래스 또는 활성화 팩터리를 검색한 다음, 해당 팩터리를 사용하여 개체를 만듭니다.Consequently, on the line of code where you're constructing a MyClass object (MyClass c;), the C++/WinRT projection calls RoGetActivationFactory to retrieve the class or activation factory, and then uses that factory to create the object. 마지막 줄도 마찬가지로 팩터리를 사용하여 정적 메서드 호출로 표시되는 항목을 만듭니다.The last line likewise uses the factory to make what appears to be a static method call. 이 모든 작업을 수행하려면 클래스를 등록하고 모듈에서 DllGetActivationFactory 진입점을 구현해야 합니다.All of this requires that your class be registered, and that your module implements the DllGetActivationFactory entry point. C++/WinRT는 매우 빠른 팩터리 캐시를 사용하므로 이 구성 요소를 사용하는 애플리케이션에 문제가 발생하지 않습니다.C++/WinRT has a very fast factory cache, so none of this causes a problem for an application consuming your component. 문제는 구성 요소 내에서 약간의 문제만 발생한다는 것입니다.The trouble is that, within your component, you've just done something that's a little problematic.

첫째, C++/WinRT 팩터리 캐시가 아무리 빠르더라도 RoGetActivationFactory(또는 팩터리 캐시를 통한 후속 호출)를 통해 호출하는 것은 구현에 직접 호출하는 것보다 항상 느립니다.Firstly, no matter how fast the C++/WinRT factory cache is, calling through RoGetActivationFactory (or even subsequent calls through the factory cache) will always be slower than calling directly into the implementation. RoGetActivationFactory, IActivationFactory::ActivateInstance, QueryInterface를 순서대로 호출하는 것은 분명히 로컬로 정의된 형식에 대해 C++ new 식을 사용하는 것만큼 효율적이지 않습니다.A call to RoGetActivationFactory followed by IActivationFactory::ActivateInstance followed by QueryInterface is obviously not going to be as efficient as using a C++ new expression for a locally-defined type. 결과적으로 숙련된 C++/WinRT 개발자는 개체를 구성 요소 내에 만들 때 winrt::make 또는 winrt::make_self 도우미 함수를 사용하는 데 익숙합니다.As a consequence, seasoned C++/WinRT developers are accustomed to using the winrt::make or winrt::make_self helper functions when creating objects within a component.

// MyClass c;
MyProject::MyClass c{ winrt::make<implementation::MyClass>() };

그러나 볼 수 있듯이 이 방법은 거의 편리하거나 간결하지도 않습니다.But, as you can see, that's not nearly as convenient nor concise. 도우미 함수를 사용하여 개체를 만들어야 하며, 구현 형식과 프로젝션된 형식을 명확히 구분해야 합니다.You must use a helper function to create the object, and you must also disambiguate between the implementation type and the projected type.

둘째, 프로젝션을 사용하여 클래스를 만들면 해당 활성화 팩터리가 캐시됩니다.Secondly, using the projection to create the class means that its activation factory will be cached. 일반적으로 이 작업을 수행하는 것이 좋습니다. 그러나 팩터리가 호출하는 동일한 모듈(DLL)에 있으면 DLL을 효과적으로 고정하여 언로딩을 방지했습니다.Normally, that's what you want, but if the factory resides in the same module (DLL) that's making the call, then you've effectively pinned the DLL and prevented it from ever unloading. 대부분의 경우 이는 중요하지 않습니다. 그러나 일부 시스템 구성 요소는 언로딩을 지원해야 합니다.In many cases, that doesn't matter; but some system components must support unloading.

이 경우 균일한 생성이라는 용어가 나타납니다.This is where the term uniform construction comes in. 만들기 코드가 클래스만 사용하는 프로젝트에 있는지 또는 클래스를 실제로 구현하는 프로젝트에 있는지 여부에 관계없이 자유롭게 동일한 구문을 사용하여 개체를 만들 수 있습니다.Regardless of whether creation code resides in a project that's merely consuming the class, or whether it resides in the project that's actually implementing the class, you can freely use the same syntax to create the object.

// MyProject::MyClass c{ winrt::make<implementation::MyClass>() };
MyClass c;

-opt[imize] 스위치를 사용하여 구성 요소 프로젝트를 빌드하면 언어 프로젝션을 통한 호출이 구현 형식을 직접 만드는 winrt::make 함수에 대한 동일한 효율적인 호출로 압축됩니다.When you build your component project with the -opt[imize] switch, the call through the language projection compiles down to the same efficient call to the winrt::make function that directly creates the implementation type. 이렇게 하면 구문을 간단하고 예측 가능하게 만들 수 있으므로 팩터리를 통해 호출할 때 성능 저하를 방지할 수 있고 프로세스에서 구성 요소를 고정할 필요가 없습니다.That makes your syntax simple and predictable, it avoids any performance hit of calling through the factory, and it avoids pinning the component in the process. 또한 이는 구성 요소 프로젝트 외에도 XAML 애플리케이션에도 유용합니다.In addition to component projects, this is also useful for XAML applications. 동일한 애플리케이션에서 구현된 클래스에 대한 RoGetActivationFactory를 무시하면 구성 요소 외부에 있을 때와 동일한 방식으로(등록할 필요 없이) 해당 클래스를 생성할 수 있습니다.Bypassing RoGetActivationFactory for classes implemented in the same application allows you to construct them (without needing to be registered) in all the same ways you could if they were outside your component.

균일한 생성은 팩터리 내부에서 제공하는 모든 호출에 적용됩니다.Uniform construction applies to any call that's served by the factory under the hood. 실제로 이는 최적화에서 생성자와 정적 구성 요소를 모두 사용한다는 것을 의미합니다.Practically, that means that the optimization serves both constructors and static members. 원래 예제는 다음과 같습니다.Here's that original example again.

MyClass c;
c.Method();
MyClass::StaticMethod();

-opt[imize]를 사용하지 않으면 첫 번째 및 마지막 명령문에서 팩터리 개체를 통해 호출해야 합니다.Without -opt[imize], the first and last statements require calls through the factory object. -opt[imize]사용하면 둘 모두에서 이 작업을 수행하지 않습니다.With -opt[imize], neither of them do. 그리고 이러한 호출은 구현에 대해 직접 컴파일되며, 심지어 인라인될 수도 있습니다.And those calls are compiled directly against the implementation, and even have the potential to be inlined. 이는 -opt[imize], 즉 직접 구현 액세스에 대해 언급할 때 자주 사용되는 다른 용어와 관련이 있습니다.Which speaks to the other term often used when talking about -opt[imize], namely direct implementation access.

언어 프로젝션이 편리하지만, 구현에 직접 액세스할 수 있는 경우 이를 활용하여 가장 효율적인 코드를 만들 수 있습니다.Language projections are convenient but, when you can directly access the implementation, you can and should take advantage of that to produce the most efficient code possible. C++/WinRT를 사용하면 프로젝션의 안전성과 생산성을 그대로 유지하도록 강요하지 않고 이 작업을 수행할 수 있습니다.C++/WinRT can do that for you, without forcing you to leave the safety and productivity of the projection.

이는 언어 프로젝션에서 해당 구현 형식에 연결하여 직접 액세스할 수 있도록 구성 요소를 상호 운용해야 하므로 주요 변경 내용입니다.This is a breaking change because the component must cooperate in order to allow the language projection to reach in and directly access its implementation types. C++/WinRT는 헤더 전용 라이브러리이므로 내부를 조사하여 진행 상황을 확인할 수 있습니다.As C++/WinRT is a header-only library, you can look inside and see what's going on. -opt[imize]를 사용하지 않으면 다음과 같은 프로젝션에서 MyClass 생성자 및 StaticMethod 멤버를 정의합니다.Without -opt[imize], the MyClass constructor, and StaticMethod member, are defined by the projection like this.

namespace winrt::MyProject
{
    inline MyClass::MyClass() :
        MyClass(impl::call_factory<MyClass>([](auto&& f){
            return f.template ActivateInstance<MyClass>(); }))
    {
    }
    inline void MyClass::StaticMethod()
    {
        impl::call_factory<MyClass, MyProject::IClassStatics>([&](auto&& f) {
            return f.StaticMethod(); });
    }
}

위의 모든 작업을 반드시 수행할 필요는 없습니다. 이는 두 호출에서 모두 call_factory라는 함수에 대한 호출이 필요하다는 것을 보여 주기 위한 것입니다.It's not necessary to follow all of the above; the intent is to show that both calls involve a call to a function named call_factory. 이러한 호출은 팩터리 캐시를 포함하고, 구현에 직접 액세스하는 것이 아닙니다.That's your clue that these calls involve the factory cache, and they're not directly accessing the implementation. -opt[imize]사용하면 이러한 동일한 함수가 전혀 정의되지 않습니다.With -opt[imize], these same functions aren't defined at all. 대신, 이러한 함수는 프로젝션에서 선언하고, 해당 정의는 구성 요소에 남아 있습니다.Instead, they're declared by the projection, and their definitions are left up to the component.

그러면 구성 요소에서 구현에 직접 호출하는 정의를 제공할 수 있습니다.The component can then provide definitions that call directly into the implementation. 이제 주요 변경 내용에 도달했습니다.Now we've arrived at the breaking change. 이러한 정의는 -component-opt[imize]를 모두 사용할 때 생성되며 Type.g.cpp라는 파일에 나타납니다. 여기서 Type은 구현되는 런타임 클래스의 이름입니다.Those definitions are generated for you when you use both -component and -opt[imize], and they appear in a file named Type.g.cpp, where Type is the name of the runtime class being implemented. 따라서 기존 프로젝트에서 -opt[imize]를 사용하도록 처음 설정하는 경우에는 다양한 링커 오류가 발생할 수 있습니다.That's why you may hit various linker errors when you first enable -opt[imize] in an existing project. 작업을 수행할 수 있도록 생성된 파일을 구현에 포함시켜야 합니다.You need to include that generated file into your implementation in order to stitch things up.

예제에서는 MyClass.h-opt[imize]의 사용 여부에 관계없이 다음과 같을 수 있습니다.In our example, MyClass.h might look like this (regardless of whether -opt[imize] is being used).

// MyClass.h
#pragma once
#include "MyClass.g.h"
 
namespace winrt::MyProject::implementation
{
    struct MyClass : ClassT<MyClass>
    {
        MyClass() = default;
 
        static void StaticMethod();
        void Method();
    };
}
namespace winrt::MyProject::factory_implementation
{
    struct MyClass : ClassT<MyClass, implementation::MyClass>
    {
    };
}

MyClass.cpp에는 모든 것이 함께 제공됩니다.Your MyClass.cpp is where it all comes together.

#include "pch.h"
#include "MyClass.h"
#include "MyClass.g.cpp" // !!It's important that you add this line!!
 
namespace winrt::MyProject::implementation
{
    void MyClass::StaticMethod()
    {
    }
 
    void MyClass::Method()
    {
    }
}

따라서 기존 프로젝트에서 균일한 생성을 사용하려면 구현 클래스에 대한 포함(및 정의) 뒤에 #include <Sub/Namespace/Type.g.cpp>가 나오도록 각 구현의 .cpp 파일을 편집해야 합니다.So, to use uniform construction in an existing project, you need to edit each implementation's .cpp file so that you #include <Sub/Namespace/Type.g.cpp> after the inclusion (and definition) of the implementation class. 이 파일은 프로젝션이 정의되지 않은 상태로 있는 함수에 대한 정의를 제공합니다.That file provides the definitions of those functions that the projection left undefined. MyClass.g.cpp 파일 내에서 이러한 정의는 다음과 같습니다.Here's what those definitions look like inside the MyClass.g.cpp file.

namespace winrt::MyProject
{
    MyClass::MyClass() :
        MyClass(make<MyProject::implementation::MyClass>())
    {
    }
    void MyClass::StaticMethod()
    {
        return MyProject::implementation::MyClass::StaticMethod();
    }
}

그리고 이를 통해 구현에 대한 효율적인 직접 호출로 프로젝션을 완벽하게 완료하고, 팩터리 캐시에 대한 호출을 방지하고, 링커를 만족시킵니다.And that nicely completes the projection with efficient calls directly into the implementation, avoiding calls to the factory cache, and satisfying the linker.

-opt[imize]에서 수행하는 마지막 작업은 C++/WinRT 1.0에 필요한 강력한 형식 결합을 제거하여 증분 빌드가 훨씬 더 빠르게 수행되는 방식으로 프로젝트의 module.g.cpp(DLL의 DllGetActivationFactoryDllCanUnloadNow 내보내기를 구현하는 데 유용한 파일) 구현을 변경하는 것입니다.The final thing that -opt[imize] does for you is to change the implementation of your project's module.g.cpp (the file that helps you to implement your DLL's DllGetActivationFactory and DllCanUnloadNow exports) in such a way that incremental builds will tend to be much faster by eliminating the strong type coupling that was required by C++/WinRT 1.0. 이를 종종 형식이 지워진 팩터리라고 합니다.This is often referred to as type-erased factories. -opt[imize]를 사용하지 않으면 구성 요소에 대해 생성된 module.g.cpp 파일은 모든 구현 클래스(이 예제에서는 MyClass.h)의 정의를 포함하여 시작됩니다.Without -opt[imize], the module.g.cpp file that's generated for your component starts off by including the definitions of all of your implementation classes—the MyClass.h, in this example. 그러면 다음과 같이 각 클래스에 대한 구현 팩터리를 직접 만듭니다.It then directly creates the implementation factory for each class like this.

if (requal(name, L"MyProject.MyClass"))
{
    return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}

다시 말하지만, 모든 세부 정보를 따를 필요는 없습니다.Again, you don't need to follow all of the details. 여기서 확인할 수 있는 유용한 것은 구성 요소에서 구현한 모든 클래스에 대한 전체 정의가 필요하다는 것입니다.What's useful to see is that this requires the complete definition for any and all classes implemented by your component. 이 경우 단일 구현을 변경하면 module.g.cpp가 다시 컴파일되므로 내부 루프에 큰 영향을 줄 수 있습니다.This can have a dramatic effect on your inner loop, as any change to a single implementation will cause module.g.cpp to recompile. -opt[imize]에서는 더 이상 그렇지 않습니다.With -opt[imize], this is no longer the case. 대신 생성된 module.g.cpp 파일에 두 가지 상황이 발생합니다.Instead, two things happen to the generated module.g.cpp file. 첫 번째는 구현 클래스를 더 이상 포함하지 않는다는 것입니다.The first is that it no longer includes any implementation classes. 다음 예제에서는 MyClass.h를 전혀 포함하지 않습니다.In this example, it won't include MyClass.h at all. 대신 해당 구현에 대한 지식 없이 구현 팩터리를 만듭니다.Instead, it creates the implementation factories without any knowledge of their implementation.

void* winrt_make_MyProject_MyClass();
 
if (requal(name, L"MyProject.MyClass"))
{
    return winrt_make_MyProject_MyClass();
}

분명히 이러한 정의를 포함할 필요가 없으며, 링커를 통해 winrt_make_Component_Class 함수의 정의를 확인합니다.Obviously, there's no need to include their definitions, and it's up to the linker to resolve the winrt_make_Component_Class function's definition. 물론, 사용자를 위해 생성된 MyClass.g.cpp 파일(및 이전에 균일한 생성을 지원하기 위해 포함된 파일)도 이 함수를 정의하므로 이에 대해 생각할 필요가 없습니다.Of course, you don't need to think about this, because the MyClass.g.cpp file that gets generated for you (and that you previously included in order to support uniform construction) also defines this function. 이 예제에서 생성된 MyClass.g.cpp 파일의 전체 내용은 다음과 같습니다.Here's the entirety of the MyClass.g.cpp file that's generated for this example.

void* winrt_make_MyProject_MyClass()
{
    return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
namespace winrt::MyProject
{
    MyClass::MyClass() :
        MyClass(make<MyProject::implementation::MyClass>())
    {
    }
    void MyClass::StaticMethod()
    {
        return MyProject::implementation::MyClass::StaticMethod();
    }
}

여기서 볼 수 있듯이 winrt_make_MyProject_MyClass 함수는 구현의 팩터리를 직접 만듭니다.As you can see, the winrt_make_MyProject_MyClass function directly creates your implementation's factory. 이렇게 하면 지정된 구현을 모두 만족스럽게 변경할 수 있으며, module.g.cpp를 다시 컴파일할 필요가 전혀 없습니다.This all means that you can happily change any given implementation, and the module.g.cpp needn't be recompiled at all. Windows 런타임 클래스를 추가하거나 제거하는 경우에만 module.g.cpp를 업데이트하고 다시 컴파일해야 합니다.It's only when you add or remove Windows Runtime classes that module.g.cpp will be updated, and need to be recompiled.

기본 클래스의 가상 메서드 재정의Overriding base class virtual methods

기본 클래스와 파생 클래스는 모두 앱 정의 클래스이지만 가상 메서드가 최상위 Windows 런타임 클래스에 정의되는 경우 파생 클래스에 가상 메서드와 관련된 문제가 있을 수 있습니다.Your derived class can have issues with virtual methods if both the base and the derived class are app-defined classes, but the virtual method is defined in a grandparent Windows Runtime class. 실제로 XAML 클래스에서 파생되면 이 문제가 발생합니다.In practice, this happens if you derive from XAML classes. 이 섹션의 나머지 부분은 Derived 클래스의 예제에서 계속 진행됩니다.The rest of this section continues from the example in Derived classes.

namespace winrt::MyNamespace::implementation
{
    struct BasePage : BasePageT<BasePage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };
}

계층 구조는 Windows::UI::Xaml::Controls::Page <- BasePage <- DerivedPage입니다.The hierarchy is Windows::UI::Xaml::Controls::Page <- BasePage <- DerivedPage. BasePage::OnNavigatedFrom 메서드는 Page::OnNavigatedFrom을 올바르게 재정의하지만, DerivedPage::OnNavigatedFromBasePage::OnNavigatedFrom을 재정의하지 않습니다.The BasePage::OnNavigatedFrom method correctly overrides Page::OnNavigatedFrom, but DerivedPage::OnNavigatedFrom doesn't override BasePage::OnNavigatedFrom.

여기서 DerivedPageBasePage에서 IPageOverrides vtable을 다시 사용합니다. 즉, IPageOverrides::OnNavigatedFrom 메서드를 재정의하지 못합니다.Here, DerivedPage reuses the IPageOverrides vtable from BasePage, which means that it fails to override the IPageOverrides::OnNavigatedFrom method. 하나의 잠재적인 해결 방법으로, BasePage 자체가 템플릿 클래스가 되고 헤더 파일에 해당 구현이 전적으로 포함되어야 합니다. 그러나 이는 받아들일 수 없을 정도로 복잡합니다.One potential solution requires BasePage to itself be a template class, and to have its implementation entirely in a header file, but that makes things unacceptably complicated.

이 문제를 해결하려면 기본 클래스에서 OnNavigatedFrom 메서드를 명시적으로 가상 메서드로 선언합니다.As a workaround, declare the OnNavigatedFrom method as explicitly virtual in the base class. 이렇게 하면 DerivedPage::IPageOverrides::OnNavigatedFrom에 대한 vtable 항목에서 BasePage::IPageOverrides::OnNavigatedFrom을 호출할 때 생산자는 BasePage::OnNavigatedFrom을 호출하며, 이 경우 가상성(virtualness)으로 인해 결국에는 DerivedPage::OnNavigatedFrom을 호출합니다.That way, when the vtable entry for DerivedPage::IPageOverrides::OnNavigatedFrom calls BasePage::IPageOverrides::OnNavigatedFrom, the producer calls BasePage::OnNavigatedFrom, which (due to its virtualness), ends up calling DerivedPage::OnNavigatedFrom.

namespace winrt::MyNamespace::implementation
{
    struct BasePage : BasePageT<BasePage>
    {
        // Note the `virtual` keyword here.
        virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };

    struct DerivedPage : DerivedPageT<DerivedPage>
    {
        void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
    };
}

이렇게 하려면 클래스 계층 구조의 모든 멤버가 OnNavigatedFrom 메서드의 반환 값 및 매개 변수 형식에 동의해야 합니다.This requires that all members of the class hierarchy agree on the return value and parameter types of the OnNavigatedFrom method. 동의하지 않는 경우 위의 버전을 가상 메서드로 사용하고 대체 항목을 래핑해야 합니다.If they disagree, then you should use the version above as the virtual method, and wrap the alternates.

중요 APIImportant APIs