【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(®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(@"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 了。
本文來自博客園,作者:where-where,轉載請注明原文鏈接:http://www.rzrgm.cn/wherewhere/p/18447226

浙公網安備 33010602011771號