이 항목은 Windows 런타임 components with C++/WinRT 항목에서 빌드하는 방법을 보여 주는 Windows 런타임 구성 요소와 이를 사용하는 애플리케이션을 바탕으로 합니다.
이 항목에서 추가하는 새로운 기능은 다음과 같습니다.
- 온도가 영하로 떨어지면 이벤트를 발생하도록 온도계 런타임 클래스를 업데이트합니다.
- 해당 이벤트를 처리할 수 있도록 온도계 런타임 클래스를 사용하는 Core 앱을 업데이트합니다.
메모
C++/WinRT Visual Studio 확장(VSIX) 및 NuGet 패키지(프로젝트 템플릿 및 빌드 지원을 함께 제공)를 설치하고 사용하는 방법에 대한 자세한 내용은 C++/WinRT에 대한 Visual Studio 지원을 참조하세요.
중요합니다
C++/WinRT를 사용하여 런타임 클래스를 사용하고 작성하는 방법에 대한 이해를 지원하는 필수 개념 및 용어는 C++/WinRT 및 C++/WinRT를 사용하여 API 사용 및 API 작성을 참조하세요.
ThermometerWRC 및 ThermometerCoreApp 만들기
코드를 빌드하고 실행할 수 있도록 이 항목에 표시된 업데이트와 함께 수행하려는 경우 첫 번째 단계는 C++/WinRT 토픽을 사용하여 Windows 런타임 구성 요소의 연습을 따르는 것입니다. 이렇게 하면 ThermometerWRC Windows 런타임 구성 요소와 이를 사용하는 ThermometerCoreApp 코어 앱을 갖게 됩니다.
이벤트를 발생하도록 ThermometerWRC 업데이트
아래 목록처럼 보이도록 업데이트 Thermometer.idl 합니다. 단정밀도 부동 소수점 숫자의 인수를 사용하여 대리자 형식이 EventHandler 인 이벤트를 선언하는 방법입니다.
// Thermometer.idl
namespace ThermometerWRC
{
runtimeclass Thermometer
{
Thermometer();
void AdjustTemperature(Single deltaFahrenheit);
event Windows.Foundation.EventHandler<Single> TemperatureIsBelowFreezing;
};
}
파일을 저장합니다. 프로젝트는 현재 상태로는 끝까지 빌드되지 않지만, 그래도 지금 빌드하여 업데이트된 버전의 \ThermometerWRC\ThermometerWRC\Generated Files\sources\Thermometer.h 및 Thermometer.cpp 스텁 파일을 생성하세요. 이러한 파일 내에서 이제 TemperatureIsBelowFreezing 이벤트의 스텁 구현을 볼 수 있습니다. C++/WinRT에서 IDL 선언 이벤트는 오버로드된 함수 집합으로 구현됩니다(속성이 오버로드된 get 및 set 함수 쌍으로 구현되는 방식과 유사). 오버로드 중 하나는 등록할 대리자를 매개변수로 받고, 토큰(winrt::event_token)을 반환합니다. 다른 하나는 토큰을 사용하고 연결된 대리자의 등록을 취소합니다.
이제 Thermometer.h와 Thermometer.cpp을 열고 Thermometer 런타임 클래스의 구현을 업데이트합니다.
Thermometer.h에 오버로드된 두 개의 TemperatureIsBelowFreezing 함수와 이러한 함수 구현에 사용할 private 이벤트 데이터 멤버를 추가합니다.
// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
struct Thermometer : ThermometerT<Thermometer>
{
...
winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler);
void TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept;
private:
winrt::event<Windows::Foundation::EventHandler<float>> m_temperatureIsBelowFreezingEvent;
...
};
}
...
위에서 볼 수 있듯이 이벤트는 winrt::event 구조체 템플릿으로 표현되며, 특정 대리자 형식(자체는 인수 형식으로 매개 변수화될 수 있습니다)으로 매개 변수화됩니다.
Thermometer.cpp에서 오버로드된 두 개의 TemperatureIsBelowFreezing 함수를 구현하세요.
// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<float> const& handler)
{
return m_temperatureIsBelowFreezingEvent.add(handler);
}
void Thermometer::TemperatureIsBelowFreezing(winrt::event_token const& token) noexcept
{
m_temperatureIsBelowFreezingEvent.remove(token);
}
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
m_temperatureFahrenheit += deltaFahrenheit;
if (m_temperatureFahrenheit < 32.f) m_temperatureIsBelowFreezingEvent(*this, m_temperatureFahrenheit);
}
}
메모
자동 이벤트 해지자가 무엇인지에 대한 자세한 내용은 등록된 대리자 해지(Revoke)를 참조하세요. 이벤트에 대한 자동 이벤트 해지자 구현을 무료로 받을 수 있습니다. 즉, C++/WinRT 프로젝션에서 제공하는 이벤트 해지자에 대한 오버로드를 구현할 필요가 없습니다.
다른 오버로드(등록 및 수동 해지 오버로드)는 프로젝션에 기본적으로 포함되어 있지 않습니다. 이는 시나리오에 최적으로 구현할 수 있는 유연성을 제공하기 위한 것입니다. 이러한 구현에 표시된 대로 event::add 및 event::remove를 호출하는 것은 효율적이고 동시성 및 스레드 안전성을 보장하는 기본값입니다. 그러나 이벤트가 매우 많은 경우 각각에 대한 이벤트 필드를 원하지 않고 대신 일종의 스파스 구현을 선택할 수 있습니다.
온도가 영하로 떨어지면 TemperatureIsBelowFreezing 이벤트를 발생하도록 AdjustTemperature 함수의 구현이 업데이트되었음을 위에서 확인할 수도 있습니다.
이벤트를 처리하도록 ThermometerCoreApp 업데이트
ThermometerCoreApp 프로젝트에서 App.cpp다음 코드를 변경하여 이벤트 처리기를 등록한 다음 온도가 영하로 떨어지도록 합니다.
WINRT_ASSERT 는 매크로 정의이며 _ASSERTE 확장됩니다.
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
winrt::event_token m_eventToken;
...
void Initialize(CoreApplicationView const &)
{
m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto &, float temperatureFahrenheit)
{
WINRT_ASSERT(temperatureFahrenheit < 32.f); // Put a breakpoint here.
});
}
...
void Uninitialize()
{
m_thermometer.TemperatureIsBelowFreezing(m_eventToken);
}
...
void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
{
m_thermometer.AdjustTemperature(-1.f);
...
}
...
};
OnPointerPressed 메서드의 변경 사항에 유의하세요. 이제 창을 클릭할 때마다 온도계의 온도에서 화씨 1도씩 뺍니다. 이제 앱은 온도가 영하로 떨어지면 발생하는 이벤트를 처리합니다. 이벤트가 예상대로 발생되고 있음을 보여 주려면 TemperatureIsBelowFreezing 이벤트를 처리하는 람다 식 내에 중단점을 배치하고, 앱을 실행하고, 창 내부를 클릭합니다.
ABI에서 매개 변수가 있는 대리자
구성 요소와 사용하는 애플리케이션 간과 같은 ABI(애플리케이션 이진 인터페이스)에서 이벤트에 액세스할 수 있어야 하는 경우 이벤트는 Windows 런타임 대리자 형식을 사용해야 합니다. 위의 예제에서는 Windows::Foundation::EventHandler<T> Windows 런타임 대리자 형식을 사용합니다. TypedEventHandler<TSender, TResult>는 Windows 런타임 대리자 형식의 또 다른 예입니다.
이러한 두 대리자 형식에 대한 형식 매개 변수는 ABI를 교차해야 하므로 형식 매개 변수도 Windows 런타임 형식이어야 합니다. 여기에는 Windows 런타임 클래스, 타사 런타임 클래스 및 숫자 및 문자열과 같은 기본 형식이 포함됩니다. 컴파일러는 해당 제약 조건을 잊어버린 경우 "T는 WinRT 형식이어야 합니다" 오류를 지원합니다.
다음은 코드 목록 형식의 예입니다. 이 항목의 앞부분에서 만든 ThermometerWRC 및 ThermometerCoreApp 프로젝트로 시작하고 이러한 목록의 코드처럼 보이도록 해당 프로젝트의 코드를 편집합니다.
이 첫 번째 목록은 ThermometerWRC 프로젝트에 대한 것입니다. 아래와 같이 ThermometerWRC.idl를 편집한 후 프로젝트를 빌드한 다음, 앞서 Thermometer.h 및 .cpp에 대해 했던 것처럼 Generated Files 폴더에서 MyEventArgs.h 및 .cpp를 프로젝트에 복사합니다. 두 파일 모두에서 static_assert를 삭제하는 것을 잊지 마세요.
// ThermometerWRC.idl
namespace ThermometerWRC
{
[default_interface]
runtimeclass MyEventArgs
{
Single TemperatureFahrenheit{ get; };
}
[default_interface]
runtimeclass Thermometer
{
...
event Windows.Foundation.EventHandler<ThermometerWRC.MyEventArgs> TemperatureIsBelowFreezing;
...
};
}
// MyEventArgs.h
#pragma once
#include "MyEventArgs.g.h"
namespace winrt::ThermometerWRC::implementation
{
struct MyEventArgs : MyEventArgsT<MyEventArgs>
{
MyEventArgs() = default;
MyEventArgs(float temperatureFahrenheit);
float TemperatureFahrenheit();
private:
float m_temperatureFahrenheit{ 0.f };
};
}
// MyEventArgs.cpp
#include "pch.h"
#include "MyEventArgs.h"
#include "MyEventArgs.g.cpp"
namespace winrt::ThermometerWRC::implementation
{
MyEventArgs::MyEventArgs(float temperatureFahrenheit) : m_temperatureFahrenheit(temperatureFahrenheit)
{
}
float MyEventArgs::TemperatureFahrenheit()
{
return m_temperatureFahrenheit;
}
}
// Thermometer.h
...
struct Thermometer : ThermometerT<Thermometer>
{
...
winrt::event_token TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler);
...
private:
winrt::event<Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs>> m_temperatureIsBelowFreezingEvent;
...
}
...
// Thermometer.cpp
#include "MyEventArgs.h"
...
winrt::event_token Thermometer::TemperatureIsBelowFreezing(Windows::Foundation::EventHandler<ThermometerWRC::MyEventArgs> const& handler) { ... }
...
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
m_temperatureFahrenheit += deltaFahrenheit;
if (m_temperatureFahrenheit < 32.f)
{
auto args = winrt::make_self<winrt::ThermometerWRC::implementation::MyEventArgs>(m_temperatureFahrenheit);
m_temperatureIsBelowFreezingEvent(*this, *args);
}
}
...
이 목록은 ThermometerCoreApp 프로젝트에 대한 것입니다.
// App.cpp
...
void Initialize(CoreApplicationView const&)
{
m_eventToken = m_thermometer.TemperatureIsBelowFreezing([](const auto&, ThermometerWRC::MyEventArgs args)
{
float degrees = args.TemperatureFahrenheit();
WINRT_ASSERT(degrees < 32.f); // Put a breakpoint here.
});
}
...
ABI를 통한 간단한 신호
이벤트와 함께 매개 변수 또는 인수를 전달할 필요가 없는 경우 고유한 간단한 Windows 런타임 대리자 형식을 정의할 수 있습니다. 아래 예제에서는 더 간단한 버전의 Thermometer 런타임 클래스를 보여줍니다. SignalDelegate라는 대리자 형식을 선언한 다음 매개 변수가 있는 이벤트 대신 신호 형식 이벤트를 발생시키는 데 사용합니다.
// ThermometerWRC.idl
namespace ThermometerWRC
{
delegate void SignalDelegate();
runtimeclass Thermometer
{
Thermometer();
event ThermometerWRC.SignalDelegate SignalTemperatureIsBelowFreezing;
void AdjustTemperature(Single value);
};
}
// Thermometer.h
...
namespace winrt::ThermometerWRC::implementation
{
struct Thermometer : ThermometerT<Thermometer>
{
...
winrt::event_token SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler);
void SignalTemperatureIsBelowFreezing(winrt::event_token const& token);
void AdjustTemperature(float deltaFahrenheit);
private:
winrt::event<ThermometerWRC::SignalDelegate> m_signal;
float m_temperatureFahrenheit{ 0.f };
};
}
// Thermometer.cpp
...
namespace winrt::ThermometerWRC::implementation
{
winrt::event_token Thermometer::SignalTemperatureIsBelowFreezing(ThermometerWRC::SignalDelegate const& handler)
{
return m_signal.add(handler);
}
void Thermometer::SignalTemperatureIsBelowFreezing(winrt::event_token const& token)
{
m_signal.remove(token);
}
void Thermometer::AdjustTemperature(float deltaFahrenheit)
{
m_temperatureFahrenheit += deltaFahrenheit;
if (m_temperatureFahrenheit < 32.f)
{
m_signal();
}
}
}
// App.cpp
struct App : implements<App, IFrameworkViewSource, IFrameworkView>
{
ThermometerWRC::Thermometer m_thermometer;
winrt::event_token m_eventToken;
...
void Initialize(CoreApplicationView const &)
{
m_eventToken = m_thermometer.SignalTemperatureIsBelowFreezing([] { /* ... */ });
}
...
void Uninitialize()
{
m_thermometer.SignalTemperatureIsBelowFreezing(m_eventToken);
}
...
void OnPointerPressed(IInspectable const &, PointerEventArgs const & args)
{
m_thermometer.AdjustTemperature(-1.f);
...
}
...
};
프로젝트 내의 매개 변수가 있는 대리자, 단순 신호 및 콜백
해당 이벤트가 Windows 런타임 형식으로 제한되지 않는 Visual Studio 프로젝트 내부 이벤트가 필요한 경우 winrt::event<Delegate> 클래스 템플릿을 계속 사용할 수 있습니다. 실제 Windows 런타임 대리자 형식 대신 winrt::delegate를 사용하면 됩니다. winrt::delegate는 Windows 런타임이 아닌 매개변수도 지원하기 때문입니다.
아래 예제에서는 먼저 매개 변수(기본적으로 간단한 신호)를 취하지 않는 대리자 서명과 문자열을 사용하는 대리자 서명을 보여줍니다.
winrt::event<winrt::delegate<>> signal;
signal.add([] { std::wcout << L"Hello, "; });
signal.add([] { std::wcout << L"World!" << std::endl; });
signal();
winrt::event<winrt::delegate<std::wstring>> log;
log.add([](std::wstring const& message) { std::wcout << message.c_str() << std::endl; });
log.add([](std::wstring const& message) { Persist(message); });
log(L"Hello, World!");
이벤트에 원하는 만큼 구독 대리자를 추가할 수 있다는 점을 확인해 보세요. 그러나 이벤트와 관련된 오버헤드가 있습니다. 필요한 것이 구독하는 대리자가 하나뿐인 간단한 콜백뿐이라면 winrt::delegate<... T>를 단독으로 사용할 수 있습니다.
winrt::delegate<> signalCallback;
signalCallback = [] { std::wcout << L"Hello, World!" << std::endl; };
signalCallback();
winrt::delegate<std::wstring> logCallback;
logCallback = [](std::wstring const& message) { std::wcout << message.c_str() << std::endl; }f;
logCallback(L"Hello, World!");
이벤트 및 대리자를 프로젝트 내에서 내부적으로 사용하는 C++/CX 코드베이스를 포팅하는 경우, winrt::delegate는 C++/WinRT에서 그 패턴을 재현하는 데 도움이 됩니다.
지연 가능한 이벤트
Windows 런타임 일반적인 패턴은 지연 가능한 이벤트입니다. 이벤트 처리기는 이벤트 인수의 GetDeferral 메서드를 호출하여 지연을 사용합니다. 이렇게 하면 연기가 완료될 때까지 이벤트 후 작업을 미뤄야 한다는 것을 이벤트 소스에 알립니다. 이렇게 하면 이벤트 처리기가 이벤트에 대한 응답으로 비동기 작업을 수행할 수 있습니다.
winrt::d eferrable_event_args 구조체 템플릿은 Windows 런타임 지연 패턴을 구현(생성)하는 도우미 클래스입니다. 다음은 예제입니다.
// Widget.idl
namespace Sample
{
runtimeclass WidgetStartingEventArgs
{
Windows.Foundation.Deferral GetDeferral();
Boolean Cancel;
};
runtimeclass Widget
{
event Windows.Foundation.TypedEventHandler<
Widget, WidgetStartingEventArgs> Starting;
};
}
// Widget.h
namespace winrt::Sample::implementation
{
struct Widget : WidgetT<Widget>
{
Widget() = default;
event_token Starting(Windows::Foundation::TypedEventHandler<
Sample::Widget, Sample::WidgetStartingEventArgs> const& handler)
{
return m_starting.add(handler);
}
void Starting(event_token const& token) noexcept
{
m_starting.remove(token);
}
private:
event<Windows::Foundation::TypedEventHandler<
Sample::Widget, Sample::WidgetStartingEventArgs>> m_starting;
};
struct WidgetStartingEventArgs : WidgetStartingEventArgsT<WidgetStartingEventArgs>,
deferrable_event_args<WidgetStartingEventArgs>
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
bool Cancel() const noexcept { return m_cancel; }
void Cancel(bool value) noexcept { m_cancel = value; }
bool m_cancel = false;
};
}
이벤트 수신자가 지연 가능한 이벤트 패턴을 사용하는 방법은 다음과 같습니다.
// EventRecipient.h
widget.Starting([](auto sender, auto args) -> fire_and_forget
{
auto deferral = args.GetDeferral();
if (!co_await CanWidgetStartAsync(sender))
{
// Do not allow the widget to start.
args.Cancel(true);
}
deferral.Complete();
});
이벤트 원본의 구현자(생산자)로서 이벤트 인수 클래스를 winrt::deferrable_event_args에서 상속합니다. < deferrable_event_argsT>는 T::GetDeferral을 구현합니다. 또한 새 도우미 메서드 deferrable_event_args::wait_for_deferrals를 제공하며, 이 메서드는 보류 중인 모든 디퍼럴이 완료되면 완료됩니다(디퍼럴이 전혀 요청되지 않은 경우에는 즉시 완료됨).
// Widget.h
IAsyncOperation<bool> TryStartWidget(Widget const& widget)
{
auto args = make_self<WidgetStartingEventArgs>();
// Raise the event to let people know that the widget is starting
// and give them a chance to prevent it.
m_starting(widget, *args);
// Wait for deferrals to complete.
co_await args->wait_for_deferrals();
// Use the results.
bool started = false;
if (!args->Cancel())
{
widget.InsertBattery();
widget.FlipPowerSwitch();
started = true;
}
co_return started;
}
디자인 지침
대리자가 아닌 이벤트를 함수 매개 변수로 전달하는 것이 좋습니다. winrt::event의 add 함수는 한 가지 예외입니다. 이 경우 대리자를 전달해야 하기 때문입니다. 이 지침의 이유는 대리자가 서로 다른 Windows 런타임 언어(클라이언트 등록을 지원하는지 또는 여러 언어를 지원하는지 여부)에서 서로 다른 양식을 사용할 수 있기 때문입니다. 여러 구독자 모델을 사용하는 이벤트는 훨씬 더 예측 가능하고 일관된 옵션을 구성합니다.
이벤트 처리기 대리자의 서명은 보낸 사람 (IInspectable) 및 인수 (일부 이벤트 인수 형식(예: RoutedEventArgs)의 두 매개 변수로 구성되어야 합니다.
내부 API를 디자인하는 경우 이러한 지침이 반드시 적용되지는 않습니다. 하지만 내부 API는 시간이 지남에 따라 공개되는 경우가 많습니다.
관련 항목
Windows developer