mirror of
https://github.com/microsoft/microsoft-ui-xaml.git
synced 2026-10-28 08:25:50 +08:00
Persist WinUI metadata across WinUI shutdown to allow WinUI restart (#11982)
## Fixes WinUI cannot be shut down and restarted in a process. This change fixes that problem, allowing for shut down and restart. Addresses #11741 ## PR Type - [x] Bugfix - [ ] Feature - [ ] Code style update (formatting, renaming) - [ ] Refactoring (no functional changes, no API changes) - [ ] Build related changes - [ ] Documentation content changes - [ ] Other (please describe): ## Description Second attempt to solve this problem. This one is simpler and does not involve new events on `WindowsXamlManager`. Thanks to Evelyn and Morten for the suggestion. Shutting down WinUI and restarting results in a crash in `MicaBackdrop`'s `Kind` property. The problem here is stale data. MUXC registers custom `DependencyProperty` objects (including `MicaBackdrop.Kind`) with MUX, and also has references to those DP objects. When MUX shuts down, it clears out all fields in its registered `CCustomDependencyProperty` objects, including those from MUXC. MUXC continues to hold on to its old and cleared out DP objects. When MUXC restarts, it sees that its caches are still filled, so it doesn't bother registering new custom DPs. It then tries to reuse its old `Kind` property, finds that the default value is now null, and crashes. The fix is for MUX to not clear out its registered `CCustomDependencyProperty` objects when shutting down. These types are agile and are valid on any UI thread. They are also potentially stored in `static readonly` fields, which means they can't be discarded and re-registered. In practice, both MUXC and the Windows Community Toolkit have custom DPs that can be reused across WinUI shutdown and restart. Two other changes as part of this PR: * Breaks a reference cycle that leaks the `WindowsXamlManager`. Previously it referenced a `XamlCore` object that referenced it back. * Updates WinUICppIslandsSampleApp to show a count of WinUI objects (Application, DispatcherQueueController, WindowsXamlManager, and DesktopWindowXamlSource). ### Current Behavior When WinUI shuts down we clear out all metadata and custom DPs. When WinUI restarts, external components (like MUXC) will find their previously registered custom DPs invalid and hit errors. After WinUI shuts down the `WindowsXamlManager` object will still be alive due to a leak. ### New Behavior When WinUI shuts down we leave all existing metadata and custom DPs alone. When WinUI restarts, external components (like MUXC) can continue using their custom DPs. Note that this has caveats for cleanup. The `Application` object now survives WinUI shutdown. Metadata objects also survive until process shutdown and technically leak, were it not for the entire process being torn down. Previously WinUI shutting down was where we cleared metadata. Now that metadata persists past shutdown, the next place that we were attempting to delete metadata is unloading the MUX binary, which only happens during process shutdown. But binary unload is not a safe place to do cleanup, because metadata objects may point to objects in other binaries which have already been unloaded. We choose to leak these objects and let the process take them down instead. When WinUI shuts down the `WindowsXamlManager` object will be released afterwards. ## Customer Impact Non-WASDK (e.g. Win32, WinForms) apps using Xaml islands can now release every single island, then re-create an island later. ## Regression Potential - [x] Low risk — isolated change, limited scope - [ ] Medium risk — touches shared components or public APIs - [ ] High risk — architectural or breaking API change ## How Has This Been Tested? - [x] I have performed a self-review of my own code - [x] I have added tests to cover my changes - [x] Existing tests pass locally ## Screenshots (if appropriate) <!-- If you are making visual changes, include before/after screenshots. --> <img width="1631" height="1346" alt="WinUICppIslandsSampleApp" src="https://github.com/user-attachments/assets/2f2ed871-1a1f-4ca1-8668-a2b18a5bb0d2" /> <img width="1368" height="1069" alt="WinUICsIslandsSampleApp" src="https://github.com/user-attachments/assets/459f0a12-5833-4282-9694-4e65952c97c5" />
This commit is contained in:
1 parent
09046b98ea
commit
d0cf13fc06
44 files changed
+1548
-214
No files matched your search
@@ -125,6 +125,7 @@ UninstallTestApps("NugetPackageTestApp",
|
||||
"Microsoft.WinUI3ControlsGallery.Debug",
|
||||
"Microsoft.WinUI3ControlsGallery",
|
||||
"WinUICppDesktopSampleApp",
|
||||
"WinUICppIslandsSampleApp",
|
||||
"DisableXamlGeneratedMainCs",
|
||||
"DisableXamlGeneratedMainNoCtorCs",
|
||||
"DisableXamlGeneratedMainCpp",
|
||||
@@ -173,6 +174,7 @@ $namesOfProcessesForDumpCollection = @(
|
||||
"WinUIGallery.Desktop.exe",
|
||||
"WinUICsDesktopSampleApp.exe",
|
||||
"WinUICppDesktopSampleApp.exe",
|
||||
"WinUICppIslandsSampleApp.exe",
|
||||
"DisableXamlGeneratedMainCs.exe",
|
||||
"DisableXamlGeneratedMainNoCtorCs.exe",
|
||||
"DisableXamlGeneratedMainCpp.exe",
|
||||
|
||||
+9
@@ -6,6 +6,7 @@
|
||||
#include "App.h"
|
||||
#include "MainWindow.h"
|
||||
#include <winrt/Microsoft.Windows.ApplicationModel.Resources.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.Hosting.h>
|
||||
#include "ShutdownOrderValidation.h"
|
||||
|
||||
using namespace winrt;
|
||||
@@ -73,6 +74,14 @@ App::~App()
|
||||
void App::OnLaunched(LaunchActivatedEventArgs const&)
|
||||
{
|
||||
window = make<MainWindow>();
|
||||
auto windowsXamlManager = Microsoft::UI::Xaml::Hosting::WindowsXamlManager::GetForCurrentThread();
|
||||
windowsXamlManager.XamlShutdownCompletedOnThread(
|
||||
[this](
|
||||
const Microsoft::UI::Xaml::Hosting::WindowsXamlManager&,
|
||||
const Microsoft::UI::Xaml::Hosting::XamlShutdownCompletedOnThreadEventArgs&)
|
||||
{
|
||||
window = nullptr;
|
||||
});
|
||||
window.Activate();
|
||||
window.Title(L"WinUICppDesktopSampleApp");
|
||||
window.as<MainWindow>()->RootFrame().Navigate(xaml_typename<Page1>());
|
||||
|
||||
+49
@@ -83,4 +83,53 @@
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK.WinUI" Version="$(WinUIVersion)" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
<!-- Register the WinUI binaries shipped in this self-contained package instead of
|
||||
relying on activatable-class registrations from an installed runtime package. -->
|
||||
<PropertyGroup>
|
||||
<WinUIClassRegistrationsFile>$(NugetPackageDirectory)\Microsoft.WindowsAppSDK.WinUI.$(WinUIVersion)\buildTransitive\native\LiftedWinRTClassRegistrations.xml</WinUIClassRegistrationsFile>
|
||||
<WinUIAppxFragment>$(IntermediateOutputPath)WinUIClassRegistrations.appxfragment</WinUIAppxFragment>
|
||||
</PropertyGroup>
|
||||
<Target Name="InsertWinUIActivatableClassEntries"
|
||||
AfterTargets="WinAppSdkInsertActivatableClassEntries"
|
||||
BeforeTargets="_GenerateAppxPackageRecipeFile;_WireUpCoreRuntime"
|
||||
Condition="Exists('$(WinUIClassRegistrationsFile)')"
|
||||
Inputs="$(MSBuildProjectFullPath);$(WinUIClassRegistrationsFile);$(FinalAppxManifestName)"
|
||||
Outputs="$(FinalAppxManifestName).winui.incremental">
|
||||
<PropertyGroup>
|
||||
<AppxManifestNamespaces>
|
||||
<Namespace Prefix="ns" Uri="http://schemas.microsoft.com/appx/manifest/foundation/windows10" />
|
||||
</AppxManifestNamespaces>
|
||||
</PropertyGroup>
|
||||
<XmlPeek XmlInputPath="$(FinalAppxManifestName)"
|
||||
Query="/ns:Package/ns:Extensions/ns:Extension/ns:InProcessServer/ns:ActivatableClass[@ActivatableClassId='Microsoft.UI.Xaml.XamlTypeInfo.XamlControlsXamlMetaDataProvider']"
|
||||
Namespaces="$(AppxManifestNamespaces)">
|
||||
<Output TaskParameter="Result" ItemName="ExistingWinUIClassRegistration" />
|
||||
</XmlPeek>
|
||||
<XmlPeek XmlInputPath="$(WinUIClassRegistrationsFile)"
|
||||
Query="/ns:Data/ns:Extension[ns:InProcessServer/ns:Path='Microsoft.UI.Xaml.dll' or ns:InProcessServer/ns:Path='Microsoft.UI.Xaml.Controls.dll' or ns:InProcessServer/ns:Path='Microsoft.UI.Xaml.Controls.Tabular.dll']"
|
||||
Namespaces="$(AppxManifestNamespaces)"
|
||||
Condition="'@(ExistingWinUIClassRegistration)' == ''">
|
||||
<Output TaskParameter="Result" ItemName="WinUIClassRegistration" />
|
||||
</XmlPeek>
|
||||
<PropertyGroup Condition="'@(ExistingWinUIClassRegistration)' == ''">
|
||||
<WinUIClassRegistrationFragment>
|
||||
%3C?xml version="1.0" encoding="utf-8"?%3E
|
||||
%3CFragment xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"%3E
|
||||
%3CExtensions%3E
|
||||
@(WinUIClassRegistration->'%(Identity)','
')
|
||||
%3C/Extensions%3E
|
||||
%3C/Fragment%3E
|
||||
</WinUIClassRegistrationFragment>
|
||||
</PropertyGroup>
|
||||
<WriteLinesToFile File="$(WinUIAppxFragment)"
|
||||
Lines="$(WinUIClassRegistrationFragment)"
|
||||
Overwrite="true"
|
||||
Condition="'@(ExistingWinUIClassRegistration)' == ''" />
|
||||
<WinAppSdkInsertActivatableClassEntries
|
||||
FullPathToAppxManifest="$(FinalAppxManifestName)"
|
||||
FullPathToAppxFragment="$(WinUIAppxFragment)"
|
||||
ExcludeWebView2ActivatableClassEntries="false"
|
||||
Condition="'@(ExistingWinUIClassRegistration)' == ''" />
|
||||
<Touch Files="$(FinalAppxManifestName).winui.incremental" AlwaysCreate="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -18,19 +18,15 @@ using namespace WinUICppIslandsSampleApp::implementation;
|
||||
// To learn more about WinUI and the WinUI project structure,
|
||||
// see: https://learn.microsoft.com/windows/apps/winui/
|
||||
|
||||
void OnAppCreated();
|
||||
void OnAppDestructed();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
App::App()
|
||||
{
|
||||
s_hasExistedInProcess = true;
|
||||
::OutputDebugString(L">>> App::App has been called.\n");
|
||||
|
||||
m_initialWindowsXamlManager = winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
|
||||
winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
@@ -44,12 +40,14 @@ App::App()
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
++s_instanceCount;
|
||||
}
|
||||
|
||||
App::~App()
|
||||
{
|
||||
::OutputDebugString(L">>> App::~App has been called.\n");
|
||||
OnAppDestructed();
|
||||
--s_instanceCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#pragma push_macro("GetCurrentTime")
|
||||
#undef GetCurrentTime
|
||||
|
||||
@@ -19,7 +21,12 @@ namespace winrt::WinUICppIslandsSampleApp::implementation
|
||||
|
||||
void OnLaunched(Microsoft::UI::Xaml::LaunchActivatedEventArgs const&);
|
||||
|
||||
winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager m_initialWindowsXamlManager{ nullptr };
|
||||
static inline bool s_hasExistedInProcess{ false };
|
||||
static uint32_t InstanceCount()
|
||||
{
|
||||
return s_instanceCount.load();
|
||||
}
|
||||
|
||||
private:
|
||||
static inline std::atomic_uint32_t s_instanceCount{ 0 };
|
||||
};
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "resource.h"
|
||||
#include <inspectable.h>
|
||||
#include <Microsoft.UI.Dispatching.Interop.h> // For ContentPreTranslateMessage
|
||||
#include <atomic>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Microsoft::UI;
|
||||
@@ -14,6 +15,21 @@ using namespace winrt::Microsoft::UI::Xaml;
|
||||
using namespace winrt::Microsoft::UI::Xaml::Hosting;
|
||||
|
||||
const auto static invalidReason = static_cast<XamlSourceFocusNavigationReason>(-1);
|
||||
std::atomic_uint32_t g_xamlSourceCount{ 0 };
|
||||
|
||||
DesktopWindow::~DesktopWindow()
|
||||
{
|
||||
// The user can close a window without clearing out the DWXSs first, which can cause the DWXS count to be wrong.
|
||||
// In that case we don't get anything that calls RemoveDesktopWindowXamlSource to update the count, and the DWXS
|
||||
// itself is deleted when the window is deleted. Accounting for the deleted DWXSs here to keep the top status bar
|
||||
// message accurate.
|
||||
g_xamlSourceCount.fetch_sub(static_cast<uint32_t>(m_xamlSources.size()));
|
||||
}
|
||||
|
||||
uint32_t DesktopWindow::XamlSourceCount()
|
||||
{
|
||||
return g_xamlSourceCount.load();
|
||||
}
|
||||
|
||||
XamlSourceFocusNavigationReason GetReasonFromKey(WPARAM key)
|
||||
{
|
||||
@@ -123,9 +139,9 @@ bool DesktopWindow::NavigateFocus(MSG* msg)
|
||||
int DesktopWindow::MessageLoop(HACCEL hAccelTable)
|
||||
{
|
||||
MSG msg = {};
|
||||
|
||||
|
||||
while (GetMessage(&msg, nullptr, 0, 0))
|
||||
{
|
||||
{
|
||||
if (!ContentPreTranslateMessage(&msg) && !TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
|
||||
{
|
||||
if (!NavigateFocus(&msg))
|
||||
@@ -214,7 +230,7 @@ DesktopWindowXamlSource DesktopWindow::CreateDesktopWindowsXamlSource(DWORD dwSt
|
||||
WindowId mainWndId = GetWindowIdFromWindow(m_hMainWnd);
|
||||
desktopSource.Initialize(mainWndId);
|
||||
|
||||
// Get the new child window's hwnd
|
||||
// Get the new child window's hwnd
|
||||
WindowId childWndId = desktopSource.SiteBridge().WindowId();
|
||||
HWND hWndXamlIsland = GetWindowFromWindowId(childWndId);
|
||||
DWORD dwNewStyle = GetWindowLong(hWndXamlIsland, GWL_STYLE);
|
||||
@@ -224,6 +240,7 @@ DesktopWindowXamlSource DesktopWindow::CreateDesktopWindowsXamlSource(DWORD dwSt
|
||||
m_takeFocusEventRevokers.push_back(desktopSource.TakeFocusRequested(winrt::auto_revoke, { this, &DesktopWindow::OnTakeFocusRequested }));
|
||||
|
||||
m_xamlSources.push_back(desktopSource);
|
||||
++g_xamlSourceCount;
|
||||
|
||||
return desktopSource;
|
||||
}
|
||||
@@ -232,13 +249,14 @@ void DesktopWindow::RemoveDesktopWindowXamlSource(winrt::Microsoft::UI::Xaml::Ho
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(m_xamlSources.size()); ++i)
|
||||
{
|
||||
|
||||
|
||||
if (m_xamlSources[i].as<IInspectable>() == dwxs.as<IInspectable>())
|
||||
{
|
||||
m_xamlSources[i].Close();
|
||||
m_xamlSources.erase(m_xamlSources.begin() + i);
|
||||
m_takeFocusEventRevokers.erase(m_takeFocusEventRevokers.begin() + i);
|
||||
|
||||
--g_xamlSourceCount;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
class DesktopWindow
|
||||
{
|
||||
public:
|
||||
~DesktopWindow();
|
||||
|
||||
int MessageLoop(HACCEL hAccelTable);
|
||||
static uint32_t XamlSourceCount();
|
||||
|
||||
private:
|
||||
winrt::Microsoft::UI::Xaml::Hosting::DesktopWindowXamlSource GetFocusedIsland();
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "DesktopWindow.h"
|
||||
#include <Psapi.h>
|
||||
#include "XamlUtil.h"
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
@@ -27,13 +29,106 @@ WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
|
||||
// This pointer needs to be global because the Application object is global, there can only be one in a process.
|
||||
winrt::com_ptr< winrt::WinUICppIslandsSampleApp::implementation::App> g_app{ nullptr };
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class WinUIState
|
||||
{
|
||||
Running,
|
||||
ShuttingDown,
|
||||
ShutdownComplete,
|
||||
};
|
||||
|
||||
thread_local winrt::Microsoft::UI::Dispatching::DispatcherQueueController s_dispatcherQueueController{ nullptr };
|
||||
thread_local std::vector<MainWindow*> s_windowsOnThread;
|
||||
std::atomic_uint32_t s_dispatcherQueueControllerCount{ 0 };
|
||||
std::atomic<WinUIState> s_winUIState{ WinUIState::ShutdownComplete };
|
||||
std::mutex s_windowsXamlManagersMutex;
|
||||
std::vector<winrt::weak_ref<winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager>> s_windowsXamlManagers;
|
||||
|
||||
void RemoveWindowsXamlManager(void* managerIdentity)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_windowsXamlManagersMutex);
|
||||
for (auto managerIterator = s_windowsXamlManagers.begin(); managerIterator != s_windowsXamlManagers.end();)
|
||||
{
|
||||
const auto manager = managerIterator->get();
|
||||
WINRT_ASSERT(manager);
|
||||
|
||||
if (winrt::get_abi(manager.as<IInspectable>()) == managerIdentity)
|
||||
{
|
||||
managerIterator = s_windowsXamlManagers.erase(managerIterator);
|
||||
}
|
||||
else
|
||||
{
|
||||
++managerIterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LogCurrentWindowsXamlManager()
|
||||
{
|
||||
const auto currentManager =
|
||||
winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager::GetForCurrentThread();
|
||||
if (!currentManager)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(s_windowsXamlManagersMutex);
|
||||
for (auto managerIterator = s_windowsXamlManagers.begin(); managerIterator != s_windowsXamlManagers.end();)
|
||||
{
|
||||
const auto manager = managerIterator->get();
|
||||
WINRT_ASSERT(manager);
|
||||
|
||||
if (manager.as<IInspectable>() == currentManager.as<IInspectable>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
++managerIterator;
|
||||
}
|
||||
}
|
||||
s_windowsXamlManagers.emplace_back(currentManager);
|
||||
|
||||
auto managerIdentity = winrt::get_abi(currentManager.as<IInspectable>());
|
||||
currentManager.XamlShutdownCompletedOnThread(
|
||||
[managerIdentity](const auto&, const auto&)
|
||||
{
|
||||
RemoveWindowsXamlManager(managerIdentity);
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t WindowsXamlManagerCount()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_windowsXamlManagersMutex);
|
||||
return static_cast<uint32_t>(s_windowsXamlManagers.size());
|
||||
}
|
||||
|
||||
void EnsureDispatcherQueueController()
|
||||
{
|
||||
if (!s_dispatcherQueueController)
|
||||
{
|
||||
s_dispatcherQueueController =
|
||||
winrt::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread();
|
||||
++s_dispatcherQueueControllerCount;
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownDispatcherQueueController()
|
||||
{
|
||||
if (s_dispatcherQueueController)
|
||||
{
|
||||
s_dispatcherQueueController.ShutdownQueue();
|
||||
s_dispatcherQueueController = nullptr;
|
||||
--s_dispatcherQueueControllerCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] int APIENTRY MainWindow::Run(_In_ HINSTANCE hInstance, _In_ int nCmdShow)
|
||||
{
|
||||
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
||||
|
||||
// Start a DispatcherQueueController. A DispatcherQueue must be running on the thread for XAML to work.
|
||||
auto dqc = winrt::Microsoft::UI::Dispatching::DispatcherQueueController::CreateOnCurrentThread();
|
||||
|
||||
MainWindow mainWindow(hInstance, nCmdShow);
|
||||
|
||||
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_ISLANDSAMPLEWINUI3));
|
||||
@@ -42,13 +137,16 @@ winrt::com_ptr< winrt::WinUICppIslandsSampleApp::implementation::App> g_app{ nul
|
||||
|
||||
// Calling DispatcherQueueController.ShutdownQueue will shutdown XAML on the thread, and automatically close some
|
||||
// other WinAppSDK objects too.
|
||||
dqc.ShutdownQueue();
|
||||
ShutdownDispatcherQueueController();
|
||||
g_app = nullptr;
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(HINSTANCE hInstance, int nCmdShow, HWND parentHWnd) noexcept
|
||||
{
|
||||
s_windowsOnThread.push_back(this);
|
||||
|
||||
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
|
||||
LoadStringW(hInstance, IDC_ISLANDSAMPLEWINUI3, szWindowClass, MAX_LOADSTRING);
|
||||
|
||||
@@ -84,6 +182,12 @@ MainWindow::~MainWindow()
|
||||
m_dispatcherQueueShutdownStartingToken = {};
|
||||
}
|
||||
}
|
||||
|
||||
const auto window = std::find(s_windowsOnThread.begin(), s_windowsOnThread.end(), this);
|
||||
if (window != s_windowsOnThread.end())
|
||||
{
|
||||
s_windowsOnThread.erase(window);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] LRESULT MainWindow::MessageHandler(UINT const message, WPARAM const wParam, LPARAM const lParam) noexcept
|
||||
@@ -156,8 +260,13 @@ void MainWindow::CreateButton(const wchar_t* name, DWORD id, std::function<void(
|
||||
|
||||
bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
{
|
||||
m_label = CreateWindow(L"STATIC", L"Starting up...", WS_CHILD | WS_VISIBLE, 10, 10, 500, 20, m_hMainWnd, 0, NULL, NULL);
|
||||
RECT clientRect{};
|
||||
::GetClientRect(m_hMainWnd, &clientRect);
|
||||
const int labelWidth = clientRect.right > 20 ? clientRect.right - 20 : 0;
|
||||
m_label = CreateWindow(L"STATIC", L"Starting up...", WS_CHILD | WS_VISIBLE, 10, 10, labelWidth, 20, m_hMainWnd, 0, NULL, NULL);
|
||||
SendMessage(m_label, WM_SETFONT, (LPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
|
||||
m_objectCountLabel = CreateWindow(L"STATIC", L"Object counts: starting up...", WS_CHILD | WS_VISIBLE, 10, 30, labelWidth, 20, m_hMainWnd, 0, NULL, NULL);
|
||||
SendMessage(m_objectCountLabel, WM_SETFONT, (LPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE);
|
||||
::SetTimer(m_hMainWnd, 101, 500, nullptr);
|
||||
|
||||
static bool s_createRTL = false; // whether to create the next DesktopWindowXamlSource content as RTL
|
||||
@@ -165,16 +274,24 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
|
||||
try
|
||||
{
|
||||
// Every XAML thread needs its own DispatcherQueueController, even when XAML is
|
||||
// already running elsewhere in the process.
|
||||
EnsureDispatcherQueueController();
|
||||
|
||||
// Start App object if needed
|
||||
if (!XamlUtil::IsXamlRunningInProcess() && g_app == nullptr)
|
||||
{
|
||||
winrt::make<winrt::WinUICppIslandsSampleApp::implementation::App>().as(g_app);
|
||||
if (!StartXaml())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
size_t position = m_xamlSources.size();
|
||||
|
||||
// If XAML's not already running on the thread, creating a XAML element before creating the DesktopWindowXamlSource will fail.
|
||||
auto dwxs = CreateDesktopWindowsXamlSource(WS_TABSTOP);
|
||||
LogCurrentWindowsXamlManager();
|
||||
|
||||
auto stackPanel = StackPanel();
|
||||
dwxs.Content(stackPanel);
|
||||
@@ -192,7 +309,7 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
|
||||
auto height = 450;
|
||||
auto width = 900;
|
||||
RectInt32 rect = { 10, 80 + static_cast<int>((height+5) * position), width, height };
|
||||
RectInt32 rect = { 10, 95 + static_cast<int>((height+5) * position), width, height };
|
||||
dwxs.SiteBridge().MoveAndResize(rect);
|
||||
|
||||
auto textBox = TextBox();
|
||||
@@ -206,20 +323,24 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
|
||||
auto createModalWindowButton = Button();
|
||||
createModalWindowButton.Content(winrt::box_value(L"Create Modal Window"));
|
||||
createModalWindowButton.Click([this,dwxs](auto, auto) {
|
||||
createModalWindowButton.Click([this](auto, auto) {
|
||||
::OutputDebugStringW(L">>> createModalWindowButton clicked.\n");
|
||||
::DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), m_hMainWnd, &MainWindow::DialogBoxProc, 0);
|
||||
});
|
||||
|
||||
auto closeDwxsButton = Button();
|
||||
closeDwxsButton.Content(winrt::box_value(L"Close this DesktopWindowXamlSource"));
|
||||
closeDwxsButton.Click([this, dwxs](auto, auto) {
|
||||
this->RemoveDesktopWindowXamlSource(dwxs);
|
||||
closeDwxsButton.Click([this, weakDwxs = winrt::make_weak(dwxs)](auto, auto) {
|
||||
if (auto dwxs = weakDwxs.get())
|
||||
{
|
||||
this->RemoveDesktopWindowXamlSource(dwxs);
|
||||
}
|
||||
});
|
||||
|
||||
auto nestedPumpButton = Button();
|
||||
nestedPumpButton.Content(winrt::box_value(L"Nested pump"));
|
||||
nestedPumpButton.Click([nestedPumpButton](auto, auto) {
|
||||
nestedPumpButton.Click([](const IInspectable& sender, auto) {
|
||||
auto nestedPumpButton = sender.as<Button>();
|
||||
nestedPumpButton.Content(winrt::box_value(L"Running..."));
|
||||
|
||||
const auto startTick = ::GetTickCount();
|
||||
@@ -254,10 +375,10 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
|
||||
auto timerButton = Button();
|
||||
timerButton.Content(winrt::box_value(L"500ms timer"));
|
||||
timerButton.Click([this, dwxs](auto, auto) {
|
||||
timerButton.Click([](auto, auto) {
|
||||
auto timer = DispatcherTimer();
|
||||
timer.Interval(TimeSpan(5000000L));
|
||||
timer.Tick([this](auto, auto) {
|
||||
timer.Tick([](auto, auto) {
|
||||
// do nothing
|
||||
});
|
||||
timer.Start();
|
||||
@@ -266,10 +387,10 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
|
||||
auto timer2Button = Button();
|
||||
timer2Button.Content(winrt::box_value(L"0s timer"));
|
||||
timer2Button.Click([this, dwxs](auto, auto) {
|
||||
timer2Button.Click([](auto, auto) {
|
||||
auto timer = DispatcherTimer();
|
||||
timer.Interval(TimeSpan(0));
|
||||
timer.Tick([this](auto, auto) {
|
||||
timer.Tick([](auto, auto) {
|
||||
// do nothing
|
||||
});
|
||||
timer.Start();
|
||||
@@ -278,7 +399,7 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
|
||||
auto timer3Button = Button();
|
||||
timer3Button.Content(winrt::box_value(L"500ms timer, no Tick handler"));
|
||||
timer3Button.Click([this, dwxs](auto, auto) {
|
||||
timer3Button.Click([](auto, auto) {
|
||||
auto timer = DispatcherTimer();
|
||||
timer.Interval(TimeSpan(5000000L));
|
||||
timer.Start();
|
||||
@@ -332,11 +453,11 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
|
||||
auto commandBarFlyoutButton1 = winrt::Microsoft::UI::Xaml::Markup::XamlReader::Load(L" \
|
||||
<Button xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' Content='Inline CommandBarFlyout' />").as<Button>();
|
||||
commandBarFlyoutButton1.Click([this, commandBarFlyout1, commandBarFlyoutButton1](auto, auto)
|
||||
commandBarFlyoutButton1.Click([commandBarFlyout1](const IInspectable& sender, auto)
|
||||
{
|
||||
auto myOption = winrt::Microsoft::UI::Xaml::Controls::Primitives::FlyoutShowOptions();
|
||||
myOption.ShowMode(winrt::Microsoft::UI::Xaml::Controls::Primitives::FlyoutShowMode::Transient);
|
||||
commandBarFlyout1.ShowAt(commandBarFlyoutButton1, myOption);
|
||||
commandBarFlyout1.ShowAt(sender.as<FrameworkElement>(), myOption);
|
||||
});
|
||||
|
||||
auto commandBarFlyout2 = winrt::Microsoft::UI::Xaml::Markup::XamlReader::Load(L" \
|
||||
@@ -384,11 +505,11 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
|
||||
auto commandBarFlyoutButton2 = winrt::Microsoft::UI::Xaml::Markup::XamlReader::Load(L" \
|
||||
<Button xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' Content='Windowed CommandBarFlyout' />").as<Button>();
|
||||
commandBarFlyoutButton2.Click([this, commandBarFlyout2, commandBarFlyoutButton2](auto, auto)
|
||||
commandBarFlyoutButton2.Click([commandBarFlyout2](const IInspectable& sender, auto)
|
||||
{
|
||||
auto myOption = winrt::Microsoft::UI::Xaml::Controls::Primitives::FlyoutShowOptions();
|
||||
myOption.ShowMode(winrt::Microsoft::UI::Xaml::Controls::Primitives::FlyoutShowMode::Transient);
|
||||
commandBarFlyout2.ShowAt(commandBarFlyoutButton2, myOption);
|
||||
commandBarFlyout2.ShowAt(sender.as<FrameworkElement>(), myOption);
|
||||
});
|
||||
|
||||
auto horizStackPanel = StackPanel();
|
||||
@@ -443,6 +564,7 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
[this](const winrt::Microsoft::UI::Dispatching::DispatcherQueue&, const winrt::Microsoft::UI::Dispatching::DispatcherQueueShutdownStartingEventArgs&)
|
||||
{
|
||||
::OutputDebugString(L">>> DispatcherQueue.ShutdownStarting fired\n");
|
||||
m_dispatcherQueueShutdownStartingToken = {};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -452,16 +574,18 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
::StringCchPrintf(str, ARRAYSIZE(str), L"Error: 0x%x", ex.code().value);
|
||||
::MessageBox(GetHandle(), str, L"Error", MB_OK);
|
||||
}
|
||||
}, 10, 40, 200, 30);
|
||||
}, 10, 55, 200, 30);
|
||||
|
||||
CreateButton(L"More...", 208, [this](HWND hwnd) {
|
||||
// Show a context menu with some interesting options.
|
||||
HMENU contextMenu = ::CreatePopupMenu();
|
||||
|
||||
const bool startAppEnabled = (g_app == nullptr && !XamlUtil::IsXamlRunningInProcess());
|
||||
const bool releaseAppEnabled = (g_app != nullptr);
|
||||
::AppendMenuW(contextMenu, MF_STRING | (startAppEnabled ? 0 : MF_GRAYED), IDM_START_APP, L"Create Xaml Application object");
|
||||
::AppendMenuW(contextMenu, MF_STRING | (releaseAppEnabled ? 0 : MF_GRAYED), IDM_RELEASE_APP, L"Release Xaml Application object");
|
||||
const bool xamlRunning = XamlUtil::IsXamlRunningInProcess();
|
||||
const bool shutdownInProgress = (s_winUIState == WinUIState::ShuttingDown);
|
||||
const bool startAppEnabled = !xamlRunning && !shutdownInProgress;
|
||||
const bool releaseAppEnabled = xamlRunning && !shutdownInProgress;
|
||||
::AppendMenuW(contextMenu, MF_STRING | (startAppEnabled ? 0 : MF_GRAYED), IDM_START_APP, L"Start WinUI");
|
||||
::AppendMenuW(contextMenu, MF_STRING | (releaseAppEnabled ? 0 : MF_GRAYED), IDM_RELEASE_APP, L"Shut down WinUI (public APIs)");
|
||||
::AppendMenuW(contextMenu, MF_SEPARATOR, 0, nullptr);
|
||||
::AppendMenuW(contextMenu, MF_STRING, IDM_CREATE_NEW_THREAD, L"Create new window on new thread");
|
||||
::AppendMenuW(contextMenu, MF_STRING, IDM_CREATE_MODAL_WINDOW, L"Create modal window on same thread");
|
||||
@@ -473,7 +597,7 @@ bool MainWindow::OnCreate(HWND, LPCREATESTRUCT)
|
||||
::GetWindowRect(hwnd, &rc);
|
||||
|
||||
::TrackPopupMenu(contextMenu, TPM_TOPALIGN | TPM_LEFTALIGN, rc.left + 5, rc.top + 5, 0, this->m_hMainWnd, nullptr);
|
||||
}, 210, 40, 50, 30);
|
||||
}, 210, 55, 50, 30);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -490,35 +614,10 @@ void MainWindow::OnCommand(HWND, int id, HWND hwndCtl, UINT )
|
||||
switch (id)
|
||||
{
|
||||
case IDM_START_APP:
|
||||
if (winrt::WinUICppIslandsSampleApp::implementation::App::s_hasExistedInProcess)
|
||||
{
|
||||
auto result = ::MessageBox(GetHandle(),
|
||||
L"It's currently not supported to create more than one Application in a process, even if the previous "
|
||||
L"instance has been closed. Press OK to proceed", L"Warning", MB_OKCANCEL | MB_ICONWARNING);
|
||||
if (result == IDCANCEL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This object derrives from Microsoft.UI.Xaml.Application. Creating this object will also create a WindowsXamlManager,
|
||||
// effectively starting up XAML in the process and on the thread.
|
||||
winrt::make<winrt::WinUICppIslandsSampleApp::implementation::App>().as(g_app);
|
||||
StartXaml();
|
||||
break;
|
||||
case IDM_RELEASE_APP:
|
||||
{
|
||||
g_app->m_initialWindowsXamlManager = nullptr;
|
||||
g_app = nullptr;
|
||||
|
||||
// MUXC has a special export that clears out some of its static state.
|
||||
HMODULE muxcHandle = GetModuleHandle(L"Microsoft.UI.Xaml.Controls.dll");
|
||||
if (muxcHandle != NULL)
|
||||
{
|
||||
typedef void(__stdcall* DeinitializeMuxcPtr)();
|
||||
auto deinitializeMuxcPtr = reinterpret_cast<DeinitializeMuxcPtr>(
|
||||
GetProcAddress(muxcHandle, "DeinitializeMUXC"));
|
||||
deinitializeMuxcPtr();
|
||||
}
|
||||
}
|
||||
ShutdownXaml();
|
||||
break;
|
||||
case IDM_CREATE_NEW_THREAD:
|
||||
::CreateThread(nullptr, 0, &MainWindow::CreateNewWindowOnNewThreadProc, this, 0, 0);
|
||||
@@ -541,7 +640,7 @@ void MainWindow::OnCommand(HWND, int id, HWND hwndCtl, UINT )
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto b : m_buttons)
|
||||
for (auto& b : m_buttons)
|
||||
{
|
||||
if (b.id == id)
|
||||
{
|
||||
@@ -551,6 +650,77 @@ void MainWindow::OnCommand(HWND, int id, HWND hwndCtl, UINT )
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::StartXaml()
|
||||
{
|
||||
if (s_winUIState == WinUIState::ShuttingDown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
EnsureDispatcherQueueController();
|
||||
|
||||
if (!g_app)
|
||||
{
|
||||
// Creating the Application also creates the WindowsXamlManager needed to start XAML.
|
||||
winrt::make<winrt::WinUICppIslandsSampleApp::implementation::App>().as(g_app);
|
||||
LogCurrentWindowsXamlManager();
|
||||
}
|
||||
else if (!XamlUtil::IsXamlRunningOnThread())
|
||||
{
|
||||
// With no current Application, XAML restores the retained App as Application.Current.
|
||||
winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
|
||||
LogCurrentWindowsXamlManager();
|
||||
}
|
||||
|
||||
s_winUIState = WinUIState::Running;
|
||||
return true;
|
||||
}
|
||||
catch (const winrt::hresult_error& ex)
|
||||
{
|
||||
wchar_t message[200]{};
|
||||
::StringCchPrintf(message, ARRAYSIZE(message), L"Unable to start XAML: 0x%08X", ex.code().value);
|
||||
::MessageBox(GetHandle(), message, L"XAML startup failed", MB_OK | MB_ICONERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::ShutdownXaml()
|
||||
{
|
||||
if (s_dispatcherQueueControllerCount.load() != 1)
|
||||
{
|
||||
::MessageBox(
|
||||
GetHandle(),
|
||||
L"Close the sample's other top-level windows before shutting down XAML. "
|
||||
L"Process-wide shutdown requires every XAML thread to stop.",
|
||||
L"XAML shutdown blocked",
|
||||
MB_OK | MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
s_winUIState = WinUIState::ShuttingDown;
|
||||
|
||||
for (auto window : s_windowsOnThread)
|
||||
{
|
||||
window->ClearXamlSources();
|
||||
}
|
||||
|
||||
ShutdownDispatcherQueueController();
|
||||
s_winUIState = WinUIState::ShutdownComplete;
|
||||
}
|
||||
catch (const winrt::hresult_error& ex)
|
||||
{
|
||||
s_winUIState = s_dispatcherQueueController ? WinUIState::Running : WinUIState::ShutdownComplete;
|
||||
|
||||
wchar_t message[200]{};
|
||||
::StringCchPrintf(message, ARRAYSIZE(message), L"Unable to shut down XAML: 0x%08X", ex.code().value);
|
||||
::MessageBox(GetHandle(), message, L"XAML shutdown failed", MB_OK | MB_ICONERROR);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::OnDestroy(HWND hwnd)
|
||||
{
|
||||
base_type::OnDestroy(hwnd);
|
||||
@@ -558,9 +728,15 @@ void MainWindow::OnDestroy(HWND hwnd)
|
||||
|
||||
void MainWindow::OnResize(HWND, UINT state, int cx, int cy)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(cx);
|
||||
UNREFERENCED_PARAMETER(cy);
|
||||
UNREFERENCED_PARAMETER(state);
|
||||
|
||||
if (m_label)
|
||||
{
|
||||
const int labelWidth = cx > 20 ? cx - 20 : 0;
|
||||
::MoveWindow(m_label, 10, 10, labelWidth, 20, TRUE);
|
||||
::MoveWindow(m_objectCountLabel, 10, 30, labelWidth, 20, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::OnPaint(HWND)
|
||||
@@ -606,6 +782,31 @@ void MainWindow::OnTimer(HWND, UINT)
|
||||
workingSetInMb);
|
||||
::SetWindowText(m_label, str);
|
||||
}
|
||||
|
||||
{
|
||||
const wchar_t* winUIState = L"Running";
|
||||
switch (s_winUIState.load())
|
||||
{
|
||||
case WinUIState::ShuttingDown:
|
||||
winUIState = L"Shutting down";
|
||||
break;
|
||||
case WinUIState::ShutdownComplete:
|
||||
winUIState = L"Shutdown complete";
|
||||
break;
|
||||
}
|
||||
|
||||
wchar_t str[200];
|
||||
::StringCchPrintf(
|
||||
str,
|
||||
ARRAYSIZE(str),
|
||||
L"Object counts | Application: %u | DispatcherQueueController: %u | WindowsXamlManager: %u | DesktopWindowXamlSource: %u | WinUI: %s",
|
||||
winrt::WinUICppIslandsSampleApp::implementation::App::InstanceCount(),
|
||||
s_dispatcherQueueControllerCount.load(),
|
||||
WindowsXamlManagerCount(),
|
||||
DesktopWindow::XamlSourceCount(),
|
||||
winUIState);
|
||||
::SetWindowText(m_objectCountLabel, str);
|
||||
}
|
||||
}
|
||||
|
||||
// Just used to store information for the DialogBox
|
||||
|
||||
@@ -33,14 +33,16 @@ private:
|
||||
|
||||
static DWORD WINAPI CreateNewWindowOnNewThreadProc(_In_ LPVOID parameter);
|
||||
|
||||
bool StartXaml();
|
||||
void ShutdownXaml();
|
||||
|
||||
void CreateButton(const wchar_t* name, DWORD id, std::function<void(HWND)> func, int x, int y, int w, int h);
|
||||
std::vector<ButtonCommand> m_buttons;
|
||||
|
||||
winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager m_windowsXamlManager{ nullptr };
|
||||
|
||||
winrt::event_token m_dispatcherQueueShutdownStartingToken;
|
||||
|
||||
HWND m_label{ 0 };
|
||||
HWND m_objectCountLabel{ 0 };
|
||||
|
||||
static INT_PTR CALLBACK DialogBoxProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
|
||||
@@ -3,29 +3,8 @@
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "App.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
|
||||
winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager g_windowsXamlManager{ nullptr };
|
||||
bool g_appDestructed {false};
|
||||
|
||||
void OnAppCreated()
|
||||
{
|
||||
// We need to initialize the WindowsXamlManager _while_ the App object is being created. If we initialize the
|
||||
// WindowsXamlManager _before_ the App object is created, this will cause the DXamlCore to start up, using it's
|
||||
// default MUX.Application object, so the app won't have a chance to use its own. If we initialize the
|
||||
// WindowsXamlManager _after_ the App object is created, the App creation will fail, because we'll call
|
||||
// MUX.Application.LoadComponent before the DXamlCore is initialized. So, we must do it here.
|
||||
g_windowsXamlManager = winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread();
|
||||
g_appDestructed = false;
|
||||
}
|
||||
|
||||
void OnAppDestructed()
|
||||
{
|
||||
g_appDestructed = true;
|
||||
}
|
||||
|
||||
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
|
||||
_In_opt_ HINSTANCE hPrevInstance,
|
||||
_In_ LPWSTR lpCmdLine,
|
||||
|
||||
@@ -22,17 +22,8 @@ namespace XamlUtil
|
||||
|
||||
inline bool IsXamlRunningOnThread()
|
||||
{
|
||||
if (IsXamlLoaded())
|
||||
{
|
||||
try
|
||||
{
|
||||
// Creating a button will throw a RPC_E_WRONG_THREAD HRESULT if XAML's not running on the thread.
|
||||
auto b = winrt::Microsoft::UI::Xaml::Controls::Button();
|
||||
return true;
|
||||
}
|
||||
catch (winrt::hresult_wrong_thread) {}
|
||||
}
|
||||
return false;
|
||||
return IsXamlLoaded() &&
|
||||
winrt::Microsoft::UI::Xaml::Hosting::WindowsXamlManager::GetForCurrentThread() != nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<config>
|
||||
<add key="repositoryPath" value="$\..\packages" />
|
||||
</config>
|
||||
<disabledPackageSources>
|
||||
<clear />
|
||||
</disabledPackageSources>
|
||||
<packageSources>
|
||||
<clear />
|
||||
<add key="MUX-Dependencies" value="https://pkgs.dev.azure.com/shine-oss/microsoft-ui-xaml/_packaging/MUX-Dependencies/nuget/v3/index.json" />
|
||||
|
||||
<!-- a local directory to allow testing nupkg files without pushing to a remote feed -->
|
||||
<add key="packagestore" value="..\..\packagestore" />
|
||||
</packageSources>
|
||||
<activePackageSource>
|
||||
<add key="All" value="(Aggregate source)" />
|
||||
</activePackageSource>
|
||||
</configuration>
|
||||
@@ -0,0 +1,8 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<!-- Windows Community Toolkit has dependent packages. Pick the version that's anonymously downloadable from shine-oss. -->
|
||||
<WindowsAppSdkPackageVersion Condition="'$(WindowsAppSdkPackageVersion)' == ''">2.4.0</WindowsAppSdkPackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\..\Packages.props" />
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
# WinUI C# Islands shutdown and restart sample
|
||||
|
||||
This sample uses a WinForms window as a process-lifetime host for WinUI content. Unlike a
|
||||
normal C# WinUI application, the native host remains alive after the WinUI DispatcherQueue
|
||||
shuts down, allowing WinUI to be initialized again in the same process.
|
||||
|
||||
The hosted content is a compiled C# XAML `UserControl` containing a MUXC `NumberBox` and a
|
||||
Windows Community Toolkit `SettingsCard`. This exercises managed XAML metadata, MUXC
|
||||
dependency-property caches, and Toolkit control state across WinUI generations.
|
||||
|
||||
Use the buttons to:
|
||||
|
||||
1. **Start WinUI** - create a `DispatcherQueueController`, `WindowsXamlManager`,
|
||||
`DesktopWindowXamlSource`, managed `Application`, and hosted XAML content.
|
||||
2. **Shut down WinUI** - release the hosted content and manager, then shut down the
|
||||
DispatcherQueue.
|
||||
3. **Restart WinUI** - perform a complete shutdown followed by another initialization.
|
||||
|
||||
The event log records `XamlShutdownCompletedOnThread` and the DispatcherQueue shutdown events.
|
||||
|
||||
Open `WinUICsIslandsSampleApp.sln`, select an architecture such as x64, and run the
|
||||
`WinUICsIslandsSampleApp` project.
|
||||
@@ -0,0 +1,45 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.6.33829.357
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinUICsIslandsSampleApp", "WinUICsIslandsSampleApp\WinUICsIslandsSampleApp.csproj", "{6B8E437C-F470-43AD-A559-FDA88EA32B36}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug_test|ARM64 = Debug_test|ARM64
|
||||
Debug_test|x64 = Debug_test|x64
|
||||
Debug_test|x86 = Debug_test|x86
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug_test|ARM64.ActiveCfg = Debug_test|ARM64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug_test|ARM64.Build.0 = Debug_test|ARM64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug_test|x64.ActiveCfg = Debug_test|x64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug_test|x64.Build.0 = Debug_test|x64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug_test|x86.ActiveCfg = Debug_test|x86
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug_test|x86.Build.0 = Debug_test|x86
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug|x64.Build.0 = Debug|x64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Debug|x86.Build.0 = Debug|x86
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Release|x64.ActiveCfg = Release|x64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Release|x64.Build.0 = Release|x64
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Release|x86.ActiveCfg = Release|x86
|
||||
{6B8E437C-F470-43AD-A559-FDA88EA32B36}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {7D841CEE-8517-473E-84F0-D23134368484}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Application
|
||||
x:Class="WinUICsIslandsSampleApp.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
|
||||
namespace WinUICsIslandsSampleApp;
|
||||
|
||||
public partial class App : Microsoft.UI.Xaml.Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitialWindowsXamlManager = WindowsXamlManager.InitializeForCurrentThread();
|
||||
|
||||
try
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
InitialWindowsXamlManager.Dispose();
|
||||
InitialWindowsXamlManager = null;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public WindowsXamlManager? InitialWindowsXamlManager { get; private set; }
|
||||
|
||||
public void ReleaseWindowsXamlManager()
|
||||
{
|
||||
InitialWindowsXamlManager?.Dispose();
|
||||
InitialWindowsXamlManager = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Hosting;
|
||||
using Forms = System.Windows.Forms;
|
||||
|
||||
namespace WinUICsIslandsSampleApp;
|
||||
|
||||
internal sealed class MainForm : Forms.Form
|
||||
{
|
||||
private enum WinUIState
|
||||
{
|
||||
Running,
|
||||
ShuttingDown,
|
||||
ShutdownComplete,
|
||||
}
|
||||
|
||||
private readonly Forms.Button _startButton;
|
||||
private readonly Forms.Button _shutdownButton;
|
||||
private readonly Forms.Button _restartButton;
|
||||
private readonly Forms.Label _statusLabel;
|
||||
private readonly Forms.Label _objectCountsLabel;
|
||||
private readonly Forms.Panel _xamlHostPanel;
|
||||
private readonly Forms.TextBox _eventLog;
|
||||
private readonly Forms.SplitContainer _contentSplitContainer;
|
||||
|
||||
private DispatcherQueueController? _dispatcherQueueController;
|
||||
private DesktopWindowXamlSource? _xamlSource;
|
||||
private App? _xamlApplication;
|
||||
private nint _islandWindow;
|
||||
private int _generation;
|
||||
private WinUIState _state = WinUIState.ShutdownComplete;
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
Text = "WinUI C# Islands shutdown and restart sample";
|
||||
Width = 1000;
|
||||
Height = 700;
|
||||
MinimumSize = new System.Drawing.Size(700, 500);
|
||||
StartPosition = Forms.FormStartPosition.CenterScreen;
|
||||
|
||||
_startButton = new Forms.Button
|
||||
{
|
||||
AutoSize = true,
|
||||
Text = "Start WinUI",
|
||||
};
|
||||
_startButton.Click += (_, _) => StartWinUI();
|
||||
|
||||
_shutdownButton = new Forms.Button
|
||||
{
|
||||
AutoSize = true,
|
||||
Text = "Shut down WinUI",
|
||||
};
|
||||
_shutdownButton.Click += (_, _) => ShutdownWinUI();
|
||||
|
||||
_restartButton = new Forms.Button
|
||||
{
|
||||
AutoSize = true,
|
||||
Text = "Restart WinUI",
|
||||
};
|
||||
_restartButton.Click += (_, _) =>
|
||||
{
|
||||
if (ShutdownWinUI())
|
||||
{
|
||||
StartWinUI();
|
||||
}
|
||||
};
|
||||
|
||||
_statusLabel = new Forms.Label
|
||||
{
|
||||
AutoSize = true,
|
||||
Padding = new Forms.Padding(12, 7, 0, 0),
|
||||
};
|
||||
|
||||
_objectCountsLabel = new Forms.Label
|
||||
{
|
||||
AutoSize = true,
|
||||
Padding = new Forms.Padding(4, 4, 0, 0),
|
||||
};
|
||||
|
||||
Forms.FlowLayoutPanel commandPanel = new()
|
||||
{
|
||||
AutoSize = true,
|
||||
Dock = Forms.DockStyle.Top,
|
||||
FlowDirection = Forms.FlowDirection.LeftToRight,
|
||||
Padding = new Forms.Padding(8),
|
||||
};
|
||||
commandPanel.Controls.AddRange(
|
||||
[
|
||||
_startButton,
|
||||
_shutdownButton,
|
||||
_restartButton,
|
||||
_statusLabel,
|
||||
_objectCountsLabel,
|
||||
]);
|
||||
commandPanel.SetFlowBreak(_statusLabel, true);
|
||||
|
||||
_xamlHostPanel = new Forms.Panel
|
||||
{
|
||||
BackColor = System.Drawing.Color.DimGray,
|
||||
Dock = Forms.DockStyle.Fill,
|
||||
Margin = new Forms.Padding(0),
|
||||
TabStop = true,
|
||||
};
|
||||
_xamlHostPanel.Resize += (_, _) => ResizeIsland();
|
||||
|
||||
_eventLog = new Forms.TextBox
|
||||
{
|
||||
Dock = Forms.DockStyle.Fill,
|
||||
Font = new System.Drawing.Font("Consolas", 9),
|
||||
Multiline = true,
|
||||
ReadOnly = true,
|
||||
ScrollBars = Forms.ScrollBars.Vertical,
|
||||
};
|
||||
|
||||
_contentSplitContainer = new Forms.SplitContainer
|
||||
{
|
||||
Dock = Forms.DockStyle.Fill,
|
||||
FixedPanel = Forms.FixedPanel.Panel2,
|
||||
SplitterWidth = 6,
|
||||
};
|
||||
_contentSplitContainer.Panel1.Controls.Add(_xamlHostPanel);
|
||||
_contentSplitContainer.Panel2.Controls.Add(_eventLog);
|
||||
|
||||
Controls.Add(_contentSplitContainer);
|
||||
Controls.Add(commandPanel);
|
||||
|
||||
UpdateState(WinUIState.ShutdownComplete);
|
||||
}
|
||||
|
||||
protected override void OnLoad(EventArgs args)
|
||||
{
|
||||
base.OnLoad(args);
|
||||
AppendLog("Native WinForms host started. WinUI is not initialized.");
|
||||
}
|
||||
|
||||
protected override void OnShown(EventArgs args)
|
||||
{
|
||||
base.OnShown(args);
|
||||
SetInitialEventLogWidth();
|
||||
}
|
||||
|
||||
protected override void OnFormClosing(Forms.FormClosingEventArgs args)
|
||||
{
|
||||
if (_dispatcherQueueController is not null && !ShutdownWinUI())
|
||||
{
|
||||
args.Cancel = true;
|
||||
}
|
||||
|
||||
base.OnFormClosing(args);
|
||||
}
|
||||
|
||||
private void StartWinUI()
|
||||
{
|
||||
if (_dispatcherQueueController is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AppendLog("Creating DispatcherQueueController.");
|
||||
_dispatcherQueueController = DispatcherQueueController.CreateOnCurrentThread();
|
||||
_dispatcherQueueController.DispatcherQueue.ShutdownStarting +=
|
||||
(_, _) => AppendLog("DispatcherQueue.ShutdownStarting");
|
||||
_dispatcherQueueController.DispatcherQueue.ShutdownCompleted +=
|
||||
(_, _) => AppendLog("DispatcherQueue.ShutdownCompleted");
|
||||
|
||||
AppendLog("Creating managed WinUI Application and WindowsXamlManager.");
|
||||
_xamlApplication = new App();
|
||||
_xamlApplication.InitialWindowsXamlManager!.XamlShutdownCompletedOnThread +=
|
||||
(_, _) => AppendLog("XamlShutdownCompletedOnThread");
|
||||
|
||||
_xamlHostPanel.CreateControl();
|
||||
_xamlSource = new DesktopWindowXamlSource();
|
||||
_xamlSource.Initialize(Win32Interop.GetWindowIdFromWindow(_xamlHostPanel.Handle));
|
||||
_islandWindow = Win32Interop.GetWindowFromWindowId(_xamlSource.SiteBridge.WindowId);
|
||||
AppendLog("DesktopWindowXamlSource initialized.");
|
||||
|
||||
_generation++;
|
||||
WinUIContent content = new();
|
||||
content.SetGeneration(_generation);
|
||||
_xamlSource.Content = content;
|
||||
ResizeIsland();
|
||||
|
||||
UpdateState(WinUIState.Running);
|
||||
AppendLog($"WinUI generation {_generation} started.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
AppendLog($"WinUI startup failed: {exception}");
|
||||
CleanupAfterFailedStartup();
|
||||
ShowError($"Unable to start WinUI: 0x{exception.HResult:X8}\n{exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShutdownWinUI()
|
||||
{
|
||||
if (_dispatcherQueueController is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AppendLog("Releasing hosted XAML content.");
|
||||
if (_xamlSource is not null)
|
||||
{
|
||||
_xamlSource.Content = null;
|
||||
_xamlSource.Dispose();
|
||||
_xamlSource = null;
|
||||
_islandWindow = 0;
|
||||
}
|
||||
|
||||
_xamlApplication?.ReleaseWindowsXamlManager();
|
||||
_xamlApplication = null;
|
||||
|
||||
UpdateState(WinUIState.ShuttingDown);
|
||||
AppendLog("Shutting down DispatcherQueue.");
|
||||
_dispatcherQueueController.ShutdownQueue();
|
||||
_dispatcherQueueController = null;
|
||||
CollectManagedXamlObjects();
|
||||
UpdateState(WinUIState.ShutdownComplete);
|
||||
AppendLog("WinUI shutdown completed.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
AppendLog($"WinUI shutdown failed: {exception}");
|
||||
ShowError($"Unable to shut down WinUI: 0x{exception.HResult:X8}\n{exception.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupAfterFailedStartup()
|
||||
{
|
||||
if (_xamlSource is not null)
|
||||
{
|
||||
_xamlSource.Content = null;
|
||||
_xamlSource.Dispose();
|
||||
_xamlSource = null;
|
||||
_islandWindow = 0;
|
||||
}
|
||||
|
||||
_xamlApplication?.ReleaseWindowsXamlManager();
|
||||
_xamlApplication = null;
|
||||
|
||||
if (_dispatcherQueueController is not null)
|
||||
{
|
||||
_dispatcherQueueController.ShutdownQueue();
|
||||
_dispatcherQueueController = null;
|
||||
}
|
||||
|
||||
CollectManagedXamlObjects();
|
||||
UpdateState(WinUIState.ShutdownComplete);
|
||||
}
|
||||
|
||||
private void UpdateState(WinUIState state)
|
||||
{
|
||||
_state = state;
|
||||
_statusLabel.Text = $"WinUI: {GetStateText(state)} | Generation: {_generation}";
|
||||
_startButton.Enabled = state == WinUIState.ShutdownComplete;
|
||||
_shutdownButton.Enabled = state == WinUIState.Running;
|
||||
_restartButton.Enabled = state == WinUIState.Running;
|
||||
UpdateObjectCounts();
|
||||
}
|
||||
|
||||
private void UpdateObjectCounts()
|
||||
{
|
||||
int dispatcherQueueCount = _dispatcherQueueController is null ? 0 : 1;
|
||||
int applicationCount = _xamlApplication is null ? 0 : 1;
|
||||
int windowsXamlManagerCount =
|
||||
_xamlApplication?.InitialWindowsXamlManager is null ? 0 : 1;
|
||||
int desktopWindowXamlSourceCount = _xamlSource is null ? 0 : 1;
|
||||
|
||||
_objectCountsLabel.Text =
|
||||
$"Object counts | DispatcherQueue: {dispatcherQueueCount} | " +
|
||||
$"Application: {applicationCount} | " +
|
||||
$"WindowsXamlManager: {windowsXamlManagerCount} | " +
|
||||
$"DesktopWindowXamlSource: {desktopWindowXamlSourceCount}";
|
||||
}
|
||||
|
||||
private static string GetStateText(WinUIState state)
|
||||
{
|
||||
return state switch
|
||||
{
|
||||
WinUIState.Running => "Running",
|
||||
WinUIState.ShuttingDown => "Shutting down",
|
||||
WinUIState.ShutdownComplete => "Shutdown complete",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(state)),
|
||||
};
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
string entry = $"{DateTime.Now:HH:mm:ss.fff} {message}";
|
||||
Debug.WriteLine(entry);
|
||||
_eventLog.AppendText(entry + Environment.NewLine);
|
||||
}
|
||||
|
||||
private void CollectManagedXamlObjects()
|
||||
{
|
||||
AppendLog("Collecting managed XAML wrappers.");
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void SetInitialEventLogWidth()
|
||||
{
|
||||
const int xamlPanelMinimumWidth = 350;
|
||||
const int eventLogMinimumWidth = 300;
|
||||
const int eventLogWidth = 360;
|
||||
int availableWidth =
|
||||
_contentSplitContainer.ClientSize.Width - _contentSplitContainer.SplitterWidth;
|
||||
if (availableWidth < xamlPanelMinimumWidth + eventLogMinimumWidth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int splitterDistance = availableWidth - eventLogWidth;
|
||||
|
||||
_contentSplitContainer.SplitterDistance =
|
||||
Math.Clamp(
|
||||
splitterDistance,
|
||||
xamlPanelMinimumWidth,
|
||||
availableWidth - eventLogMinimumWidth);
|
||||
_contentSplitContainer.Panel1MinSize = xamlPanelMinimumWidth;
|
||||
_contentSplitContainer.Panel2MinSize = eventLogMinimumWidth;
|
||||
}
|
||||
|
||||
private void ResizeIsland()
|
||||
{
|
||||
if (_islandWindow != 0)
|
||||
{
|
||||
if (!SetWindowPos(
|
||||
_islandWindow,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
_xamlHostPanel.ClientSize.Width,
|
||||
_xamlHostPanel.ClientSize.Height,
|
||||
SetWindowPosFlags.NoZOrder | SetWindowPosFlags.ShowWindow))
|
||||
{
|
||||
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowError(string message)
|
||||
{
|
||||
Forms.MessageBox.Show(
|
||||
this,
|
||||
message,
|
||||
Text,
|
||||
Forms.MessageBoxButtons.OK,
|
||||
Forms.MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum SetWindowPosFlags : uint
|
||||
{
|
||||
NoZOrder = 0x0004,
|
||||
ShowWindow = 0x0040,
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool SetWindowPos(
|
||||
nint window,
|
||||
nint insertAfter,
|
||||
int x,
|
||||
int y,
|
||||
int width,
|
||||
int height,
|
||||
SetWindowPosFlags flags);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
namespace WinUICsIslandsSampleApp;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
[STAThread]
|
||||
private static void Main()
|
||||
{
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
System.Windows.Forms.Application.EnableVisualStyles();
|
||||
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
|
||||
System.Windows.Forms.Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<UserControl
|
||||
x:Class="WinUICsIslandsSampleApp.WinUIContent"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls">
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<StackPanel
|
||||
Width="420"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<TextBlock
|
||||
x:Name="GenerationText"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{StaticResource TitleTextBlockStyle}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="This content is compiled C# XAML hosted by DesktopWindowXamlSource." />
|
||||
<NumberBox
|
||||
x:Name="ValueNumberBox"
|
||||
Header="MUXC NumberBox"
|
||||
Value="1" />
|
||||
<Button
|
||||
HorizontalAlignment="Center"
|
||||
Click="IncrementButton_Click"
|
||||
Content="Increment value" />
|
||||
<toolkit:SettingsCard
|
||||
Description="This managed Toolkit control is recreated with each WinUI generation."
|
||||
Header="Toolkit sample option">
|
||||
<ToggleSwitch
|
||||
AutomationProperties.Name="Toolkit sample toggle" />
|
||||
</toolkit:SettingsCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace WinUICsIslandsSampleApp;
|
||||
|
||||
public sealed partial class WinUIContent : Microsoft.UI.Xaml.Controls.UserControl
|
||||
{
|
||||
public WinUIContent()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void SetGeneration(int generation)
|
||||
{
|
||||
GenerationText.Text = $"WinUI generation {generation}";
|
||||
}
|
||||
|
||||
private void IncrementButton_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
ValueNumberBox.Value++;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.0.1" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>$(WindowsAppSdkTargetPlatformVersion)</TargetPlatformMinVersion>
|
||||
<RootNamespace>WinUICsIslandsSampleApp</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>
|
||||
<Configurations>Debug;Release;Debug_test</Configurations>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>NU1605</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Reference WinForms without setting UseWindowsForms, which would import the WPF XAML build targets. -->
|
||||
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WindowsForms" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
||||
<PackageReference Include="Microsoft.Windows.CSWinRT" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK.WinUI" />
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) Microsoft Corporation. Licensed under the MIT License. -->
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="WinUICsIslandsSampleApp.app" />
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<maxversiontested Id="10.0.18362.0" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.Windows.Common-Controls"
|
||||
version="6.0.0.0"
|
||||
processorArchitecture="*"
|
||||
publicKeyToken="6595b64144ccf1df"
|
||||
language="*"
|
||||
/>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
@@ -34,6 +34,8 @@ call :buildSamplesSolution %reporoot%\Samples\WinUICppIsland2SampleApp\WinUICppI
|
||||
if ERRORLEVEL 1 goto:eof
|
||||
call :buildSamplesSolution %reporoot%\Samples\WinUICppIslandsSampleApp\WinUICppIslandsSampleApp.sln
|
||||
if ERRORLEVEL 1 goto:eof
|
||||
call :buildSamplesSolution %reporoot%\Samples\WinUICsIslandsSampleApp\WinUICsIslandsSampleApp.sln /m
|
||||
if ERRORLEVEL 1 goto:eof
|
||||
call :buildSamplesSolution %reporoot%\Samples\DisableXamlGeneratedMain\DisableXamlGeneratedMain.sln
|
||||
if ERRORLEVEL 1 goto:eof
|
||||
REM The C# projects in the solution above are single-project MSIX apps and so only emit their MSIX
|
||||
|
||||
@@ -828,15 +828,14 @@ namespace Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests
|
||||
|
||||
Log.Comment($"Validate that shutdown log $logFilePath contains expected order of events.");
|
||||
|
||||
// NOTE: the dtors listed below are not officially part of the contract, but they can be important because
|
||||
// Cpp apps may rely on the timing of when they're called (intentionally or not).
|
||||
// XAML shutdown releases Page1. App then releases MainWindow from the first
|
||||
// XamlShutdownCompletedOnThread handler. Retained dynamic metadata keeps App alive.
|
||||
string expectedShutdownLog =
|
||||
@" Window.Closed raised.
|
||||
ShutdownStarting raised.
|
||||
DispatcherQueue work is running.
|
||||
Page1 Unloaded event raised.
|
||||
Page1::~Page1 called.
|
||||
App::~App called.
|
||||
MainWindow::~MainWindow called.
|
||||
XamlShutdownCompletedOnThread raised.
|
||||
ShutdownCompleted raised.";
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
using Common;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
using Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests.Common;
|
||||
using Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests.Infra;
|
||||
using Microsoft.Windows.Apps.Test.Foundation;
|
||||
using Microsoft.Windows.Apps.Test.Foundation.Controls;
|
||||
using Microsoft.Windows.Apps.Test.Foundation.Waiters;
|
||||
using WEX.TestExecution;
|
||||
using WEX.TestExecution.Markup;
|
||||
|
||||
namespace Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests
|
||||
{
|
||||
[TestClass]
|
||||
public class WinUICppIslandsSampleAppTests
|
||||
{
|
||||
private static Process s_process;
|
||||
private static UIObject s_root;
|
||||
|
||||
public static TestApplicationInfo WinUICppIslandsSampleApp
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TestApplicationInfo(
|
||||
"WinUICppIslandsSampleApp",
|
||||
"WinUICppIslandsSampleApp_6f07fta6qpts2!App",
|
||||
"WinUICppIslandsSampleApp_6f07fta6qpts2",
|
||||
"IslandSampleWinUI3",
|
||||
"WinUICppIslandsSampleApp.exe",
|
||||
"WinUICppIslandsSampleApp",
|
||||
isUwpApp: false,
|
||||
TestApplicationInfo.MUXCertSerialNumber,
|
||||
TestApplicationInfo.MUXBaseAppxDir);
|
||||
}
|
||||
}
|
||||
|
||||
[ClassInitialize]
|
||||
[TestProperty("RunAs", "User")]
|
||||
[TestProperty("Classification", "ScenarioTestSuite")]
|
||||
[TestProperty("IgnoreForValidateWindowsAppSDK", "True")]
|
||||
[TestProperty("Platform", "Any")]
|
||||
[TestProperty("IsolationLevel", "Method")]
|
||||
public static void ClassInitialize(TestContext testContext)
|
||||
{
|
||||
TestApplicationInfo testAppInfo = WinUICppIslandsSampleApp;
|
||||
TestAppInstallHelper.EnableSideloadingApps();
|
||||
Verify.IsTrue(TestAppInstallHelper.InstallTestAppFromPackageIfNeeded(
|
||||
testContext.TestDeploymentDir,
|
||||
testAppInfo.TestAppPackageName,
|
||||
testAppInfo.TestAppPackageFamilyName,
|
||||
testAppInfo.InstallerName,
|
||||
testAppInfo.ProcessName));
|
||||
}
|
||||
|
||||
[ClassCleanup]
|
||||
public static void ClassCleanup()
|
||||
{
|
||||
if (s_process != null)
|
||||
{
|
||||
if (!s_process.HasExited)
|
||||
{
|
||||
s_process.Kill(entireProcessTree: true);
|
||||
s_process.WaitForExit();
|
||||
}
|
||||
|
||||
s_process.Dispose();
|
||||
s_process = null;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Description("Verify MUXC metadata remains usable after process-wide WinUI shutdown and restart.")]
|
||||
public void MuxcMetadataAfterWinUIRestart()
|
||||
{
|
||||
var windowCondition = UICondition.CreateFromName(
|
||||
WinUICppIslandsSampleApp.TestAppMainWindowTitle);
|
||||
using (var launchWaiter = new AppLaunchWaiter(windowCondition))
|
||||
{
|
||||
uint processId = Application.ActivateApplication(
|
||||
WinUICppIslandsSampleApp.TestAppName,
|
||||
null);
|
||||
s_process = Process.GetProcessById((int)processId);
|
||||
launchWaiter.TryWait(TimeSpan.FromSeconds(20));
|
||||
s_root = launchWaiter.Source;
|
||||
}
|
||||
|
||||
Verify.IsNotNull(s_root, "Sample app window");
|
||||
if (s_root == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeButton("Create DesktopWindowXamlSource");
|
||||
WaitForElement(s_root, "Inline CommandBarFlyout", expectedToExist: true);
|
||||
|
||||
InvokeMoreMenuItem("Shut down WinUI (public APIs)");
|
||||
WaitForElement(s_root, "Inline CommandBarFlyout", expectedToExist: false);
|
||||
|
||||
InvokeMoreMenuItem("Start WinUI");
|
||||
|
||||
InvokeButton("Create DesktopWindowXamlSource");
|
||||
WaitForElement(s_root, "Inline CommandBarFlyout", expectedToExist: true);
|
||||
}
|
||||
|
||||
private static void InvokeButton(string name)
|
||||
{
|
||||
UIObject buttonObject = FindElement.GetDescendantByName(s_root, name);
|
||||
Verify.IsNotNull(buttonObject, name);
|
||||
if (buttonObject == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find button '{name}'.");
|
||||
}
|
||||
|
||||
new Button(buttonObject).Invoke();
|
||||
}
|
||||
|
||||
private static void InvokeMoreMenuItem(string name)
|
||||
{
|
||||
UIObject moreButton = FindElement.GetDescendantByName(s_root, "More...");
|
||||
Verify.IsNotNull(moreButton, "More...");
|
||||
if (moreButton == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not find the 'More...' button.");
|
||||
}
|
||||
|
||||
InputHelper.LeftClick(moreButton);
|
||||
|
||||
UIObject menuItem = WaitForElement(UIObject.Root, name, expectedToExist: true);
|
||||
Verify.IsNotNull(menuItem, name);
|
||||
if (menuItem == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not find menu item '{name}'.");
|
||||
}
|
||||
|
||||
InputHelper.LeftClick(menuItem);
|
||||
}
|
||||
|
||||
private static UIObject WaitForElement(UIObject root, string name, bool expectedToExist)
|
||||
{
|
||||
// TestEnvironment.WaitUntilElementLoadedByName uses ElementCache, which requires
|
||||
// TestEnvironment.Application. This plain Win32 sample is launched directly instead.
|
||||
var condition = UICondition.CreateFromName(name);
|
||||
for (int attempt = 0; attempt < 50; attempt++)
|
||||
{
|
||||
root.Descendants.TryFind(condition, out UIObject element);
|
||||
if ((element != null) == expectedToExist)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
Wait.ForMilliseconds(100);
|
||||
}
|
||||
|
||||
throw new WaiterTimedOutException(
|
||||
expectedToExist
|
||||
? $"Expected to find '{name}'."
|
||||
: $"Expected '{name}' to be removed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,18 +437,7 @@ namespace Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests.Infra
|
||||
// Calling CoCreateInstance with CLSCTX_LOCAL_SERVER gets us an instance that will not launch an elevated app.
|
||||
// This is essentially what UAPApp.Launch does, except for this step - using UAPApp.Launch here would result in
|
||||
// the app being elevated.
|
||||
if (NativeMethods.CoCreateInstance(
|
||||
NativeMethods.CLSID_ApplicationActivationManager,
|
||||
IntPtr.Zero,
|
||||
NativeMethods.CLSCTX.CLSCTX_LOCAL_SERVER,
|
||||
NativeMethods.CLSID_IApplicationActivationManager,
|
||||
out object applicationActivationManagerAsObject) != 0)
|
||||
{
|
||||
throw new Exception("Failed to create ApplicationActivationManager!");
|
||||
}
|
||||
|
||||
var applicationActivationManager = (NativeMethods.IApplicationActivationManager)applicationActivationManagerAsObject;
|
||||
applicationActivationManager.ActivateApplication(_appName, null, NativeMethods.ActivateOptions.None, out uint processId);
|
||||
ActivateApplication(_appName, null, NativeMethods.ActivateOptions.None);
|
||||
|
||||
waiter.Wait();
|
||||
window = waiter.Source;
|
||||
@@ -458,6 +447,40 @@ namespace Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests.Infra
|
||||
return window;
|
||||
}
|
||||
|
||||
public static uint ActivateApplication(string appUserModelId, string arguments)
|
||||
{
|
||||
return ActivateApplication(
|
||||
appUserModelId,
|
||||
arguments,
|
||||
NativeMethods.ActivateOptions.NoErrorUI | NativeMethods.ActivateOptions.NoSplashScreen);
|
||||
}
|
||||
|
||||
private static uint ActivateApplication(
|
||||
string appUserModelId,
|
||||
string arguments,
|
||||
NativeMethods.ActivateOptions options)
|
||||
{
|
||||
if (NativeMethods.CoCreateInstance(
|
||||
NativeMethods.CLSID_ApplicationActivationManager,
|
||||
IntPtr.Zero,
|
||||
NativeMethods.CLSCTX.CLSCTX_LOCAL_SERVER,
|
||||
NativeMethods.CLSID_IApplicationActivationManager,
|
||||
out object applicationActivationManagerAsObject) != 0)
|
||||
{
|
||||
throw new Exception("Failed to create ApplicationActivationManager!");
|
||||
}
|
||||
|
||||
var applicationActivationManager =
|
||||
(NativeMethods.IApplicationActivationManager)applicationActivationManagerAsObject;
|
||||
int result = applicationActivationManager.ActivateApplication(
|
||||
appUserModelId,
|
||||
arguments,
|
||||
options,
|
||||
out uint processId);
|
||||
Marshal.ThrowExceptionForHR(result);
|
||||
return processId;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
int appWindowsProcessId = GetProcessIdFromAppWindow();
|
||||
@@ -772,7 +795,8 @@ namespace Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests.Infra
|
||||
{
|
||||
// Activates the specified immersive application for the "Launch" contract, passing the provided arguments
|
||||
// string into the application. Callers can obtain the process Id of the application instance fulfilling this contract.
|
||||
IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);
|
||||
[PreserveSig]
|
||||
int ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);
|
||||
IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);
|
||||
IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ namespace Microsoft.UI.Xaml.Tests.MUXControls.InteractionTests.Infra
|
||||
// Note - Make sure that this list stays in sync with test\scripts\runtests.ps1 and
|
||||
// Helix\scripts\TestPass-OneTimeMachineSetup.ps1
|
||||
else if ((installedPackage.Id.Name == "WinUICppDesktopSampleApp") ||
|
||||
(installedPackage.Id.Name == "WinUICppIslandsSampleApp") ||
|
||||
(installedPackage.Id.Name == "WinUICsDesktopSampleApp") ||
|
||||
(installedPackage.Id.Name == "DisableXamlGeneratedMainCs") ||
|
||||
(installedPackage.Id.Name == "DisableXamlGeneratedMainNoCtorCs") ||
|
||||
|
||||
@@ -442,7 +442,15 @@ void XamlIslandTests::WindowsXamlManagerKeptAlive()
|
||||
IUnknown* rawUnk3 {reinterpret_cast<IUnknown*>(wxm3)};
|
||||
VERIFY_ARE_EQUAL(rawUnk1, rawUnk3);
|
||||
|
||||
Platform::WeakReference weakWxm(wxm3);
|
||||
wxm2 = nullptr;
|
||||
wxm3 = nullptr;
|
||||
|
||||
VERIFY_IS_NOT_NULL(weakWxm.Resolve<WindowsXamlManager>());
|
||||
|
||||
dqc->ShutdownQueue();
|
||||
|
||||
VERIFY_IS_NULL(weakWxm.Resolve<WindowsXamlManager>());
|
||||
});
|
||||
|
||||
WaitForSingleObjectWithTimeout(uiThread);
|
||||
@@ -868,7 +876,9 @@ void XamlIslandTests::IslandStressWorker(IslandSceneKind sceneKind)
|
||||
app = nullptr;
|
||||
dqcOuter->ShutdownQueue();
|
||||
|
||||
VERIFY_IS_TRUE(XamlIslandTests_ApplicationWithMuxc::DidDestructorRun());
|
||||
// Third-party components may have registered custom DPs that need to stay valid in order to restart WinUI after
|
||||
// shutdown, so MUX no longer cleans up metadata after shutdown. That means the Application object stays alive
|
||||
// for the duration of the process.
|
||||
}
|
||||
|
||||
void XamlIslandTests::IslandsOnDifferentThreadsStress()
|
||||
@@ -3801,6 +3811,207 @@ void XamlIslandTests::ValidateDispatcherShutdownModeInDesktopApp()
|
||||
WaitForSingleObjectWithTimeout(uiThread);
|
||||
}
|
||||
|
||||
struct ApplicationBehaviorState
|
||||
{
|
||||
Application^ application = nullptr;
|
||||
ResourceDictionary^ resources = nullptr;
|
||||
DebugSettings^ debugSettings = nullptr;
|
||||
FocusVisualKind focusVisualKind{};
|
||||
ApplicationHighContrastAdjustment highContrastAdjustment{};
|
||||
ApplicationTheme requestedTheme{};
|
||||
bool enableFrameRateCounter{};
|
||||
bool failFastOnErrors{};
|
||||
};
|
||||
|
||||
void VerifyApplicationBehaviorAtCoreShutdown(const ApplicationBehaviorState& state)
|
||||
{
|
||||
VERIFY_IS_NULL(Application::Current);
|
||||
|
||||
// Calling Exit when WinUI is already shut down is a no-op
|
||||
state.application->Exit();
|
||||
|
||||
//
|
||||
// WinUI's Application object is a combination of things that are process-wide, independent of any UI
|
||||
// thread (like Xaml metadata, HighContrastAdjustment getter, FocusVisualKind, DebugSettings object,
|
||||
// and FailFastOnErrors), and things that are tied to a UI thread (everything else). Confirm that the
|
||||
// parts independent of the UI thread still work after shutdown, and the UI-thread-dependent stuff
|
||||
// returns RPC_E_WRONG_THREAD.
|
||||
//
|
||||
|
||||
VERIFY_ARE_EQUAL(state.highContrastAdjustment, state.application->HighContrastAdjustment);
|
||||
VERIFY_ARE_EQUAL(state.focusVisualKind, state.application->FocusVisualKind);
|
||||
VERIFY_IS_TRUE(state.debugSettings == state.application->DebugSettings);
|
||||
VERIFY_ARE_EQUAL(state.failFastOnErrors, state.debugSettings->FailFastOnErrors);
|
||||
|
||||
auto appXamlUri = ref new Uri(L"ms-appx:///App.xaml");
|
||||
VERIFY_THROWS_SPECIFIC_WINRT(
|
||||
Application::LoadComponent(state.application, appXamlUri),
|
||||
Platform::Exception^,
|
||||
[](Platform::Exception^ exception) { return exception->HResult == RPC_E_WRONG_THREAD; });
|
||||
|
||||
VERIFY_THROWS_SPECIFIC_WINRT(
|
||||
static_cast<void>(state.application->Resources),
|
||||
Platform::Exception^,
|
||||
[](Platform::Exception^ exception) { return exception->HResult == RPC_E_WRONG_THREAD; });
|
||||
VERIFY_THROWS_SPECIFIC_WINRT(
|
||||
static_cast<void>(state.application->DispatcherShutdownMode),
|
||||
Platform::Exception^,
|
||||
[](Platform::Exception^ exception) { return exception->HResult == RPC_E_WRONG_THREAD; });
|
||||
VERIFY_THROWS_SPECIFIC_WINRT(
|
||||
static_cast<void>(state.application->RequestedTheme),
|
||||
Platform::Exception^,
|
||||
[](Platform::Exception^ exception) { return exception->HResult == RPC_E_WRONG_THREAD; });
|
||||
VERIFY_THROWS_SPECIFIC_WINRT(
|
||||
state.application->RequestedTheme = state.requestedTheme,
|
||||
Platform::Exception^,
|
||||
[](Platform::Exception^ exception) { return exception->HResult == RPC_E_WRONG_THREAD; });
|
||||
|
||||
const auto newHighContrastAdjustment =
|
||||
state.highContrastAdjustment == ApplicationHighContrastAdjustment::Auto
|
||||
? ApplicationHighContrastAdjustment::None
|
||||
: ApplicationHighContrastAdjustment::Auto;
|
||||
VERIFY_THROWS_SPECIFIC_WINRT(
|
||||
state.application->HighContrastAdjustment = newHighContrastAdjustment,
|
||||
Platform::Exception^,
|
||||
[](Platform::Exception^ exception) { return exception->HResult == RPC_E_WRONG_THREAD; });
|
||||
|
||||
VERIFY_THROWS_SPECIFIC_WINRT(
|
||||
static_cast<void>(state.debugSettings->EnableFrameRateCounter),
|
||||
Platform::Exception^,
|
||||
[](Platform::Exception^ exception) { return exception->HResult == RPC_E_WRONG_THREAD; });
|
||||
}
|
||||
|
||||
void VerifyApplicationBehaviorAfterStartup(ApplicationBehaviorState& state)
|
||||
{
|
||||
auto restartedApplication = Application::Current;
|
||||
VERIFY_IS_NOT_NULL(restartedApplication);
|
||||
if (restartedApplication == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VERIFY_IS_TRUE(state.application == restartedApplication);
|
||||
|
||||
auto restartedResources = restartedApplication->Resources;
|
||||
VERIFY_IS_NOT_NULL(restartedResources);
|
||||
VERIFY_IS_TRUE(state.resources != restartedResources);
|
||||
state.resources = restartedResources;
|
||||
|
||||
VERIFY_IS_TRUE(state.debugSettings == restartedApplication->DebugSettings);
|
||||
|
||||
state.enableFrameRateCounter = !state.enableFrameRateCounter;
|
||||
state.debugSettings->EnableFrameRateCounter = state.enableFrameRateCounter;
|
||||
VERIFY_ARE_EQUAL(state.enableFrameRateCounter, state.debugSettings->EnableFrameRateCounter);
|
||||
}
|
||||
|
||||
void XamlIslandTests::ApplicationBehaviorAtCoreShutdown()
|
||||
{
|
||||
auto uiThread = RunOnNewThread([]()
|
||||
{
|
||||
VERIFY_SUCCEEDED(::RoInitialize(RO_INIT_SINGLETHREADED));
|
||||
|
||||
ApplicationBehaviorState state;
|
||||
|
||||
VERIFY_IS_NULL(Application::Current);
|
||||
|
||||
Application::Start(ref new ApplicationInitializationCallback(
|
||||
[&](ApplicationInitializationCallbackParams^)
|
||||
{
|
||||
state.application = ref new Application();
|
||||
|
||||
auto dispatcherQueue = DispatcherQueue::GetForCurrentThread();
|
||||
VERIFY_IS_NOT_NULL(dispatcherQueue);
|
||||
if (dispatcherQueue == nullptr)
|
||||
{
|
||||
::PostQuitMessage(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool queued = dispatcherQueue->TryEnqueue(ref new DispatcherQueueHandler([&]
|
||||
{
|
||||
auto currentApplication = Application::Current;
|
||||
VERIFY_IS_NOT_NULL(currentApplication);
|
||||
if (currentApplication == nullptr)
|
||||
{
|
||||
::PostQuitMessage(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue teardown after asserts
|
||||
currentApplication->Exit();
|
||||
VERIFY_IS_TRUE(state.application == currentApplication);
|
||||
|
||||
state.resources = state.application->Resources;
|
||||
VERIFY_IS_NOT_NULL(state.resources);
|
||||
state.debugSettings = state.application->DebugSettings;
|
||||
VERIFY_IS_NOT_NULL(state.debugSettings);
|
||||
|
||||
static_cast<void>(state.application->DispatcherShutdownMode);
|
||||
state.focusVisualKind = state.application->FocusVisualKind;
|
||||
state.highContrastAdjustment = state.application->HighContrastAdjustment;
|
||||
state.requestedTheme = state.application->RequestedTheme;
|
||||
|
||||
state.enableFrameRateCounter = state.debugSettings->EnableFrameRateCounter;
|
||||
state.failFastOnErrors = state.debugSettings->FailFastOnErrors;
|
||||
}));
|
||||
VERIFY_IS_TRUE(queued);
|
||||
if (!queued)
|
||||
{
|
||||
::PostQuitMessage(0);
|
||||
}
|
||||
}));
|
||||
|
||||
VerifyApplicationBehaviorAtCoreShutdown(state);
|
||||
|
||||
Application::Start(ref new ApplicationInitializationCallback(
|
||||
[&](ApplicationInitializationCallbackParams^)
|
||||
{
|
||||
auto dispatcherQueue = DispatcherQueue::GetForCurrentThread();
|
||||
VERIFY_IS_NOT_NULL(dispatcherQueue);
|
||||
if (dispatcherQueue == nullptr)
|
||||
{
|
||||
::PostQuitMessage(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool queued = dispatcherQueue->TryEnqueue(ref new DispatcherQueueHandler([&]
|
||||
{
|
||||
auto restartedApplication = Application::Current;
|
||||
VERIFY_IS_NOT_NULL(restartedApplication);
|
||||
if (restartedApplication == nullptr)
|
||||
{
|
||||
::PostQuitMessage(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue teardown after asserts
|
||||
restartedApplication->Exit();
|
||||
|
||||
VerifyApplicationBehaviorAfterStartup(state);
|
||||
}));
|
||||
VERIFY_IS_TRUE(queued);
|
||||
if (!queued)
|
||||
{
|
||||
::PostQuitMessage(0);
|
||||
}
|
||||
}));
|
||||
|
||||
VerifyApplicationBehaviorAtCoreShutdown(state);
|
||||
|
||||
auto dispatcherQueueController = DispatcherQueueController::CreateOnCurrentThread();
|
||||
auto windowsXamlManager = WindowsXamlManager::InitializeForCurrentThread();
|
||||
VERIFY_IS_NOT_NULL(windowsXamlManager);
|
||||
|
||||
VerifyApplicationBehaviorAfterStartup(state);
|
||||
|
||||
dispatcherQueueController->ShutdownQueue();
|
||||
VerifyApplicationBehaviorAtCoreShutdown(state);
|
||||
|
||||
CloseObject(windowsXamlManager);
|
||||
});
|
||||
WaitForSingleObjectWithTimeout(uiThread);
|
||||
}
|
||||
|
||||
void XamlIslandTests::ValidateCallbackErrorPropagatesInDesktopApp()
|
||||
{
|
||||
// Regression test for Bug 39852717: When the app's ApplicationInitializationCallback
|
||||
@@ -3984,7 +4195,7 @@ void XamlIslandTests::ValidateUiaFindAllWithWindowedPopup()
|
||||
::Sleep(500);
|
||||
|
||||
LowBudgetWaitForIdle(ih1);
|
||||
|
||||
|
||||
RunOnIslandUIThread(ih1, [&]()
|
||||
{
|
||||
popup->IsOpen = true;
|
||||
@@ -4026,10 +4237,10 @@ void XamlIslandTests::ValidateUiaFindAllWithWindowedPopup()
|
||||
};
|
||||
|
||||
visitElement(L"Root", windowElement.Get());
|
||||
|
||||
|
||||
wrl::ComPtr<IUIAutomationCondition> trueCondition;
|
||||
LogThrow_IfFailed(automation->CreateTrueCondition(&trueCondition));
|
||||
|
||||
|
||||
wrl::ComPtr<IUIAutomationElementArray> children;
|
||||
LogThrow_IfFailed(windowElement->FindAll(TreeScope_Descendants, trueCondition.Get(), &children));
|
||||
|
||||
@@ -4066,7 +4277,7 @@ void XamlIslandTests::ValidateNavigationView()
|
||||
XamlIslandTestHelper testHelper(this);
|
||||
testHelper.StartAppOnCurrentThread();
|
||||
|
||||
IUnknown* unk {nullptr};
|
||||
IUnknown* unk {nullptr};
|
||||
|
||||
LOG_OUTPUT(L"Create MyNavView...");
|
||||
{
|
||||
@@ -4077,10 +4288,10 @@ void XamlIslandTests::ValidateNavigationView()
|
||||
unk->AddRef();
|
||||
unk->AddRef();
|
||||
unk->AddRef();
|
||||
|
||||
|
||||
navView->SelectedItem = 1;
|
||||
}
|
||||
|
||||
|
||||
int refcount = GetRefCount(unk);
|
||||
VERIFY_ARE_EQUAL(3, refcount);
|
||||
|
||||
@@ -4355,7 +4566,7 @@ void XamlIslandTests::PopupsWorkInHwndlessIslands()
|
||||
<Flyout x:Name="WindowlessFlyout">
|
||||
<TextBlock Text="Flyout content" />
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button x:Name="WindowedFlyoutButton" Content="Click to open windowed flyout">
|
||||
<Button.Flyout>
|
||||
@@ -4364,7 +4575,7 @@ void XamlIslandTests::PopupsWorkInHwndlessIslands()
|
||||
<MenuFlyoutItem Text="Item 2" />
|
||||
<MenuFlyoutItem Text="Item 3" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>)"));
|
||||
|
||||
@@ -203,6 +203,11 @@ public:
|
||||
TEST_METHOD(ValidateDispatcherShutdownModeInIslandsApp)
|
||||
TEST_METHOD(ValidateDispatcherShutdownModeInDesktopApp)
|
||||
|
||||
BEGIN_TEST_METHOD(ApplicationBehaviorAtCoreShutdown)
|
||||
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
|
||||
TEST_METHOD_PROPERTY(L"Description", L"Validate Application behavior before shutdown, after shutdown, and after restart.")
|
||||
END_TEST_METHOD()
|
||||
|
||||
BEGIN_TEST_METHOD(ValidateCallbackErrorPropagatesInDesktopApp)
|
||||
TEST_METHOD_PROPERTY(L"Description", L"Verify that an app callback failure in Application.Start propagates as an error return instead of fail-fasting inside WinUI.")
|
||||
END_TEST_METHOD()
|
||||
|
||||
@@ -45,6 +45,13 @@ void DynamicMetadataStorage::Destroy()
|
||||
FAIL_FAST_ASSERT(s_storage == nullptr);
|
||||
}
|
||||
|
||||
void DynamicMetadataStorage::Abandon()
|
||||
{
|
||||
// Metadata can hold references to objects implemented by other DLLs. During process teardown those DLLs may
|
||||
// already have been uninitialized, so intentionally leak the storage instead of running their destructors.
|
||||
(void)s_storage.release();
|
||||
}
|
||||
|
||||
void DynamicMetadataStorage::ResetInstance()
|
||||
{
|
||||
// First, mark all custom properties as invalid.
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace DirectUI
|
||||
void ResetInstance();
|
||||
static void Reset();
|
||||
static void Destroy();
|
||||
static void Abandon();
|
||||
|
||||
void DestroyCustomPropertiesAndDPs()
|
||||
{
|
||||
@@ -170,6 +171,15 @@ namespace DirectUI
|
||||
}
|
||||
}
|
||||
|
||||
static void Abandon()
|
||||
{
|
||||
if (DirectUI::CStaticLock::IsInitialized())
|
||||
{
|
||||
DirectUI::CStaticLock lock;
|
||||
DynamicMetadataStorage::Abandon();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
DirectUI::CStaticLock m_lock;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "FrameworkApplication.g.h"
|
||||
#include "BackgroundTaskFrameworkContext.h"
|
||||
#include <MetadataAPI.h>
|
||||
#include <DynamicMetadataStorage.h>
|
||||
#include <DependencyLocator.h>
|
||||
#include <CStaticLock.h>
|
||||
#include "ThreadPoolService.h"
|
||||
@@ -177,16 +178,6 @@ void DeinitializeDll()
|
||||
g_nTlsIsCoreServicesReady = TLS_OUT_OF_INDEXES;
|
||||
}
|
||||
|
||||
// Ideally the metadata has already been cleaned up by now. We called MetadataAPI::Destroy() when the
|
||||
// last (main) FrameworkView went away (and called FrameworkApplication::ReleaseCurrent()).
|
||||
// The only exception right now is our Standalone test runner (ttds.exe). Instead of updating that code,
|
||||
// we're leaving this call here to preserve the existing
|
||||
// cleanup behavior as a fallback option. If anyone ever runs into a crash because we're cleaning up
|
||||
// metadata here that ends up trying to release a reference to an object whose code pages are no longer
|
||||
// there, they will need to make sure there's a MetadataAPI::Reset() call that happened sooner (*before*
|
||||
// modules are being unloaded).
|
||||
MetadataAPI::Destroy();
|
||||
|
||||
ThreadPoolService::DetachFactories();
|
||||
|
||||
BackgroundTaskFrameworkContext::GlobalDeinit();
|
||||
@@ -224,6 +215,27 @@ void DeinitializeDll()
|
||||
|
||||
IGNORERESULT(WarningContextGlobalDeinit());
|
||||
|
||||
//
|
||||
// There is a change to the shutdown process some time after WASDK 2.5.3 that allowed WinUI to restart in a process
|
||||
// where it has shut down. The problem was metadata and custom DependencyProperties. When the last instance of WinUI
|
||||
// shuts down, it also unregisters all metadata (including custom DPs previously registered with it). When it restarts,
|
||||
// external components like MUXC tries to reuse their previously registered custom DPs, only to find that their fields
|
||||
// have all been cleared and the DP is invalid.
|
||||
//
|
||||
// One approach is to have all components release their custom DPs and reregister after restarting, but that's a breaking
|
||||
// change for components that already defined custom DPs as static readonly fields that can't be re-created. The approach
|
||||
// we went with was to have WinUI not touch the metadata at all during shutdown, since the metadata objects aren't bound
|
||||
// to any UI thread and can be safely reused after restarting in the process anyway.
|
||||
//
|
||||
// Doing that leads us to problems during process teardown, where WinUI's dll unload will clear the metadata in the process.
|
||||
// Previously this metadata would have already been cleaned up by WinUI shutdown, but now it stays around until the middle
|
||||
// of unloading binaries. WinUI metadata can have references to objects implemented by other DLLs, which may already be
|
||||
// unloaded, so we cannot safely call out to them during final cleanup. Since the process is being destroyed anyway,
|
||||
// we'll just leak these metadata objects instead. Intentionally skip the call to MetadataAPI::Destroy(), and also clear
|
||||
// the unique pointer holding the DynamicMetadataStorage so that CRT doesn't try to delete it.
|
||||
//
|
||||
DynamicMetadataStorageInstanceWithLock::Abandon();
|
||||
|
||||
// Uninitializing the dependency locator needs to be the absolute last thing that we do*.
|
||||
// Any of the above calls could rely on a Dependency and since we never get this far in
|
||||
// a normal modern app process, uninitializing it any earlier is a bug farm waiting to happen
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "UnhandledExceptionEventArgs.g.h"
|
||||
#include <FrameworkTheming.h>
|
||||
#include <DependencyLocator.h>
|
||||
#include <MetadataResetter.h>
|
||||
#include <process.h>
|
||||
#include <RuntimeEnabledFeatures.h>
|
||||
#include "NormalLaunchActivatedEventArgs.h"
|
||||
@@ -454,19 +453,10 @@ void FrameworkApplication::ReleaseCurrent()
|
||||
// alive without Xaml, in which case Xaml should reuse the same instance should we ever get reinitialized.
|
||||
IFCFAILFAST(ctl::AsWeak(g_pApplication, &g_previousApplicationWeak));
|
||||
|
||||
// The metadata store may hold a reference to an IXamlMetadataProvider, which is usually the
|
||||
// Application object (which derives from FrameworkApplication). In other words, there may be
|
||||
// a reference cycle. We want to break that cycle when the main FrameworkView goes away.
|
||||
g_pApplication->m_metadataRef = nullptr;
|
||||
ctl::release_interface(g_pApplication);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<MetadataResetter> FrameworkApplication::GetMetadataReference()
|
||||
{
|
||||
return m_metadataRef;
|
||||
}
|
||||
|
||||
_Check_return_ HRESULT FrameworkApplicationFactory::get_CurrentImpl(_Outptr_result_maybenull_ xaml::IApplication** ppValue)
|
||||
{
|
||||
FrameworkApplication* pInstance = FrameworkApplication::GetCurrentNoRef();
|
||||
@@ -629,10 +619,6 @@ _Check_return_ HRESULT FrameworkApplication::Initialize()
|
||||
IFCEXPECT_RETURN(m_pDebugSettings); // Should never fail
|
||||
m_pDebugSettings->UpdatePeg(true);
|
||||
|
||||
// Set up the metadata resetter. This object clears out the process-wide metadata when it is safe to do so (before
|
||||
// DLLs are getting unloaded, but after we're done shutting down the visual tree).
|
||||
m_metadataRef = std::make_shared<MetadataResetter>();
|
||||
|
||||
// Determine which AppPolicyWindowingModel is being used. Use Application::GetAppPolicyWindowingModel() to
|
||||
// get the current Windowing model.
|
||||
//
|
||||
@@ -999,6 +985,10 @@ _Check_return_ HRESULT FrameworkApplication::ExitImpl()
|
||||
_Check_return_ HRESULT FrameworkApplication::put_RequestedThemeImpl(
|
||||
_In_ xaml::ApplicationTheme value)
|
||||
{
|
||||
IFC_RETURN(DXamlServices::IsDXamlCoreInitialized() ? S_OK : RPC_E_WRONG_THREAD);
|
||||
DXamlCore* dxamlCore = DXamlCore::GetCurrent();
|
||||
IFCEXPECT_RETURN(dxamlCore);
|
||||
|
||||
// RequestedTheme cannot be set after app.xaml has been loaded.
|
||||
if (!m_isRequestedThemeSettable)
|
||||
{
|
||||
@@ -1049,7 +1039,7 @@ _Check_return_ HRESULT FrameworkApplication::put_RequestedThemeImpl(
|
||||
// Note: This is special, we bypass the CApplication::SetValue so this property never actually
|
||||
// get's set on the core object. Just an annoying nuance because our parser set's this by going
|
||||
// through CApplication::SetValue. See comment above for more info.
|
||||
IFC_RETURN(DXamlCore::GetCurrent()->GetHandle()->GetFrameworkTheming()->SetRequestedTheme(value));
|
||||
IFC_RETURN(dxamlCore->GetHandle()->GetFrameworkTheming()->SetRequestedTheme(value));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1066,7 +1056,11 @@ _Check_return_ HRESULT FrameworkApplication::put_RequestedThemeImpl(
|
||||
_Check_return_ HRESULT FrameworkApplication::get_RequestedThemeImpl(
|
||||
_Out_ xaml::ApplicationTheme* pValue)
|
||||
{
|
||||
auto theme = DXamlCore::GetCurrent()->GetHandle()->GetFrameworkTheming()->GetBaseTheme();
|
||||
IFC_RETURN(DXamlServices::IsDXamlCoreInitialized() ? S_OK : RPC_E_WRONG_THREAD);
|
||||
DXamlCore* dxamlCore = DXamlCore::GetCurrent();
|
||||
IFCEXPECT_RETURN(dxamlCore);
|
||||
|
||||
auto theme = dxamlCore->GetHandle()->GetFrameworkTheming()->GetBaseTheme();
|
||||
*pValue = (theme == Theming::Theme::Light ? xaml::ApplicationTheme_Light : xaml::ApplicationTheme_Dark);
|
||||
|
||||
RRETURN(S_OK);
|
||||
@@ -1214,10 +1208,14 @@ _Check_return_ HRESULT FrameworkApplication::get_HighContrastAdjustmentImpl(_Out
|
||||
// Sets the FrameworkApplication::HighContrastAdjustment property value.
|
||||
_Check_return_ HRESULT FrameworkApplication::put_HighContrastAdjustmentImpl(_In_ xaml::ApplicationHighContrastAdjustment value)
|
||||
{
|
||||
IFC_RETURN(DXamlServices::IsDXamlCoreInitialized() ? S_OK : RPC_E_WRONG_THREAD);
|
||||
DXamlCore* dxamlCore = DXamlCore::GetCurrent();
|
||||
IFCEXPECT_RETURN(dxamlCore);
|
||||
|
||||
if (m_highContrastAdjustment != value)
|
||||
{
|
||||
m_highContrastAdjustment = value;
|
||||
IFC_RETURN(DXamlCore::GetCurrent()->OnApplicationHighContrastAdjustmentChanged());
|
||||
IFC_RETURN(dxamlCore->OnApplicationHighContrastAdjustmentChanged());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <FrameworkApplication.g.h>
|
||||
#include <XamlIslandRootCollection.g.h>
|
||||
#include <MetadataResetter.h>
|
||||
#include "theming\inc\Theme.h"
|
||||
|
||||
XAML_ABI_NAMESPACE_BEGIN
|
||||
@@ -58,8 +57,6 @@ namespace DirectUI
|
||||
|
||||
static _Check_return_ HRESULT GetApplicationHighContrastAdjustment(_Out_ ApplicationHighContrastAdjustment* pApplicationHighContrastAdjustment);
|
||||
|
||||
std::shared_ptr<MetadataResetter> GetMetadataReference();
|
||||
|
||||
AppPolicyWindowingModel GetAppPolicyWindowingModel() { return m_appPolicyWindowingModel; }
|
||||
|
||||
_Check_return_ HRESULT RaiseUnhandledExceptionEvent(_In_ HRESULT hrToReport, _In_opt_ HSTRING hstrMessage, _Inout_ bool* pfHandled);
|
||||
@@ -194,8 +191,6 @@ namespace DirectUI
|
||||
|
||||
XAML::PLM::PLMHandler* m_pPLMHandlerForMTA { nullptr };
|
||||
|
||||
std::shared_ptr<MetadataResetter> m_metadataRef;
|
||||
|
||||
// Stores the current FocusVisualKind property value.
|
||||
xaml::FocusVisualKind m_focusVisualKind { xaml::FocusVisualKind_DottedLine };
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <MetadataAPI.h>
|
||||
#include <DependencyLocator.h>
|
||||
#include <MetadataResetter.h>
|
||||
|
||||
using namespace DirectUI;
|
||||
using namespace DirectUISynonyms;
|
||||
@@ -46,9 +45,6 @@ IFACEMETHODIMP FrameworkView::Initialize(_In_ wac::ICoreApplicationView* pCoreAp
|
||||
ReplaceInterface(m_pCoreApplicationView, pCoreApplicationView);
|
||||
IFC(HookCoreActivationEvents(TRUE /* fRegister */));
|
||||
|
||||
// Get reference to metadata from app object after it has been initialized.
|
||||
m_metadataRef = FrameworkApplication::GetCurrentNoRef()->GetMetadataReference();
|
||||
|
||||
Cleanup:
|
||||
if (FAILED(hr) && fIsMainView)
|
||||
{
|
||||
@@ -119,10 +115,6 @@ Cleanup:
|
||||
// (CoreApplicationView references the FrameworkView, and the FrameworkView references the CoreApplicationView).
|
||||
ReleaseInterface(m_pCoreApplicationView);
|
||||
|
||||
// Clear the reference to the metadata store from this thread. If this is the last reference, this will
|
||||
// reset the metadata store.
|
||||
m_metadataRef = nullptr;
|
||||
|
||||
VERIFYHR(hr);
|
||||
// should we return hr as result of Uninitialize?
|
||||
RRETURN(S_OK);
|
||||
@@ -236,4 +228,3 @@ _Check_return_ HRESULT FrameworkView::HookCoreActivationEvents(bool fRegister)
|
||||
Cleanup:
|
||||
RRETURN(hr);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <FrameworkView.g.h>
|
||||
#include <MetadataResetter.h>
|
||||
|
||||
namespace DirectUI
|
||||
{
|
||||
@@ -34,7 +33,5 @@ namespace DirectUI
|
||||
_In_ waa::IActivatedEventArgs* pArgs);
|
||||
|
||||
EventRegistrationToken m_ActivatedEventToken;
|
||||
|
||||
std::shared_ptr<MetadataResetter> m_metadataRef;
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
#include "precomp.h"
|
||||
#include <MetadataResetter.h>
|
||||
|
||||
using namespace DirectUI;
|
||||
|
||||
MetadataResetter::~MetadataResetter()
|
||||
{
|
||||
MetadataAPI::Reset();
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace DirectUI
|
||||
{
|
||||
class MetadataResetter
|
||||
{
|
||||
public:
|
||||
~MetadataResetter();
|
||||
};
|
||||
}
|
||||
@@ -133,11 +133,19 @@ public:
|
||||
{
|
||||
auto managerStrongRef = m_manager;
|
||||
|
||||
// WARNING: "this" will be invalid after Close() returns!
|
||||
// XamlCore keeps WindowsXamlManager alive, and WindowsXamlManager keeps XamlCore alive through m_xamlCore.
|
||||
// Keep the manager alive locally while closing the core, then break that cycle after raising its final event.
|
||||
FAIL_FAST_ASSERT(m_manager);
|
||||
this->Close();
|
||||
|
||||
managerStrongRef->RaiseXamlShutdownCompletedOnThreadEvent(args);
|
||||
|
||||
// The core is now closed, so mark the manager closed while still on its owning thread. External references
|
||||
// may be released from another thread after shutdown, and its destructor must not try to close it there.
|
||||
IFCFAILFAST(managerStrongRef->CloseImpl(true /* synchronous */));
|
||||
|
||||
m_manager.Reset();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -526,7 +534,6 @@ _Check_return_ HRESULT WindowsXamlManager::XamlCore::Close()
|
||||
|
||||
if (lastInstanceInProcess)
|
||||
{
|
||||
// MetadataAPI::Reset() is called in FrameworkApplication::ReleaseCurrent when m_metadataRef is reset
|
||||
FrameworkApplication::ReleaseCurrent();
|
||||
|
||||
// ActivationFactoryCache is a process-wide singleton (DependencyLocator StoragePolicyFlags::None). It caches
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
<ClCompile Include="..\StaticStore.cpp"/>
|
||||
<ClCompile Include="..\XBFXamlTypeWrapper.cpp"/>
|
||||
<ClCompile Include="..\MetadataAPI.cpp"/>
|
||||
<ClCompile Include="..\MetadataResetter.cpp"/>
|
||||
<ClCompile Include="..\ActivationAPI.cpp"/>
|
||||
<ClCompile Include="..\ResourceDictionary_partial.cpp"/>
|
||||
<ClCompile Include="..\DirectUIXamlQualifiedObject.cpp"/>
|
||||
|
||||
@@ -249,6 +249,7 @@ if ($Mode -eq "ScenarioTestSuite")
|
||||
{
|
||||
Publish-Item "$binpath\Samples\WinUICsDesktopSampleApp_Test\*.msix*" "$outpath\Test\"
|
||||
Publish-Item "$binpath\Samples\WinUICppDesktopSampleApp_Test\*.msix*" "$outpath\Test\"
|
||||
Publish-Item "$binpath\Samples\WinUICppIslandsSampleApp_Test\*.msix*" "$outpath\Test\"
|
||||
Publish-Item "$binpath\Samples\DisableXamlGeneratedMainCs_Test\*.msix" "$outpath\Test\"
|
||||
Publish-Item "$binpath\Samples\DisableXamlGeneratedMainNoCtorCs_Test\*.msix" "$outpath\Test\"
|
||||
Publish-Item "$binpath\Samples\DisableXamlGeneratedMainCpp_Test\*.msix" "$outpath\Test\"
|
||||
@@ -282,6 +283,7 @@ if ($Mode -eq "ScenarioTestSuite")
|
||||
if (-not ($env:BUILD_DEFINITIONNAME -and ($env:BUILD_DEFINITIONNAME.Contains("ValidateReunion") -or $env:BUILD_DEFINITIONNAME.Contains("WindowsAppSDK"))))
|
||||
{
|
||||
Publish-Item "$binpath\Samples\WinUICppDesktopSampleApp_Test\Dependencies\$redistPlatform\*.appx" "$outpath\Test\"
|
||||
Publish-Item "$binpath\Samples\WinUICppIslandsSampleApp_Test\Dependencies\$redistPlatform\*.appx" "$outpath\Test\"
|
||||
}
|
||||
|
||||
# TODO: Remove below check
|
||||
|
||||
@@ -95,6 +95,7 @@ if (!$SkipPackageUninstall)
|
||||
# Note - Make sure that this list is kept in sync with controls\test\testinfra\MUXTestInfra\TestAppInstallHelper.cs
|
||||
Write-Host "Attempting to uninstall previous versions of sample apps";
|
||||
Get-AppxPackage "WinUICppDesktopSampleApp" | Remove-AppxPackage -ErrorAction SilentlyContinue;
|
||||
Get-AppxPackage "WinUICppIslandsSampleApp" | Remove-AppxPackage -ErrorAction SilentlyContinue;
|
||||
Get-AppxPackage "WinUICsDesktopSampleApp" | Remove-AppxPackage -ErrorAction SilentlyContinue;
|
||||
Get-AppxPackage "Microsoft.WinUI3ControlsGallery.Debug" | Remove-AppxPackage -ErrorAction SilentlyContinue;
|
||||
Get-AppxPackage "Microsoft.WinUI3ControlsGallery" | Remove-AppxPackage -ErrorAction SilentlyContinue;
|
||||
|
||||
+29
-15
@@ -8,6 +8,7 @@
|
||||
# .\run-tests-on-vm.ps1 -VMName "MyVM" "Button*" -HostingMode WPF
|
||||
# .\run-tests-on-vm.ps1 -VMName "MyVM" "MyTest" -SkipPayload
|
||||
# .\run-tests-on-vm.ps1 -VMName "MyVM" "MyTest" -FullCopy
|
||||
# .\run-tests-on-vm.ps1 -VMName "MyVM" "MySampleAppTest" -Mode ScenarioTestSuite
|
||||
# .\run-tests-on-vm.ps1 -VMName "MyVM" -Stop
|
||||
#
|
||||
# First run will prompt for VM credentials and cache them (encrypted, per-user).
|
||||
@@ -18,12 +19,6 @@ param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$VMName,
|
||||
|
||||
[Parameter(Position=0, Mandatory=$false)]
|
||||
[string]$TestName,
|
||||
|
||||
[Parameter(ValueFromRemainingArguments=$true)]
|
||||
[string[]]$AdditionalArgs,
|
||||
|
||||
[ValidateSet("x86", "x64", "arm64")]
|
||||
[string]$Platform = "",
|
||||
|
||||
@@ -36,8 +31,8 @@ param(
|
||||
# Skip CreateTestPayload step (use existing payload)
|
||||
[switch]$SkipPayload,
|
||||
|
||||
# CreateTestPayload mode. Most tests use DevTestSuite.
|
||||
[ValidateSet("Auto", "DevTestSuite")]
|
||||
# CreateTestPayload mode. Most tests use DevTestSuite; sample app tests use ScenarioTestSuite.
|
||||
[ValidateSet("Auto", "DevTestSuite", "ScenarioTestSuite")]
|
||||
[string]$Mode = "Auto",
|
||||
|
||||
# Force a full copy instead of incremental
|
||||
@@ -54,11 +49,22 @@ param(
|
||||
[switch]$ResetCredential,
|
||||
|
||||
# Stop any running test on the VM and clean up
|
||||
[switch]$Stop
|
||||
[switch]$Stop,
|
||||
|
||||
# The first positional argument is the test name; remaining arguments are passed to runtests.cmd.
|
||||
[Parameter(Position=0, ValueFromRemainingArguments=$true)]
|
||||
[string[]]$TestArguments
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$TestName = if ($TestArguments.Count -gt 0) { $TestArguments[0] } else { $null }
|
||||
$AdditionalArgs = if ($TestArguments.Count -gt 1) {
|
||||
@($TestArguments[1..($TestArguments.Count - 1)])
|
||||
} else {
|
||||
@()
|
||||
}
|
||||
|
||||
# Force UTF-8 so streamed VM log output uses a single, predictable encoding.
|
||||
try {
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
@@ -627,12 +633,20 @@ echo %ERRORLEVEL% > "$exitFile"
|
||||
}
|
||||
}
|
||||
|
||||
# te.exe returns 0 even when tests fail. Parse the TAEF summary line.
|
||||
if ($exitCode -eq 0 -and (Test-Path $logFile)) {
|
||||
$logLines = Read-NormalizedLog $logFile
|
||||
$summaryLine = $logLines | Where-Object { $_ -match 'Summary:\s+Total=\d+' } | Select-Object -Last 1
|
||||
if ($summaryLine -match 'Failed=(\d+)') {
|
||||
if ([int]$Matches[1] -gt 0) { $exitCode = 1 }
|
||||
# te.exe can return 0 when tests fail or no tests run. Require a non-empty passing TAEF summary.
|
||||
if ($exitCode -eq 0) {
|
||||
if (-not (Test-Path $logFile)) {
|
||||
$exitCode = 1
|
||||
} else {
|
||||
$logLines = Read-NormalizedLog $logFile
|
||||
$summaryLine = $logLines | Where-Object { $_ -match 'Summary:\s+Total=\d+' } | Select-Object -Last 1
|
||||
if (-not $summaryLine) {
|
||||
$exitCode = 1
|
||||
} elseif ($summaryLine -match 'Total=(\d+).+Failed=(\d+)') {
|
||||
if ([int]$Matches[1] -eq 0 -or [int]$Matches[2] -gt 0) {
|
||||
$exitCode = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user