Windows 런타임 구성 요소에서 이벤트 발생

참고 항목

C++/WinRT Windows 런타임 구성 요소의 이벤트 발생에 대한 자세한 내용은 C++/WinRT의 이벤트 작성을 참조하세요.

Windows 런타임 구성 요소가 백그라운드 스레드(작업자 스레드)에서 사용자 정의 대리자 형식의 이벤트를 발생시키며 JavaScript가 이벤트를 받을 수 있게 하려는 경우 다음 방법 중 하나로 구현 및/또는 발생시킬 수 있습니다.

  • (옵션 1) Windows.UI.Core.CoreDispatcher를 통해 이벤트를 발생시켜 이벤트를 JavaScript 스레드 컨텍스트로 마샬링합니다. 일반적으로 이것이 최상의 옵션이지만, 일부 시나리오에서는 가장 빠른 성능을 제공하지 않을 수 있습니다.
  • (옵션 2) Windows.Foundation.EventHandler<Object>를 사용합니다. 단, 이벤트 형식 정보가 손실됩니다. 옵션 1이 불가능하거나 성능이 부적절한 경우 형식 정보가 손실되어도 되면 두 번째 선택 항목으로 사용하기에 적합합니다. C# Windows 런타임 구성 요소를 작성하는 경우 Windows.Foundation.EventHandler<Object> 형식을 사용할 수 없습니다. 대신 해당 형식이 System.EventHandler로 프로젝션되므로 대신 사용해야 합니다.
  • (옵션 3) 구성 요소에 대한 고유한 프록시 및 스텁을 만듭니다. 이 옵션은 구현하기는 가장 어렵지만 타입 정보를 유지하며 까다로운 시나리오에서 옵션 1에 비해 더 나은 성능을 제공할 수 있습니다.

옵션들 중에서 하나를 사용하지 않고 백그라운드 스레드에서 이벤트를 발생시킬 경우 JavaScript 클라이언트가 이벤트를 수신하지 않습니다.

배경

만드는 데 사용되는 언어와 관계없이 모든 Windows 런타임 구성 요소 및 앱은 기본적으로 COM 객체입니다. Windows API에서 구성 요소 대부분은 민첩한 COM 객체로 백그라운드 스레드 및 UI 스레드의 객체와 동일하게 잘 통신할 수 있습니다. COM 객체를 민첩하게 만들 수 없는 경우 프록시 및 스텁이라고 하는 헬퍼 객체가 UI 스레드 배경 스레드 경계를 넘어 다른 COM 객체와 통신해야 합니다. (COM 용어로, 이는 스레드 아파트 사이의 통신이라고 알려져 있습니다.)

Windows API 객체 대부분은 민첩하거나 프록시 및 스텁이 기본 제공됩니다. 그러나 Windows.Foundation.TypedEventHandler<TSender, TResult> 같은 제네릭 타입은 타입 인수가 제공될 때까지 완전한 타입이 아니기 때문에 프록시 및 스텁을 만들 수 없습니다. JavaScript 클라이언트에서만 프록시 또는 스텁의 부족이 문제가 되지만, C++ 또는 .NET 언어뿐만 아니라 JavaScript에서도 구성 요소를 사용할 수 있도록 하려면 다음 세 가지 옵션 중 하나를 사용해야 합니다.

(옵션 1) CoreDispatcher를 통해 이벤트 발생시키기

Windows.UI.Core.CoreDispatcher를 사용하여 사용자 정의 대리자 타입의 이벤트를 보낼 수 있으며 JavaScript에서 이벤트를 수신할 수 있습니다. 어떤 옵션을 사용해야 할 지 잘 모르겠는 경우, 먼저 이 옵션부터 적용해 보세요. 이벤트 발생과 이벤트 처리 간의 대기 시간이 문제가 되는 경우, 다른 두 옵션 중 하나를 적용해 보세요.

다음 예제에서는 CoreDispatcher를 사용하여 강력한 타입의 이벤트를 발생하는 방법을 보여줍니다. 타입 인수는 객체가 아니라 토스트입니다.

public event EventHandler<Toast> ToastCompletedEvent;
private void OnToastCompleted(Toast args)
{
    var completedEvent = ToastCompletedEvent;
    if (completedEvent != null)
    {
        completedEvent(this, args);
    }
}

public void MakeToastWithDispatcher(string message)
{
    Toast toast = new Toast(message);
    // Assume you have a CoreDispatcher at class scope.
    // Initialize it here, then use it from the background thread.
    var window = Windows.UI.Core.CoreWindow.GetForCurrentThread();
    m_dispatcher = window.Dispatcher;

    Task.Run( () =>
    {
        if (ToastCompletedEvent != null)
        {
            m_dispatcher.RunAsync(CoreDispatcherPriority.Normal,
            new DispatchedHandler(() =>
            {
                this.OnToastCompleted(toast);
            })); // end m_dispatcher.RunAsync
         }
     }); // end Task.Run
}

(옵션 2) 타입 정보<의 손실이> 있지만 EventHandler를 사용합니다.

참고 항목

C# Windows 런타임 구성 요소를 작성하는 경우 Windows.Foundation.EventHandler<Object> 형식을 사용할 수 없습니다. 대신 해당 형식이 System.EventHandler로 프로젝션되므로 대신 사용해야 합니다.

백그라운드 스레드에서 이벤트를 보내는 또 다른 방법은 Windows.Foundation.EventHandler<객체를> 이벤트의 타입으로 사용하는 것입니다. Windows는 제네릭 타입으로의 구체적인 인스턴스화 및 그에 대한 프록시 및 스텁을 제공합니다. 단점은 이벤트 인수 및 발신자의 타입 정보가 손실된다는 것입니다. C++ 및 .NET 클라이언트는 설명서를 통해 이벤트를 받을 때 다시 캐스팅할 타입을 알아야 합니다. JavaScript 클라이언트는 기존의 타입 정보가 필요하지 않습니다. 메타데이터의 이름에 따라 인수 속성을 찾습니다.

이 예제는 C#에서 Windows.Foundation.EventHandler<객체를> 사용하는 방법을 보여 줍니다.

public sealed Class1
{
// Declare the event
public event EventHandler<Object> ToastCompletedEvent;

    // Raise the event
    public async void MakeToast(string message)
    {
        Toast toast = new Toast(message);
        // Fire the event from a background thread to allow this thread to continue
        Task.Run(() =>
        {
            if (ToastCompletedEvent != null)
            {
                OnToastCompleted(toast);
            }
        });
    }

    private void OnToastCompleted(Toast args)
    {
        var completedEvent = ToastCompletedEvent;
        if (completedEvent != null)
        {
            completedEvent(this, args);
        }
    }
}

JavaScript 쪽에서 이 이벤트를 사용하는 방법은 다음과 같습니다.

toastCompletedEventHandler: function (event) {
   var toastType = event.toast.toastType;
   document.getElementById("toasterOutput").innerHTML = "<p>Made " + toastType + " toast</p>";
}

(옵션 3) 사용자 고유의 프록시 및 스텁 만들기

타입 정보가 완전히 보존되어 있는 사용자 정의 이벤트 타입의 잠재적 성능 향상을 위해서는, 고유한 프록시 및 스텁 객체를 만들고 이를 앱 패키지에 포함해야 합니다. 이 옵션을 사용해야 하는 경우가 드물게 있는데 바로 다른 두 옵션 중 어느 것도 적절하지 않은 경우입니다. 게다가 이 옵션이 다른 두 옵션보다 더 나은 성능을 제공한다는 보장도 없습니다. 실제 성능은 여러 요인에 따라 달라집니다. Visual Studio 프로파일러 또는 기타 프로파일링 도구를 사용하여 애플리케이션의 실제 성능을 측정하고 이벤트가 실제로 병목 상태에 있는지 확인합니다.

이 문서의 나머지 내용은 C#을 사용하여 기본 Windows 런타임 구성 요소를 만든 다음 C++를 사용하여 JavaScript가 비동기 오퍼레이션에서 구성 요소에 의해 발생하는 Windows.Foundation.TypedEventHandler<TSender, TResult> 이벤트를 사용할 수 있도록 하는 프록시 및 스텁에 대한 DLL을 만드는 방법을 보여 줍니다. (C++ 또는 Visual Basic을 사용하여 구성 요소를 만들 수도 있습니다. 프록시 및 스텁 만들기와 관련된 단계는 동일합니다.) 이 연습은 Windows 런타임 in-process 구성 요소 샘플(C++/CX) 만들기를 기반으로 하며 그 용도를 설명하는 데 도움이 됩니다.

이 연습은 다음 부분으로 이루어져 있습니다.

  • 여기서 기본 Windows 런타임 클래스 두 가지를 만듭니다. 한 클래스는 Windows.FoundationTypedEventHandler TSender, TResult<> 타입의 이벤트를 노출하고 다른 클래스는 TValue에 대한 인수로 JavaScript에 반환되는 타입입니다. 해당 클래스들은 이후 단계를 완료할 때까지 JavaScript와 통신할 수 없습니다.
  • 이 앱은 기본 클래스 객체를 활성화하고, 메서드를 호출하고, Windows 런타임 구성 요소에서 발생하는 이벤트를 처리합니다.
  • 프록시 및 스텁 클래스를 생성하는 도구에 필요합니다.
  • 그런 다음 IDL 파일을 사용하여 프록시 및 스텁에 대한 C 소스 코드를 생성합니다.
  • COM 런타임에서 찾을 수 있도록 프록시 스텁 객체를 등록하고 앱 프로젝트에서 proxy-stub DLL을 참조합니다.

Windows 런타임 구성 요소를 만들기

Visual Studio의 on the menu bar에서 파일을 누르고 > 새 프로젝트를 선택합니다. 새 프로젝트의 대화상자에서, JavaScript 유니버설 Windows를 > 확장한 다음 빈 앱을 선택합니다. 프로젝트의 이름을 ToasterApplication으로 입력한 다음 확인 버튼을 선택합니다.

솔루션에 C# Windows 런타임 구성 요소 추가: 솔루션 탐색기에서, 솔루션의 바로 가기 메뉴를 열고 새 프로젝트 추가를 > 선택합니다. Visual C# > Microsoft Store를 확장하고 Windows 런타임 구성 요소를 선택합니다. 프로젝트의 이름을 ToasterComponent로 입력한 다음 확인 버튼을 선택합니다. ToasterComponent는 이후 단계에서 만들 구성 요소의 루트 네임스페이스가 됩니다.

솔루션 탐색기에서, 솔루션 바로 가기 메뉴를 열고 속성을 선택합니다. 속성 페이지의 대화상자에서 왼쪽 창의 구성 속성을 선택한 다음, 대화 상자 맨 위에서 구성을 디버그로 설정하고 플랫폼을 x86, x64, 또는 ARM으로 설정합니다. 확인 단추를 선택합니다.

중요 플랫폼 = 나중에 솔루션에 추가할 네이티브 코드 Win32 DLL에 대해 유효하지 않으므로 모든 CPU가 작동하지 않습니다.

솔루션 탐색기에서, 프로젝트의 이름과 일치하도록 class1.cs의 이름을 ToasterComponent.cs로 바꿉니다. Visual Studio는 파일의 클래스 이름이 새 파일 이름과 일치하도록 자동으로 바꿉니다.

.cs 파일에서 Windows.Foundation 네임스페이스에 대한 using 지시문을 추가하여 TypedEventHandler를 범위로 가져옵니다.

프록시 및 스텁이 필요한 경우 구성 요소는 인터페이스를 사용하여 퍼블릭 멤버를 노출해야 합니다. ToasterComponent.cs에서, 토스터에 대한 인터페이스 하나를 정의하고 그 토스터가 생성하는 토스트에 대한 인터페이스를 정의합니다.

참고 C#에서는 이 단계를 건너뛸 수 있습니다. 대신 먼저 클래스를 만든 다음 바로 가기 메뉴를 열고 인터페이스 > 추출 리팩터링을 선택합니다. 생성된 코드에서 인터페이스에 공용 접근성을 수동으로 제공합니다.

	public interface IToaster
        {
            void MakeToast(String message);
            event TypedEventHandler<Toaster, Toast> ToastCompletedEvent;

        }
        public interface IToast
        {
            String ToastType { get; }
        }

IToast 인터페이스에 있는 검색 가능한 문자열로 토스트 형식을 설명할 수 있습니다. IToaster 인터페이스에는 토스트를 만드는 메서드와 토스트 생성을 나타내는 이벤트가 있습니다. 이 이벤트는 토스트의 특정 부분(즉, 형식)을 반환하므로 타입화된 이벤트라고 합니다.

다음으로, 이러한 인터페이스를 구현하고 나중에 프로그래밍할 JavaScript 앱에서 액세스할 수 있도록 봉인된 퍼블릭 클래스가 필요합니다.

	public sealed class Toast : IToast
        {
            private string _toastType;

            public string ToastType
            {
                get
                {
                    return _toastType;
                }
            }
            internal Toast(String toastType)
            {
                _toastType = toastType;
            }

        }
        public sealed class Toaster : IToaster
        {
            public event TypedEventHandler<Toaster, Toast> ToastCompletedEvent;

            private void OnToastCompleted(Toast args)
            {
                var completedEvent = ToastCompletedEvent;
                if (completedEvent != null)
                {
                    completedEvent(this, args);
                }
            }

            public void MakeToast(string message)
            {
                Toast toast = new Toast(message);
                // Fire the event from a thread-pool thread to enable this thread to continue
                Windows.System.Threading.ThreadPool.RunAsync(
                (IAsyncAction action) =>
                {
                    if (ToastCompletedEvent != null)
                    {
                        OnToastCompleted(toast);
                    }
                });
           }
        }

이전 코드에서, 토스트를 만든 다음 스레드 풀 작업 항목을 스핀업하여 알림을 실행합니다. 비동기 호출에 await 키워드(keyword)를 적용하도록 IDE에서 제안할 수 있지만, 이 경우 오퍼레이션 결과에 따라 달라지는 작업을 메서드가 수행하지 않기 때문에 필요하지 않습니다.

참고 이전 코드의 비동기 호출은 ThreadPool.RunAsync만을 사용하여 백그라운드 스레드에서 이벤트를 실행하는 간단한 방법을 시연합니다. 다음 예제와 같이 특정 메서드를 작성할 수 있으며, .NET 작업 스케줄러가 UI 스레드에 대한 비동기/대기 호출을 자동으로 마샬링하므로 제대로 작동합니다.  

	public async void MakeToast(string message)
    {
        Toast toast = new Toast(message)
        await Task.Delay(new Random().Next(1000));
        OnToastCompleted(toast);
    }

지금 프로젝트를 빌드하는 경우 클린 빌드해야 합니다.

JavaScript 앱을 프로그래밍하기

이제 JavaScript 앱에 버튼을 추가하여 방금 정의한 클래스를 사용하여 토스트를 만들 수 있습니다. 이렇게 하기 전에, 방금 만든 ToasterComponent 프로젝트에 대한 참조를 추가해야 합니다. 솔루션 탐색기의 ToasterApplication 프로젝트의 바로 가기 메뉴를 연 다음, 추가 > 참조를 선택한 후 새 참조 추가 단추를 선택합니다. 참조 추가 대화 상자의 솔루션 아래 왼쪽 창에서, 구성 요소 프로젝트를 선택한 다음, 가운데 창에서 ToasterComponent를 선택합니다. 확인 단추를 선택합니다.

솔루션 탐색기에서 ToasterApplication 프로젝트에 대한 바로 가기 메뉴를 열고 시작 프로젝트로 설정을 선택합니다.

default.js 파일의 끝에서, 구성 요소를 호출하고 구성 요소로부터 다시 호출될 함수를 포함하는 네임스페이스를 추가합니다. 네임스페이스에는 토스트 만들기 함수와 토스트 완료 이벤트를 처리하는 함수 두 가지가 있습니다. makeToast를 구현하면 Toaster 개체를 만들고 이벤트 처리기를 등록하며 알림을 만들 수 있습니다. 지금까지 이벤트 처리기는 다음과 같이 많은 작업을 수행하지 않습니다.

	WinJS.Namespace.define("ToasterApplication"), {
       makeToast: function () {

          var toaster = new ToasterComponent.Toaster();
          //toaster.addEventListener("ontoastcompletedevent", ToasterApplication.toastCompletedEventHandler);
          toaster.ontoastcompletedevent = ToasterApplication.toastCompletedEventHandler;
          toaster.makeToast("Peanut Butter");
       },

       toastCompletedEventHandler: function(event) {
           // The sender of the event (the delegate's first type parameter)
           // is mapped to event.target. The second argument of the delegate
           // is contained in event, which means in this case event is a
           // Toast class, with a toastType string.
           var toastType = event.toastType;

           document.getElementById('toastOutput').innerHTML = "<p>Made " + toastType + " toast</p>";
        },
    });

makeToast 기능은 단추에 연결되어야 합니다. default.httml을 업데이트하여 토스트 만들기의 결과를 출력하는 버튼과 공간을 포함합니다.

    <body>
        <h1>Click the button to make toast</h1>
        <button onclick="ToasterApplication.makeToast()">Make Toast!</button>
        <div id="toasterOutput">
            <p>No Toast Yet...</p>
        </div>
    </body>

TypedEventHandler를 사용하지 않았다면 이제 로컬 머신에서 앱을 실행하고 단추를 클릭해 알림을 만들 수 있습니다. 그러나 앱에서는 아무 일도 일어나지 않습니다. 이유를 확인하려면 ToastCompletedEvent를 발생시키는 관리 코드를 디버깅합니다. 프로젝트를 중지한 다음 메뉴 모음에서 디버그 > 토스터 애플리케이션 속성을 선택합니다. 디버거 형식관리 전용으로 변경합니다. 메뉴 모음에서 디버그 > 예외를 선택한 다음, 공용 언어 런타임 예외를 선택합니다.

이제 앱을 실행하고 토스트 만들기 버튼을 클릭합니다. 디버거가 유효하지 않은 캐스트 예외를 catch합니다. 메시지상으로 명확하지는 않지만, 이 예외가 발생하는 이유는 해당 인터페이스에 프록시가 없기 때문입니다.

missing proxy

구성 요소에 대한 프록시 및 스텁을 만드는 첫 번째 단계는 인터페이스에 고유 ID 또는 GUID를 추가하는 것입니다. 그러나 코딩을 C#나 Visual Basic에서 하거나, 또는 다른 .NET 언어를 사용해 코딩을 하거나 또는 C++로 코딩하는지에 따라 사용할 GUID 형식이 달라집니다.

구성 요소의 인터페이스에 대한 GUID를 생성하는 방법 (C# 및 기타 .NET 언어)

메뉴 모음에서 도구 > GUID 만들기를 선택합니다. 대화 상자에서 5를 선택합니다. [Guid("xxxxxxxx-xxxx...xxxx")]. 새 GUID 버튼을 선택한 다음 복사 버튼을 선택합니다.

guid generator tool

다시 인터페이스 정의로 이동한 다음, IToaster 인터페이스 바로 앞에 새 GUID를 붙여 넣습니다(아래 예를 참조). (예제에서는 GUID를 사용하지 마세요. 모든 고유 인터페이스에는 고유한 GUID가 있어야 합니다.)

[Guid("FC198F74-A808-4E2A-9255-264746965B9F")]
        public interface IToaster...

System.Runtime.InteropServices 네임스페이스에서 using 지시문을 추가합니다.

IToast 인터페이스에서 이러한 단계들을 반복합니다.

구성 요소의 인터페이스(C++)에 대한 GUID를 생성하는 방법

메뉴 모음에서 도구 > GUID 만들기를 선택합니다. 대화 상자에서 3을 선택합니다. static const struct GUID = {...}. 새 GUID 버튼을 선택한 다음 복사 버튼을 선택합니다.

IToaster 인터페이스 정의 바로 앞에 GUID를 붙여넣습니다. 붙여넣기를 한 후, GUID는 다음 예시와 유사해야 합니다. (예제에서는 GUID를 사용하지 마세요. 모든 고유 인터페이스에는 고유한 GUID가 있어야 합니다.)

// {F8D30778-9EAF-409C-BCCD-C8B24442B09B}
    static const GUID <<name>> = { 0xf8d30778, 0x9eaf, 0x409c, { 0xbc, 0xcd, 0xc8, 0xb2, 0x44, 0x42, 0xb0, 0x9b } };

Windows.Foundation.Metadata에 대한 using 지시문을 추가해서 GuidAttribute를 범위로 가져옵니다.

이제 다음 예제와 같이 형식이 지정되도록 const GUID를 GuidAttribute로 수동으로 변환합니다. 중괄호는 대괄호와 괄호로 대체되고 후행 세미콜론은 제거됩니다.

// {E976784C-AADE-4EA4-A4C0-B0C2FD1307C3}
    [GuidAttribute(0xe976784c, 0xaade, 0x4ea4, 0xa4, 0xc0, 0xb0, 0xc2, 0xfd, 0x13, 0x7, 0xc3)]
    public interface IToaster
    {...

IToast 인터페이스에서 이러한 단계들을 반복합니다.

인터페이스가 고유 ID를 가지고 있으므로 .winmd 파일을 winmdidl 명령줄 도구에 제공하여 IDL 파일을 생성한 다음, 해당 IDL 파일을 MIDL 명령줄 도구에 제공하여 프록시 및 스텁에 대한 C 소스 코드를 생성할 수 있습니다. 다음 단계에서 보여주듯이, 빌드 후 이벤트를 만드는 경우 Visual Studio에서 이 작업을 수행합니다.

프록시 및 스텁 소스 코드를 생성하기

사용자 지정 빌드 후 이벤트를 추가하려면, 솔루션 탐색기에서 ToasterComponent 프로젝트에 대한 바로 가기 메뉴를 열고 속성을 선택합니다. 속성 페이지의 왼쪽 창에서 빌드 이벤트를 선택한 다음, 빌드 후 편집 버튼을 선택합니다. 빌드 후 명령줄에 다음 명령을 추가합니다. (winmdidl 도구를 찾도록 환경 변수를 설정하려면 먼저 배치 파일을 호출해야 합니다.)

call "$(DevEnvDir)..\..\vc\vcvarsall.bat" $(PlatformName)
winmdidl /outdir:output "$(TargetPath)"
midl /metadata_dir "%WindowsSdkDir%References\CommonConfiguration\Neutral" /iid "$(ProjectDir)$(TargetName)_i.c" /env win32 /h "$(ProjectDir)$(TargetName).h" /winmd "Output\$(TargetName).winmd" /W1 /char signed /nologo /winrt /dlldata "$(ProjectDir)dlldata.c" /proxy "$(ProjectDir)$(TargetName)_p.c" "Output\$(TargetName).idl"

중요 ARM 또는 x64 프로젝트 구성에서 MIDL /env 매개 변수를 x64 또는 arm32로 변경합니다.

.winmd 파일이 변경될 때마다 IDL 파일이 다시 생성되도록 하려면 빌드 후 이벤트를 실행하는 시점을 빌드가 프로젝트 출력을 업데이트할 때로 변경합니다. 빌드 이벤트 속성 페이지는 다음과 같아야 합니다. build events

솔루션을 다시 빌드하여 IDL을 생성하고 컴파일합니다.

ToasterComponent 프로젝트 디렉토리에서 ToasterComponent.h, ToasterComponent_i.c, ToasterComponent_p.c 및 dlldata.c를 검색하여 MIDL이 솔루션을 올바르게 컴파일했는지 확인할 수 있습니다.

프록시 및 스텁 코드를 DLL로 컴파일하기

이제 필요한 파일이 있으므로, 해당 파일을 컴파일하여 C++ 파일인 DLL을 생성할 수 있습니다. 프록시 빌드를 지원하는 새 프로젝트를 추가하여 이 과정을 최대한 쉽게 만들 수 있습니다. ToasterApplication 솔루션에 대한 바로 가기 메뉴를 열고 추가 > 새 프로젝트를 선택합니다. 새 프로젝트 대화 상자의 왼쪽 창에서 Visual C++ > Windows > Univeral Windows를 확장하고 가운데 창에서 DLL(UWP 앱)을 선택합니다. (이것은 C++ Windows 런타임 구성 요소 프로젝트가 아닙니다.) 프로젝트 이름을 프록시로 지정한 다음 확인 버튼을 선택합니다. C# 클래스에서 변경이 생기면, 빌드 후 이벤트를 통해 해당 파일이 업데이트됩니다.

기본적으로 프록시 프로젝트는 헤더 .h 파일 및 C++ .cpp 파일을 생성합니다. MIDL에서 생성된 파일에서 DLL이 빌드되므로 .h 및 .cpp 파일이 필요하지 않습니다. 솔루션 탐색기에서 프로젝트에 대한 바로 가기 메뉴를 열고 제거를 선택한 다음, 삭제를 확인합니다.

이제 프로젝트가 비어 있으므로 MIDL에서 생성된 파일을 다시 추가할 수 있습니다. 프록시 프로젝트의 바로 가기 메뉴를 열고 기존 추가 > 기존 항목을 선택합니다. 대화 상자에서 ToasterComponent 프로젝트 디렉터리로 이동하고 ToasterComponent.h, ToasterComponent_i.c, ToasterComponent_p.c 및 dlldata.c 파일을 선택합니다. 추가 단추를 선택합니다.

프록시 프로젝트에서, .def 파일을 만들어 dlldata.c에 설명된 DLL 내보내기를 정의합니다. 프로젝트에 대한 바로 가기 메뉴를 열고 추가 > 새 항목을 선택합니다. 대화 상자의 왼쪽 창에서 코드를 선택한 다음, 가운데 창에서 모듈 정의 파일을 선택합니다. 파일 이름을 proxies.def로 지정한 다음, 추가 단추를 선택합니다. 해당 .def 파일을 열고 dlldata.c에 정의된 내보내기를 포함하도록 수정합니다.

EXPORTS
    DllCanUnloadNow         PRIVATE
    DllGetClassObject       PRIVATE

지금 프로젝트를 빌드하면 실패합니다. 이 프로젝트를 올바르게 컴파일하려면, 프로젝트를 컴파일하고 연결하는 방법을 변경해야 합니다. 솔루션 탐색기에서 Proxies 프로젝트에 대한 바로 가기 메뉴를 연 다음, 속성을 선택합니다. 속성 페이지를 다음과 같이 변경합니다.

왼쪽 창에서 C/C++ > 전처리기를 선택한 다음, 오른쪽 창에서 전처리기 정의를 선택하고 아래쪽 화살표 단추를 선택한 다음, 편집을 선택합니다. 상자에 다음 정의를 추가합니다.

WIN32;_WINDOWS

C/C++ > 미리 컴파일된 헤더에서 미리 컴파일된 헤더미리 컴파일된 헤더 사용 안 함으로 변경한 다음, 적용 단추를 선택합니다.

링커 > 일반에서 가져오기 라이브러리 무시로 변경한 다음, 적용 단추를 선택합니다.

링커 > 입력에서 추가 종속성을 선택하고 아래쪽 화살표 단추를 선택한 다음, 편집을 선택합니다. 상자에 다음 텍스트를 추가합니다.

rpcrt4.lib;runtimeobject.lib

해당 라이브러리를 목록 행에 직접 붙여넣지 마세요. 편집 상자를 사용하여 Visual Studio에서 MSBuild가 올바른 추가 종속성을 유지하도록 합니다.

이러한 변경을 마쳤으면 속성 페이지 대화 상자에서 확인 단추를 선택합니다.

다음으로 ToasterComponent 프로젝트에 대한 종속성을 가져옵니다. 이 과정을 통해 프록시 프로젝트가 빌드되기 전에 토스터가 빌드됩니다. 프록시를 빌드하려면 토스터 프로젝트가 파일을 생성해야 하므로 이 작업이 필요합니다.

프록시 프로젝트에 대한 바로 가기 메뉴를 열고 프로젝트 종속성을 선택합니다. 체크 상자를 선택하여 프록시 프로젝트가 ToasterComponent 프로젝트에 의존하고 있음을 나타내고, Visual Studio에서 올바른 순서로 빌드가 진행되고 있는지 확인합니다.

Visual Studio 메뉴 모음에서 빌드 > 솔루션 다시 빌드를 선택하여 솔루션이 올바르게 빌드되는지 확인합니다.

프록시 및 스텁 등록하기

ToasterApplication 프로젝트에서 package.appxmanifest에 대한 바로 가기 메뉴를 열고 연결 프로그램을 선택합니다. 연결 프로그램 대화 상자에서 XML 텍스트 편집기를 선택한 다음, 확인 단추를 선택합니다. windows.activatableClass.proxyStub 확장 등록을 제공하고 프록시의 GUID를 기반으로 하는 일부 XML에서 붙여 넣기를 해보겠습니다. TOasterComponent_i.c를 열어 .appxmanifest 파일에서 사용할 GUID를 찾으십시오. 다음 예제의 항목과 유사한 항목을 찾습니다. 또한 IToast, IToaster 및 세 번째 인터페이스(Toaster와 Toast라는 두 개의 매개 변수를 가진 입력된 이벤트 처리기)에 대한 정의를 확인합니다. 이것은 Toaster 클래스에 정의되어 있는 이벤트와 일치합니다. IToast 및 IToaster를 위한 GUID가 C# 파일의 인터페이스에 정의된 GUID와 일치하는지 확인합니다. 타입화된 이벤트 처리기 인터페이스는 자동으로 생성되므로, 이 인터페이스의 GUID도 자동으로 생성됩니다.

MIDL_DEFINE_GUID(IID, IID___FITypedEventHandler_2_ToasterComponent__CToaster_ToasterComponent__CToast,0x1ecafeff,0x1ee1,0x504a,0x9a,0xf5,0xa6,0x8c,0x6f,0xb2,0xb4,0x7d);

MIDL_DEFINE_GUID(IID, IID___x_ToasterComponent_CIToast,0xF8D30778,0x9EAF,0x409C,0xBC,0xCD,0xC8,0xB2,0x44,0x42,0xB0,0x9B);

MIDL_DEFINE_GUID(IID, IID___x_ToasterComponent_CIToaster,0xE976784C,0xAADE,0x4EA4,0xA4,0xC0,0xB0,0xC2,0xFD,0x13,0x07,0xC3);

이제 GUID를 복사했으면 이를 추가한 노드의 package.appxmanifest에 붙여 넣고 Extensions라고 이름을 지정한 다음, 다시 포맷합니다. 매니페스트 항목은 다음 예제와 유사하지만, 사용자 고유의 GUID를 사용해야 합니다. XML의 ClassId GUID는 ITypedEventHandler2와 동일합니다. GUID가 ToasterComponent_i.c에 나열된 첫 번째 GUID이기 때문입니다. 여기서 GUID는 대/소문자를 구분하지 않습니다. IToast 및 IToaster에 대한 GUID를 수동으로 다시 포맷하는 대신, 인터페이스 정의로 돌아가서 올바른 형식을 가진 GuidAttribute 값을 얻을 수 있습니다. C++에서, 올바른 형식의 GUID는 주석에 있습니다. 어떤 경우든 ClassId와 이벤트 처리기 모두에 사용되는 GUID의 서식을 수동으로 다시 지정해야 합니다.

	  <Extensions> <!--Use your own GUIDs!!!-->
        <Extension Category="windows.activatableClass.proxyStub">
          <ProxyStub ClassId="1ecafeff-1ee1-504a-9af5-a68c6fb2b47d">
            <Path>Proxies.dll</Path>
            <Interface Name="IToast" InterfaceId="F8D30778-9EAF-409C-BCCD-C8B24442B09B"/>
            <Interface Name="IToaster"  InterfaceId="E976784C-AADE-4EA4-A4C0-B0C2FD1307C3"/>  
            <Interface Name="ITypedEventHandler_2_ToasterComponent__CToaster_ToasterComponent__CToast" InterfaceId="1ecafeff-1ee1-504a-9af5-a68c6fb2b47d"/>
          </ProxyStub>      
        </Extension>
      </Extensions>

확장 XML 노드를 패키지 노드의 직계 자식과 리소스 노드의 피어로 붙여 넣습니다.

계속 진행하기 전에 다음 사항을 확인하십시오.

  • ProxyStub ClassId는 ToasterComponent_i.c 파일의 첫 번째 GUID로 설정됩니다. classId에 대해 이 파일에서 정의된 첫 번째 GUID를 사용합니다. (ITypedEventHandler2의 GUID와 같을 수 있습니다.)
  • 경로는 프록시 이진 파일의 패키지 상대 경로입니다. (이 연습에서 proxies.dll과 ToasterApplication.winmd는 동일한 폴더에 있습니다.)
  • 본 GUID는 올바른 형식으로 되어있습니다. (잘못되기 쉽습니다.)
  • 매니페스트의 인터페이스 ID는 ToasterComponent_i.c 파일의 IID와 일치합니다.
  • 본 인터페이스 이름은 매니페스트에서 고유합니다. 해당 항목은 시스템에서 사용되지 않으므로 값을 선택할 수 있습니다. 정의한 인터페이스와 명확하게 일치하는 인터페이스 이름을 선택하는 것이 좋습니다. 생성된 인터페이스의 경우, 그 이름이 생성된 인터페이스를 나타내야 합니다. ToasterComponent_i.c 파일을 사용하여 인터페이스 이름을 생성할 수 있습니다.

지금 솔루션을 실행하려고 하면 proxies.dll이 페이로드의 일부가 아니라는 오류가 발생합니다. ToasterApplication 프로젝트의 참조 폴더에 대한 바로 가기 메뉴를 열고 참조 추가를 선택합니다. 프록시 프로젝트 옆에 있는 체크 상자를 선택합니다. 또한 ToasterComponent 옆에 있는 체크 상자도 선택되어 있는지 확인합니다. 확인 단추를 선택합니다.

이제 본 프로젝트를 빌드해야 합니다. 프로젝트를 실행하고 토스트를 만들 수 있는지 확인합니다.