- A+
所属分类:.NET技术
众所周知,WAS (Windows App SDK,俗称 WinUI3)在刚开始是支持 UWP 的,甚至最早只支持 UWP,但是微软在正式版发布前删除了对 UWP 的支持,不过真的删除了吗?初生之鸟在2023年10月发现在 VS 调试下无视报错继续运行可以正常在 UWP 加载 WAS。随着 WAS 的开源,WAS 阻止在 UWP 上运行的原因也被找到,至此大家终于找到在 UWP 上使用 WAS 的方法了。
WAS 阻止在 UWP 上运行的方法很简单,就是检查注册表HKEY_LOCAL_MACHINESOFTWAREMicrosoftWinUIXamlEnableUWPWindow
是否为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_MACHINESOFTWAREMicrosoftWinUIXaml] "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_MACHINESOFTWAREMicrosoftWinUIXamlEnableUWPWindow</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(®OpenKeyExWPtr, overrideRegOpenKeyExW); _ = Detours.DetourDetach(®CloseKeyPtr, overrideRegCloseKey); _ = Detours.DetourDetach(®QueryValueExWPtr, 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(@"SoftwareMicrosoftWinUIXaml", 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(@"SOFTWAREMicrosoftWinUIXaml"); 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 了。