【UWP】让 UWP 自己托管自己 —— Windows App SDK 篇

news/2025/3/11 2:45:46/文章来源:https://www.cnblogs.com/wherewhere/p/18446822

众所周知,UWP 使用的窗口模型是 CoreWindow,但是 UWP 本身只是一个应用模型,所以完全可以创建 win32 窗口,那么我们可以不可以创建一个 win32 窗口,然后像 XAML 岛 (XAML Islands) 一样把 XAML 托管上去呢?本篇将讲述如何利用 WAS (Windows App SDK,俗称 WinUI3) 在 UWP 创建一个 XAML 岛窗口。

示例

演示视频:https://x.com/wherewhere7/status/1721570411388039587

由于 WAS 在 win32 应用模型下本身就是个 XAML 岛,所以 WAS 对 XAML 岛的支持要比 WUXC (Windows.UI.Xaml.Controls) 要好多了,接下来的内容大多是将 WAS 中实现窗口的方法迁移到 C#。

首先,不管是 WUXC 还是 WAS 的 XAML 岛都会判断当前的应用模型是否为ClassicDesktop,所以我们需要利用Detours劫持AppPolicyGetWindowingModel方法。具体内容如下:

#r "nuget:Detours.Win32Metadata"
#r "nuget:Microsoft.Windows.CsWin32"using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Storage.Packaging.Appx;
using Detours = Microsoft.Detours.PInvoke;/// <summary>
/// Represents a hook for the <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.
/// </summary>
public sealed partial class HookWindowingModel : IDisposable
{/// <summary>/// The value that indicates whether the class has been disposed./// </summary>private bool disposed;/// <summary>/// The reference count for the hook./// </summary>private static int refCount;/// <summary>/// The value that represents the current process token./// </summary>private const int currentProcessToken = -6;/// <remarks>The original <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.</remarks>/// <inheritdoc cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/>private static unsafe delegate* unmanaged[Stdcall]<HANDLE, AppPolicyWindowingModel*, WIN32_ERROR> AppPolicyGetWindowingModel;/// <summary>/// Initializes a new instance of the <see cref="HookWindowingModel"/> class./// </summary>public HookWindowingModel(){refCount++;StartHook();}/// <summary>/// Finalizes this instance of the <see cref="HookWindowingModel"/> class./// </summary>~HookWindowingModel(){Dispose();}/// <summary>/// Gets the value that indicates whether the hook is active./// </summary>public static bool IsHooked { get; private set; }/// <summary>/// Gets or sets the windowing model to use when the hooked <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function is called./// </summary>internal static AppPolicyWindowingModel WindowingModel { get; set; } = AppPolicyWindowingModel.AppPolicyWindowingModel_ClassicDesktop;/// <summary>/// Starts the hook for the <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function./// </summary>private static unsafe void StartHook(){if (!IsHooked){using FreeLibrarySafeHandle library = PInvoke.GetModuleHandle("KERNEL32.dll");if (!library.IsInvalid && NativeLibrary.TryGetExport(library.DangerousGetHandle(), nameof(PInvoke.AppPolicyGetWindowingModel), out nint appPolicyGetWindowingModel)){void* appPolicyGetWindowingModelPtr = (void*)appPolicyGetWindowingModel;delegate* unmanaged[Stdcall]<HANDLE, AppPolicyWindowingModel*, WIN32_ERROR> overrideAppPolicyGetWindowingModel = &OverrideAppPolicyGetWindowingModel;_ = Detours.DetourRestoreAfterWith();_ = Detours.DetourTransactionBegin();_ = Detours.DetourUpdateThread(PInvoke.GetCurrentThread());_ = Detours.DetourAttach(ref appPolicyGetWindowingModelPtr, overrideAppPolicyGetWindowingModel);_ = Detours.DetourTransactionCommit();AppPolicyGetWindowingModel = (delegate* unmanaged[Stdcall]<HANDLE, AppPolicyWindowingModel*, WIN32_ERROR>)appPolicyGetWindowingModelPtr;IsHooked = true;}}}/// <summary>/// Ends the hook for the <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function./// </summary>private static unsafe void EndHook(){if (--refCount == 0 && IsHooked){void* appPolicyGetWindowingModelPtr = AppPolicyGetWindowingModel;delegate* unmanaged[Stdcall]<HANDLE, AppPolicyWindowingModel*, WIN32_ERROR> overrideAppPolicyGetWindowingModel = &OverrideAppPolicyGetWindowingModel;_ = Detours.DetourTransactionBegin();_ = Detours.DetourUpdateThread(PInvoke.GetCurrentThread());_ = Detours.DetourDetach(&appPolicyGetWindowingModelPtr, overrideAppPolicyGetWindowingModel);_ = Detours.DetourTransactionCommit();AppPolicyGetWindowingModel = null;IsHooked = false;}}/// <param name="policy">A pointer to a variable of the <a href="https://docs.microsoft.com/windows/win32/api/appmodel/ne-appmodel-apppolicywindowingmodel">AppPolicyWindowingModel</a> enumerated type./// When the function returns successfully, the variable contains the <see cref="WindowingModel"/> when the identified process is current; otherwise, the windowing model of the identified process.</param>/// <remarks>The overridden <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.</remarks>/// <inheritdoc cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/>[UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]private static unsafe WIN32_ERROR OverrideAppPolicyGetWindowingModel(HANDLE processToken, AppPolicyWindowingModel* policy){if ((int)processToken.Value == currentProcessToken){*policy = WindowingModel;return WIN32_ERROR.ERROR_SUCCESS;}return AppPolicyGetWindowingModel(processToken, policy);}/// <inheritdoc/>public void Dispose(){if (!disposed && IsHooked){EndHook();}GC.SuppressFinalize(this);disposed = true;}
}

准备工作完成,接下来我们就可以创建窗口了,首先我们需要新创建一个线程,CoreWindow 线程无法新建 XAML 岛,不过在 XAML 岛线程可以,新建线程只需要用Thread就行了。

new Thread(() => { ... });

WAS 提供了AppWindow来管理 win32 窗口,我们只需要使用它创建一个窗口就行了。

AppWindow window = AppWindow.Create();

接下来我们需要创建 XAML 岛,这时我们就需要利用上面劫持器来劫持获取应用模型的方法了。

DispatcherQueueController controller;
DesktopWindowXamlSource source;using (HookWindowingModel hook = new())
{controller = DispatcherQueueController.CreateOnCurrentThread();source = new DesktopWindowXamlSource();
}

然后我们就可以把 XAML 岛糊到之前创建的 AppWindow 上了。

source.Initialize(window.Id);
DesktopChildSiteBridge bridge = source.SiteBridge;
bridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow;
bridge.Show();DispatcherQueue dispatcherQueue = controller.DispatcherQueue;
window.AssociateWithDispatcherQueue(dispatcherQueue);

由于 XAML 岛存在的一些特性,当窗口扩展标题栏或者全屏化的是否窗口内容并不会跟着变化,所以我们需要一些小魔法来让它在变化时调整大小。

window.Changed += (sender, args) =>
{if (args.DidPresenterChange){bridge.ResizePolicy = ContentSizePolicy.None;bridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow;}
};

最后不要忘了保持当前线程,不然这里跑完了窗口就退出了。

dispatcherQueue.RunEventLoop();
await controller.ShutdownQueueAsync();

当窗口关闭后记得执行DispatcherQueue.EnqueueEventLoopExit()来释放保持的线程。

最后把之前的东西组合起来,再加点东西:

/// <summary>
/// Create a new <see cref="DesktopWindow"/> instance.
/// </summary>
/// <param name="launched">Do something after <see cref="DesktopWindowXamlSource"/> created.</param>
/// <returns>The new instance of <see cref="DesktopWindow"/>.</returns>
public static Task<DesktopWindow> CreateAsync(Action<DesktopWindowXamlSource> launched)
{TaskCompletionSource<DesktopWindow> taskCompletionSource = new();new Thread(async () =>{try{DispatcherQueueController controller;DesktopWindowXamlSource source;AppWindow window = AppWindow.Create();using (HookWindowingModel hook = new()){controller = DispatcherQueueController.CreateOnCurrentThread();source = new DesktopWindowXamlSource();}source.Initialize(window.Id);DesktopChildSiteBridge bridge = source.SiteBridge;bridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow;bridge.Show();window.Changed += (sender, args) =>{if (args.DidPresenterChange){bridge.ResizePolicy = ContentSizePolicy.None;bridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow;}};DispatcherQueue dispatcherQueue = controller.DispatcherQueue;window.AssociateWithDispatcherQueue(dispatcherQueue);TrackWindow(window);launched(source);DesktopWindow desktopWindow = new(){AppWindow = window,WindowXamlSource = source};taskCompletionSource.SetResult(desktopWindow);dispatcherQueue.RunEventLoop();await controller.ShutdownQueueAsync();}catch (Exception e){taskCompletionSource.SetException(e);}}){Name = nameof(DesktopWindowXamlSource)}.Start();return taskCompletionSource.Task;
}/// <summary>
/// Create a new <see cref="DesktopWindow"/> instance.
/// </summary>
/// <param name="dispatcherQueue">The <see cref="DispatcherQueue"/> to provide thread.</param>
/// <param name="launched">Do something after <see cref="DesktopWindowXamlSource"/> created.</param>
/// <returns>The new instance of <see cref="DesktopWindow"/>.</returns>
public static Task<DesktopWindow> CreateAsync(DispatcherQueue dispatcherQueue, Action<DesktopWindowXamlSource> launched)
{TaskCompletionSource<DesktopWindow> taskCompletionSource = new();_ = dispatcherQueue.TryEnqueue(() =>{try{DesktopWindowXamlSource source;AppWindow window = AppWindow.Create();window.AssociateWithDispatcherQueue(dispatcherQueue);TrackWindow(window);using (HookWindowingModel hook = new()){source = new DesktopWindowXamlSource();}source.Initialize(window.Id);DesktopChildSiteBridge bridge = source.SiteBridge;bridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow;bridge.Show();window.Changed += (sender, args) =>{if (args.DidPresenterChange){bridge.ResizePolicy = ContentSizePolicy.None;bridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow;}};launched(source);DesktopWindow desktopWindow = new(){AppWindow = window,WindowXamlSource = source};taskCompletionSource.SetResult(desktopWindow);}catch (Exception e){taskCompletionSource.SetException(e);}});return taskCompletionSource.Task;
}private static void TrackWindow(AppWindow window)
{if (ActiveDesktopWindows.ContainsKey(window.DispatcherQueue)){ActiveDesktopWindows[window.DispatcherQueue] += 1;}else{ActiveDesktopWindows[window.DispatcherQueue] = 1;}window.Destroying -= AppWindow_Destroying;window.Destroying += AppWindow_Destroying;
}private static void AppWindow_Destroying(AppWindow sender, object args)
{if (ActiveDesktopWindows.TryGetValue(sender.DispatcherQueue, out ulong num)){num--;if (num == 0){ActiveDesktopWindows.Remove(sender.DispatcherQueue);sender.DispatcherQueue.EnqueueEventLoopExit();return;}ActiveDesktopWindows[sender.DispatcherQueue] = num;}
}private static Dictionary<DispatcherQueue, ulong> ActiveDesktopWindows { get; } = [];

其中DesktopWindow是用来存放 AppWindowDesktopWindowXamlSource的类,如果不嫌麻烦的话可以包裹成一个和 Microsoft.UI.Xaml.Window一样的东西。

最后附上示例应用:https://github.com/wherewhere/CoreAppUWP/tree/muxc

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/826213.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【算法】记忆化搜索

[!TIP] 一种剪枝算法,优化运算效率,减少冗余计算基本内容入门例子 [P1028 [NOIP2001 普及组] 数的计算]([P1028 NOIP2001 普及组] 数的计算 - 洛谷 | 计算机科学教育新生态)题目要求:输入n,输出一共可以构造多少个数列,要求数列的第 i不能超过第i-1个数的一半 示例:输入6…

『模拟赛』NOIP2024模拟1

『模拟赛记录』NOIP2024模拟1Rank 有点可惜,A. 玩游戏 绝妙贪心题。感觉这种能产生很多假做法且都可 hack 的贪心都是好题。 赛时不知道为什么犯唐没交一开始的暴力贪心。 考虑双指针,设左右指针分别为 \(l,r\)。主要思路是实时维护当前两个指针向两边最近的一个区间和不为正…

2024-2025-1 20241312《计算机基础与程序设计》第6周学习总结

这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 <作业要求的链接>(如2024-2025-1计算机基础与程序设计第六周作业)这个作业的目标 Polya如何解决问题 简单类型与组合类型 复合数据结构 查找与排序算法 算法复杂度 递…

工程师和科学家的高等数学及python实例:2三角函数 II

2 三角函数 II 学完本章内容后,你应该能够 ● 讨论反三角函数的图形 ● 讨论倒数函数的图形 ● 评估正弦、余弦和正切函数的变换 2.1 引言 本章将继续讨论三角函数,研究上一章中涉及的三个三角函数的倒数和反三角函数。本章还将讨论这些函数的变换。 2.2 三角函数的倒数 正弦…

搭建主从DNS服务器实现域名正逆向解析

1.前置工作:关闭防火墙及selinux 2.安装软件:yum install -y bind 3.本文仅搭建本地DNS实现逆向域名解析 1)建议复制逆向解析模板再进行修改 模板路径:/etc/named.rfc1912.zones 2)进入主配置文件并添加逆向配置文件 vim /etc/named.conf 注意:区域名称中IP地址反向书写,…

2024-2025-1 20241407《计算机基础与程序设计》第六周学习总结

这个作业属于哪个课程 [2024-2025-1计算机基础与程序设计](https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP)这个作业要求在哪里 2024-2025-1计算机基础与程序设计第六周作业这个作业的目标 学习Polya如何解决问题,简单类型与组合类型,复合数据结构,查找与排序算法…

Me-and-My-Girlfriend-1靶机渗透 (Vulnhub内网渗透)

一、Me-and-My-Girlfriend-1: 1、渗透目标: Description: This VM tells us that there are a couple of lovers namely Alice and Bob, where the couple was originally very romantic, but since Alice worked at a private company, "Ceban Corp", something …

pc大屏适配

在文件utils里配置 const baseSize = 12 //设置rem函数 function setRem() {//页面宽度相对于设计图宽的缩放比例,根据需要修改 1920(设计稿宽度)const scale = document.documentElement.clientWidth / 1920;// 设置页面根节点字体大小(match.min(scale,2)"指最高放大比…

高级语言程序设计第五次个人作业

2024高级语言程序设计:https://edu.cnblogs.com/campus/fzu/2024C 高级语言程序设计课程第五次个人作业:https://edu.cnblogs.com/campus/fzu/2024C/homework/13298 学号:102400111 姓名:蔡伟仡 8.11.18.11.28.11.38.11.48.11.58.11.68.11.78.11.89.11.19.11.29.11.39.11.49.11…

数据采集与融合技术实践作业三

数据采集与融合技术实践作业三 gitee链接:https://gitee.com/wei-yuxuan6/myproject/tree/master/作业3 作业① Scrapy爬取图片实验要求:指定一个网站,爬取这个网站中的所有的所有图片,例如:中国气象网(http://www.weather.com.cn)。使用scrapy框架分别实现单线程和多线…

固态移动硬盘不识别数据恢复

当固态移动硬盘不被识别时,数据恢复成为一个关键问题。以下是一些有效的数据恢复方法: 一、检查连接与驱动程序 检查连接:首先确认固态移动硬盘的连接是否正确,包括SATA接口、电源连接(如果适用)以及USB线或其他转接线是否损坏或松动。对于外置硬盘,确保连接线和转接器没…

OpenGL编程指南(原书第9版)

百度网盘下载地址: https://pan.baidu.com/s/1ATLvMOcW1jwBXSygOg3xUA 关注微信公众号 回复 1100 获取提取码: