Hello,
I am developing a hardware monitoring widget based on Xbox Game Bar Widget using C++/WinRT and UWP XAML.
Environment
- Windows 10 22H2
- Visual Studio 2026
- C++/WinRT
- UWP Xbox Game Bar Widget
- Microsoft.Gaming.XboxGameBar API
Scenario
A Win32 background process (SensorHost.exe) collects hardware sensor information and sends data through Named Pipe:
- CPU temperature
- CPU power
- CPU fan speed
- CPU clock
- CPU utilization
The UWP Game Bar Widget receives this data and updates the XAML UI.
The current architecture:
SensorHost.exe
|
| Named Pipe
|
HardwareManager
|
|
Widget1::m_data
|
|
DispatcherTimer (200ms)
|
|
x:Bind properties
Current implementation
Instead of updating TextBlock.Text directly, I changed to XAML data binding.
XAML:
<TextBlock
x:Name="CpuFanText"
Text="{x:Bind CpuFan}"
Foreground="#F1F4F6"
FontSize="18"
FontWeight="Bold"/>
Widget1 constructor:
Widget1::Widget1() : m_data()
{
InitializeComponent();
App::Hardware().SetUpdateCallback(
std::bind(&Widget1::UpdateDataCallback, this)
);
m_timer =
Windows::UI::Xaml::DispatcherTimer();
m_timer.Interval(
std::chrono::milliseconds(200)
);
m_timer.Tick(
{ this, &Widget1::OnTimerTick }
);
m_timer.Start();
}
The callback only updates the local data:
void Widget1::UpdateDataCallback()
{
auto data = App::Hardware().GetSensorData();
m_data = data;
}
The UI is refreshed by the timer:
void Widget1::OnTimerTick(
winrt::Windows::Foundation::IInspectable const&,
winrt::Windows::Foundation::IInspectable const&)
{
Bindings->Update();
}
The binding properties return hstring:
winrt::hstring Widget1::CpuTemp()
{
return winrt::to_hstring((int)m_data.cpuTemp) + L" ℃";
}
winrt::hstring Widget1::CpuFan()
{
return winrt::to_hstring((int)m_data.cpuFan) + L" RPM";
}
Problem
The memory usage of HardwareBar.exe continuously increases when the widget is running.
After further investigation, I found that the memory growth is directly related to the call:
void Widget1::OnTimerTick(
winrt::Windows::Foundation::IInspectable const&,
winrt::Windows::Foundation::IInspectable const&)
{
Bindings->Update();
}
The behavior is:
When Bindings->Update(); is enabled:
void Widget1::OnTimerTick(
winrt::Windows::Foundation::IInspectable const&,
winrt::Windows::Foundation::IInspectable const&)
{
Bindings->Update();
}
The memory usage of HardwareBar.exe continuously increases over time.
The working set keeps growing even though:
The amount of sensor data remains stable.
The widget UI size does not change.
No new pages or controls are created.
No additional threads are created.
When Bindings->Update(); is commented out:
void Widget1::OnTimerTick(
winrt::Windows::Foundation::IInspectable const&,
winrt::Windows::Foundation::IInspectable const&)
{
// Bindings->Update();
}
The memory growth disappears.
The process memory remains stable during long-term execution.
Therefore, the issue appears to be specifically related to repeatedly calling Bindings->Update() on a C++/WinRT XAML x:Bind generated binding object.
The update interval is only 200ms:
m_timer.Interval(
std::chrono::milliseconds(200)
);
which means Bindings->Update() is called approximately 5 times per second.
I would like to know:
Is repeatedly calling Bindings->Update() in C++/WinRT XAML expected to allocate internal objects that are not immediately released?
Is there a known memory leak issue with x:Bind and Bindings->Update()?
Is there a recommended pattern for updating real-time data in a C++/WinRT Xbox Game Bar Widget?
#pragma once
#include "Widget1.g.h"
#include "App.h"
#include <winrt/Microsoft.Gaming.XboxGameBar.h>
namespace winrt::HardwareBar::implementation
{
struct Widget1 : Widget1T<Widget1>
{
Widget1();
Widget1::~Widget1()
{
if (m_widget)
{
m_widget.SettingsClicked(
m_settingsToken);
}
App::Hardware().SetUpdateCallback(nullptr);
}
int32_t MyProperty();
void MyProperty(int32_t value);
void MyButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args);
winrt::fire_and_forget StartSensorHost();
void OnNavigatedTo(winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
// Settings click handler for widget settings click event
Windows::Foundation::IAsyncAction SettingsButtonClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::Foundation::IInspectable const& e);
winrt::hstring CpuUtil();
winrt::hstring CpuTemp();
winrt::hstring CpuClock();
winrt::hstring CpuPower();
winrt::hstring CpuFan();
void UpdateDataCallback();
void OnTimerTick(
winrt::Windows::Foundation::IInspectable const&,
winrt::Windows::Foundation::IInspectable const&);
private:
winrt::event_token m_settingsToken{};
Microsoft::Gaming::XboxGameBar::XboxGameBarWidget m_widget{ nullptr };
Microsoft::Gaming::XboxGameBar::XboxGameBarWidgetControl m_widgetControl{ nullptr };
SensorData m_data{0};
winrt::Windows::UI::Xaml::DispatcherTimer m_timer{ nullptr };
};
}
namespace winrt::HardwareBar::factory_implementation
{
struct Widget1 : Widget1T<Widget1, implementation::Widget1>
{
};
}
#include "pch.h"
#include "Widget1.h"
#include "Widget1.g.cpp"
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.UI.Core.h>
#include <iomanip>
#include <sstream>
using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::ApplicationModel;
using namespace Microsoft::Gaming::XboxGameBar;
std::string FormatFloat(double value, int precision = 2)
{
std::ostringstream oss;
oss << std::fixed << std::setprecision(precision) << value;
return oss.str();
}
namespace winrt::HardwareBar::implementation
{
Widget1::Widget1() : m_data()
{
InitializeComponent();
App::Hardware().SetUpdateCallback(std::bind(&Widget1::UpdateDataCallback, this));
m_timer =
Windows::UI::Xaml::DispatcherTimer();
m_timer.Interval(
std::chrono::milliseconds(200)
);
m_timer.Tick(
{ this, &Widget1::OnTimerTick }
);
m_timer.Start();
}
void Widget1::UpdateDataCallback() {
auto data =
App::Hardware().GetSensorData();
m_data = data;
}
void Widget1::OnTimerTick(
winrt::Windows::Foundation::IInspectable const&,
winrt::Windows::Foundation::IInspectable const&)
{
Bindings->Update();
}
winrt::hstring Widget1::CpuUtil() {
return winrt::to_hstring((int)m_data.cpuUtil) + L" %";
}
winrt::hstring Widget1::CpuTemp() {
return winrt::to_hstring((int)m_data.cpuTemp) + L" ℃";
}
winrt::hstring Widget1::CpuClock() {
return winrt::to_hstring((int)m_data.cpuClock) + L" MHz";
}
winrt::hstring Widget1::CpuPower() {
return winrt::to_hstring(FormatFloat(m_data.cpuPower) + " W");
}
winrt::hstring Widget1::CpuFan() {
return winrt::to_hstring((int)m_data.cpuFan) + L" RPM";
}
int32_t Widget1::MyProperty()
{
throw hresult_not_implemented();
}
void Widget1::MyProperty(int32_t /* value */)
{
throw hresult_not_implemented();
}
void Widget1::MyButton_Click(IInspectable const&, RoutedEventArgs const&)
{
//myButton().Content(box_value(L"Clicked"));
}
winrt::fire_and_forget Widget1::StartSensorHost()
{
// 使用 co_await 异步等待,不阻塞 UI 线程
co_await FullTrustProcessLauncher::LaunchFullTrustProcessForCurrentAppAsync();
}
void Widget1::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e)
{
m_widget =
e.Parameter()
.as<Microsoft::Gaming::XboxGameBar::XboxGameBarWidget>();
m_widget.MinWindowSize(
{ 200,30 }
);
m_widget.MaxWindowSize(
{ 600,70 }
);
m_widgetControl = XboxGameBarWidgetControl(m_widget);
// Hook up event that's fired when our settings button is clicked
m_settingsToken = m_widget.SettingsClicked({ this, &Widget1::SettingsButtonClick });
}
Windows::Foundation::IAsyncAction Widget1::SettingsButtonClick(
winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::Foundation::IInspectable const& e)
{
auto strong_this{ get_strong() };
co_await m_widget.ActivateSettingsAsync();
//Comment out code below to demonstrate how to activate settings with a Uri string
//
//hstring appExtID = L"WidgetSettings"; // ID of Settings Widget
//hstring appID = L"App";
//hstring uriSubPath = L"[uriSubPath]";
//hstring uriQuery = L"[?uriQuery]";
//hstring uriFragment = L"[#uriFragment]";
//Uri uri = m_widgetControl.CreateActivationUri(appID, appExtID, uriSubPath, uriQuery, uriFragment);
//co_await m_widget.ActivateSettingsWithUriAsync(uri);
}
}
<Page
x:Class="HardwareBar.Widget1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:HardwareBar"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Padding="0"
Margin="0" BorderThickness="0" Background="Transparent">
<Grid Opacity="1" Padding="10,0,0,0"
Margin="0" BorderThickness="0">
<StackPanel
HorizontalAlignment="Left"
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="9" BorderThickness="0">
<TextBlock
Text="CPU"
Foreground="#F1F4F6"
FontSize="18"
FontWeight="Bold"/>
<TextBlock
x:Name="CpuUtilText"
Text="{x:Bind CpuUtil}"
Foreground="#F1F4F6"
FontSize="18"
FontWeight="Bold"/>
<TextBlock
x:Name="CpuTempText"
Text="{x:Bind CpuTemp}"
Foreground="#F1F4F6"
FontSize="18"
FontWeight="Bold"/>
<TextBlock
x:Name="CpuClockText"
Text="{x:Bind CpuClock}"
Foreground="#F1F4F6"
FontSize="18"
FontWeight="Bold"/>
<TextBlock
x:Name="CpuPowerText"
Text="{x:Bind CpuPower}"
Foreground="#F1F4F6"
FontSize="18"
FontWeight="Bold"/>
<TextBlock
x:Name="CpuFanText"
Text="{x:Bind CpuFan}"
Foreground="#F1F4F6"
FontSize="18"
FontWeight="Bold"/>
</StackPanel>
</Grid>
</Page>