【UWP】在 UWP 中使用 Windows App SDK

众所周知,WAS (Windows App SDK,俗称 WinUI3)在刚开始是支持 UWP 的,甚至最早只支持 UWP,但是微软在正式版发布前删除了对 UWP 的支持,不过真的删除了吗?初生之鸟2023年10月发现在 VS 调试下无视报错继续运行可以正常在 UWP 加载 WAS。随着 WAS 的开源,WAS 阻止在 UWP 上运行的原因也被找到,至此大家终于找到在 UWP 上使用 WAS 的方法了。

WAS 阻止在 UWP 上运行的方法很简单,就是检查注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WinUI\Xaml\EnableUWPWindow是否为00000001,如果不是就直接报错。

Window_Partial.cpp#L80-L114
// ----------------------------------------------------------------------
//                               IWindow
// ----------------------------------------------------------------------
Window::Window()
{
    // The first window created internally by DXamlCore _must_ be a UWP Window.  DXamlCore
    // requires and controls the lifetime of a hidden UWP Microsoft.UI.Xaml.Window.
    // note that this Window instance will be the 'real' window for UWP instances, but
    // serves as a dummy for all other instances. dummy behavior is deprecated and being removed.
    auto dxamlCore = DXamlCore::GetCurrent();
    Window* window = dxamlCore->GetDummyWindowNoRef();

    if (!window)
    {
        // Do a runtime check to see if UWP should be enabled
        static auto runtimeEnabledFeatureDetector = RuntimeFeatureBehavior::GetRuntimeEnabledFeatureDetector();
        auto UWPWindowEnabled = runtimeEnabledFeatureDetector->IsFeatureEnabled(RuntimeEnabledFeature::EnableUWPWindow);

        // WinUI UWP
        if (!UWPWindowEnabled && DXamlCore::GetCurrent()->GetHandle()->GetInitializationType() != InitializationType::IslandsOnly)
        {
            ::RoOriginateError(
                E_NOT_SUPPORTED,
                wrl_wrappers::HStringReference(
                L"WinUI: Error creating an UWP Window. Creating an UWP window is not allowed."
                ).Get());
            XAML_FAIL_FAST();
        }
        m_spWindowImpl = std::make_shared<UWPWindowImpl>(this);
    }
    else
    {
        m_spWindowImpl = std::make_shared<DesktopWindowImpl>(this);
    }
}
Window_Partial.cpp#L80-L114
{ L"EnableUWPWindow", RuntimeEnabledFeature::EnableUWPWindow, false, 0, 0 }

所以我们只需要修改注册表就行了。

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WinUI\Xaml]
"EnableUWPWindow"=dword:00000001

但是到处修改注册表并不是一个好主意,于是初生之鸟便提出利用Detours来劫持读取注册表的方法:HookCoreAppWinUI

我们将其翻译成 C#,再加一些小修改,便能得出如下内容:

#r "nuget:Detours.Win32Metadata"
#r "nuget:Microsoft.Windows.CsWin32"

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Registry;
using Detours = Microsoft.Detours.PInvoke;

/// <summary>
/// Represents a hook for getting the value of the <c>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WinUI\Xaml\EnableUWPWindow</c> registry key always returning <see langword="00000001"/>.
/// </summary>
public partial class HookRegistry : 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 dictionary that maps the <see cref="HKEY"/> to a value that indicates whether the key is a real key.
    /// </summary>
    private static readonly Dictionary<HKEY, bool> xamlKeyMap = [];

    /// <summary>
    /// The object used to synchronize access to the <see cref="xamlKeyMap"/> dictionary.
    /// </summary>
    private static readonly object locker = new();

    /// <remarks>The original <see cref="PInvoke.RegOpenKeyEx(HKEY, PCWSTR, uint, REG_SAM_FLAGS, HKEY*)"/> function.</remarks>
    /// <inheritdoc cref="PInvoke.RegOpenKeyEx(HKEY, PCWSTR, uint, REG_SAM_FLAGS, HKEY*)"/>
    private static unsafe delegate* unmanaged[Stdcall]<HKEY, PCWSTR, uint, REG_SAM_FLAGS, HKEY*, WIN32_ERROR> RegOpenKeyExW;

    /// <remarks>The original <see cref="PInvoke.RegCloseKey(HKEY)"/> function.</remarks>
    /// <inheritdoc cref="PInvoke.RegCloseKey(HKEY)"/>
    private static unsafe delegate* unmanaged[Stdcall]<HKEY, WIN32_ERROR> RegCloseKey;

    /// <remarks>The original <see cref="PInvoke.RegQueryValueEx(HKEY, PCWSTR, uint*, REG_VALUE_TYPE*, byte*, uint*)"/> function.</remarks>
    /// <inheritdoc cref="PInvoke.RegQueryValueEx(HKEY, PCWSTR, uint*, REG_VALUE_TYPE*, byte*, uint*)"/>
    private static unsafe delegate* unmanaged[Stdcall]<HKEY, PCWSTR, uint*, REG_VALUE_TYPE*, byte*, uint*, WIN32_ERROR> RegQueryValueExW;

    /// <summary>
    /// Initializes a new instance of the <see cref="HookRegistry"/> class.
    /// </summary>
    public HookRegistry()
    {
        refCount++;
        StartHook();
    }

    /// <summary>
    /// Finalizes this instance of the <see cref="HookRegistry"/> class.
    /// </summary>
    ~HookRegistry()
    {
        Dispose();
    }

    /// <summary>
    /// Gets the value that indicates whether the hook is active.
    /// </summary>
    public static bool IsHooked { get; private set; }

    /// <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("ADVAPI32.dll");
            if (!library.IsInvalid
                && NativeLibrary.TryGetExport(library.DangerousGetHandle(), "RegOpenKeyExW", out nint regOpenKeyExW)
                && NativeLibrary.TryGetExport(library.DangerousGetHandle(), nameof(PInvoke.RegCloseKey), out nint regCloseKey)
                && NativeLibrary.TryGetExport(library.DangerousGetHandle(), "RegQueryValueExW", out nint regQueryValueExW))
            {
                void* regOpenKeyExWPtr = (void*)regOpenKeyExW;
                void* regCloseKeyPtr = (void*)regCloseKey;
                void* regQueryValueExWPtr = (void*)regQueryValueExW;

                delegate* unmanaged[Stdcall]<HKEY, PCWSTR, uint, REG_SAM_FLAGS, HKEY*, WIN32_ERROR> overrideRegOpenKeyExW = &OverrideRegOpenKeyExW;
                delegate* unmanaged[Stdcall]<HKEY, WIN32_ERROR> overrideRegCloseKey = &OverrideRegCloseKey;
                delegate* unmanaged[Stdcall]<HKEY, PCWSTR, uint*, REG_VALUE_TYPE*, byte*, uint*, WIN32_ERROR> overrideRegQueryValueExW = &OverrideRegQueryValueExW;

                _ = Detours.DetourRestoreAfterWith();

                _ = Detours.DetourTransactionBegin();
                _ = Detours.DetourUpdateThread(PInvoke.GetCurrentThread());
                _ = Detours.DetourAttach(ref regOpenKeyExWPtr, overrideRegOpenKeyExW);
                _ = Detours.DetourAttach(ref regCloseKeyPtr, overrideRegCloseKey);
                _ = Detours.DetourAttach(ref regQueryValueExWPtr, overrideRegQueryValueExW);
                _ = Detours.DetourTransactionCommit();

                RegOpenKeyExW = (delegate* unmanaged[Stdcall]<HKEY, PCWSTR, uint, REG_SAM_FLAGS, HKEY*, WIN32_ERROR>)regOpenKeyExWPtr;
                RegCloseKey = (delegate* unmanaged[Stdcall]<HKEY, WIN32_ERROR>)regCloseKeyPtr;
                RegQueryValueExW = (delegate* unmanaged[Stdcall]<HKEY, PCWSTR, uint*, REG_VALUE_TYPE*, byte*, uint*, WIN32_ERROR>)regQueryValueExWPtr;

                IsHooked = true;
            }
        }
    }

    /// <summary>
    /// Ends the hook for the <see cref="PInvoke.AppPolicyGetWindowingModel(HANDLE, AppPolicyWindowingModel*)"/> function.
    /// </summary>
    public static unsafe void EndHook()
    {
        if (--refCount == 0 && IsHooked)
        {
            void* regOpenKeyExWPtr = RegOpenKeyExW;
            void* regCloseKeyPtr = RegCloseKey;
            void* regQueryValueExWPtr = RegQueryValueExW;

            delegate* unmanaged[Stdcall]<HKEY, PCWSTR, uint, REG_SAM_FLAGS, HKEY*, WIN32_ERROR> overrideRegOpenKeyExW = &OverrideRegOpenKeyExW;
            delegate* unmanaged[Stdcall]<HKEY, WIN32_ERROR> overrideRegCloseKey = &OverrideRegCloseKey;
            delegate* unmanaged[Stdcall]<HKEY, PCWSTR, uint*, REG_VALUE_TYPE*, byte*, uint*, WIN32_ERROR> overrideRegQueryValueExW = &OverrideRegQueryValueExW;

            _ = Detours.DetourTransactionBegin();
            _ = Detours.DetourUpdateThread(PInvoke.GetCurrentThread());
            _ = Detours.DetourDetach(&regOpenKeyExWPtr, overrideRegOpenKeyExW);
            _ = Detours.DetourDetach(&regCloseKeyPtr, overrideRegCloseKey);
            _ = Detours.DetourDetach(&regQueryValueExWPtr, overrideRegQueryValueExW);
            _ = Detours.DetourTransactionCommit();

            RegOpenKeyExW = null;
            RegCloseKey = null;
            RegQueryValueExW = null;

            IsHooked = false;
        }
    }

    /// <remarks>The overridden <see cref="PInvoke.RegOpenKeyEx(HKEY, PCWSTR, uint, REG_SAM_FLAGS, HKEY*)"/> function.</remarks>
    /// <inheritdoc cref="PInvoke.RegOpenKeyEx(HKEY, PCWSTR, uint, REG_SAM_FLAGS, HKEY*)"/>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
    private static unsafe WIN32_ERROR OverrideRegOpenKeyExW(HKEY hKey, PCWSTR lpSubKey, uint ulOptions, REG_SAM_FLAGS samDesired, HKEY* phkResult)
    {
        WIN32_ERROR result = RegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, phkResult);
        if (hKey == HKEY.HKEY_LOCAL_MACHINE && lpSubKey.ToString().Equals(@"Software\Microsoft\WinUI\Xaml", StringComparison.OrdinalIgnoreCase))
        {
            if (result == WIN32_ERROR.ERROR_FILE_NOT_FOUND)
            {
                HKEY key = new(HANDLE.INVALID_HANDLE_VALUE);
                xamlKeyMap[key] = false;
                *phkResult = key;
                result = WIN32_ERROR.ERROR_SUCCESS;
            }
            else if (result == WIN32_ERROR.ERROR_SUCCESS)
            {
                xamlKeyMap[*phkResult] = true;
            }
        }
        return result;
    }

    /// <remarks>The overridden <see cref="PInvoke.RegCloseKey(HKEY)"/> function.</remarks>
    /// <inheritdoc cref="PInvoke.RegCloseKey(HKEY)"/>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
    private static unsafe WIN32_ERROR OverrideRegCloseKey(HKEY hKey)
    {
        bool isXamlKey;
        lock (locker)
        {
            if (isXamlKey = xamlKeyMap.TryGetValue(hKey, out bool isRealKey))
            {
                xamlKeyMap.Remove(hKey);
            }
            return isXamlKey
                ? isRealKey
                    ? RegCloseKey(hKey) // real key
                    : WIN32_ERROR.ERROR_SUCCESS // simulated key
                : hKey == HANDLE.INVALID_HANDLE_VALUE
                    ? WIN32_ERROR.ERROR_INVALID_HANDLE
                    : RegCloseKey(hKey);
        }
    }

    /// <remarks>The overridden <see cref="PInvoke.RegQueryValueEx(HKEY, PCWSTR, uint*, REG_VALUE_TYPE*, byte*, uint*)"/> function.</remarks>
    /// <inheritdoc cref="PInvoke.RegQueryValueEx(HKEY, PCWSTR, uint*, REG_VALUE_TYPE*, byte*, uint*)"/>
    [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
    private static unsafe WIN32_ERROR OverrideRegQueryValueExW(HKEY hKey, PCWSTR lpValueName, [Optional] uint* lpReserved, [Optional] REG_VALUE_TYPE* lpType, [Optional] byte* lpData, [Optional] uint* lpcbData)
    {
        if (lpValueName.Value != default && lpValueName.ToString().Equals("EnableUWPWindow", StringComparison.OrdinalIgnoreCase))
        {
            lock (locker)
            {
                if (xamlKeyMap.TryGetValue(hKey, out bool isRealKey))
                {
                    WIN32_ERROR result;
                    if (isRealKey)
                    {
                        // real key
                        result = RegQueryValueExW(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
                        if (result == WIN32_ERROR.ERROR_SUCCESS && lpData != default)
                        {
                            *lpData = 1;
                        }
                        else if (result == WIN32_ERROR.ERROR_FILE_NOT_FOUND)
                        {
                            if (lpData == default && lpcbData != default)
                            {
                                *lpcbData = sizeof(int);
                                result = WIN32_ERROR.ERROR_SUCCESS;
                            }
                            else if (lpData != default && lpcbData != default)
                            {
                                if (*lpcbData >= sizeof(int))
                                {
                                    *lpData = 1;
                                    result = WIN32_ERROR.ERROR_SUCCESS;
                                }
                                else
                                {
                                    result = WIN32_ERROR.ERROR_MORE_DATA;
                                }
                            }
                        }
                    }
                    else
                    {
                        // simulated key
                        result = WIN32_ERROR.ERROR_FILE_NOT_FOUND;
                        if (lpData == default && lpcbData != default)
                        {
                            *lpcbData = sizeof(int);
                            result = WIN32_ERROR.ERROR_SUCCESS;
                        }
                        else if (lpData != default && lpcbData != default)
                        {
                            if (*lpcbData >= sizeof(int))
                            {
                                *lpData = 1;
                                result = WIN32_ERROR.ERROR_SUCCESS;
                            }
                            else
                            {
                                result = WIN32_ERROR.ERROR_MORE_DATA;
                            }
                        }
                    }
                    return result;
                }
            }
        }
        return RegQueryValueExW(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
    }

    /// <inheritdoc/>
    public void Dispose()
    {
        if (!disposed && IsHooked)
        {
            EndHook();
        }
        GC.SuppressFinalize(this);
        disposed = true;
    }
}

随后我们只需要在入口点创建App时进行劫持就行了。

private static bool IsSupportCoreWindow
{
    get
    {
        try
        {
            RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\WinUI\Xaml");
            return registryKey?.GetValue("EnableUWPWindow") is > 0;
        }
        catch
        {
            return false;
        }
    }
}

private static void Main()
{
    ComWrappersSupport.InitializeComWrappers();
    HookRegistry hookRegistry = null;
    try
    {
        if (!IsSupportCoreWindow)
        {
            hookRegistry = new HookRegistry();
        }
        XamlCheckProcessRequirements();
        Application.Start(p =>
        {
            DispatcherQueueSynchronizationContext context = new(DispatcherQueue.GetForCurrentThread());
            SynchronizationContext.SetSynchronizationContext(context);
            _ = new App();
        });
    }
    finally
    {
        hookRegistry?.Dispose();
    }
}

当然想要自定义入口函数,我们需要在csproj加上定义。

<DefineConstants>$(DefineConstants);DISABLE_XAML_GENERATED_MAIN</DefineConstants>

同时还要记得在清单中明确入口点。

<?xml version="1.0" encoding="utf-8"?>
<Package ...>
  ...
  <Applications>
    <Application ...
      EntryPoint="明确的入口点">
      ...
    </Application>
  </Applications>
  ...
</Package>

随后我们就可以正常的使用 UWP/WAS 了。

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

posted @ 2024-10-18 15:49  where-where  阅读(0)  评论(0编辑  收藏  举报