|
众所周知,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 target="_blank" 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;
- }
- }
复制代码 准备工作完成,接下来我们就可以创建窗口了,如果顺利的话我们只需要new Microsoft.UI.Xaml.Window()就行了,但是很遗憾,经过测试在 UWP 并不能正常初始化这个类,有可能是我使用的方法不太正确,或许以后可能能找到正常使用的办法,不过现在我们只能去手动创建一个 Win32 窗口了。
首先我们需要新创建一个线程,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()来释放保持的线程。
最后把之前的东西组合起来,再加点东西:其中DesktopWindow是用来存放 AppWindow 和 DesktopWindowXamlSource的类,如果不嫌麻烦的话可以包裹成一个和 Microsoft.UI.Xaml.Window一样的东西。
最后附上示例应用:https://github.com/wherewhere/CoreAppUWP/tree/muxc
来源:https://www.cnblogs.com/wherewhere/p/18446822
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|