refactor RegistryWatcher

This commit is contained in:
DismissedLight
2024-01-08 21:55:59 +08:00
parent 8710150897
commit 5f9b4a7cb2
3 changed files with 103 additions and 104 deletions

View File

@@ -15,12 +15,4 @@ internal static partial class PInvoke
ppv = (TInterface)o;
return hr;
}
internal static unsafe WIN32_ERROR RegOpenKeyEx(HKEY hKey, string lpSubKey, uint ulOptions, REG_SAM_FLAGS samDesired, out HKEY phkResult)
{
HKEY hKeyResult;
WIN32_ERROR __result = PInvoke.RegOpenKeyEx(hKey, lpSubKey, ulOptions, samDesired, &hKeyResult);
phkResult = hKeyResult;
return __result;
}
}

View File

@@ -18,7 +18,7 @@ internal sealed partial class DynamicHttpProxy : IWebProxy, IDisposable
{
UpdateProxy();
watcher = RegistryWatcher.Create(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections", OnRegistryChanged);
watcher = new (@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections");
watcher.Start();
}

View File

@@ -1,146 +1,153 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.Win32;
using System.Runtime.InteropServices;
using Windows.Win32.Foundation;
using Windows.Win32.System.Registry;
using static Windows.Win32.PInvoke;
namespace Snap.Hutao.Core.Shell;
internal sealed partial class RegistryWatcher : IDisposable
{
private readonly ManualResetEvent eventTerminate = new(false);
private const REG_SAM_FLAGS RegSamFlags =
REG_SAM_FLAGS.KEY_QUERY_VALUE |
REG_SAM_FLAGS.KEY_NOTIFY |
REG_SAM_FLAGS.KEY_READ;
private const REG_NOTIFY_FILTER RegNotifyFilters =
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_NAME |
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_ATTRIBUTES |
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_LAST_SET |
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_SECURITY;
private readonly ManualResetEvent disposeEvent = new(false);
private readonly CancellationTokenSource cancellationTokenSource = new();
private readonly REG_SAM_FLAGS samFlags = REG_SAM_FLAGS.KEY_QUERY_VALUE | REG_SAM_FLAGS.KEY_NOTIFY | REG_SAM_FLAGS.KEY_READ;
private readonly REG_NOTIFY_FILTER notiftFilters = REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_ATTRIBUTES |
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_SECURITY;
private HKEY hKey;
private string subKey = default!;
private readonly HKEY hKey;
private readonly string subKey = default!;
private readonly Action valueChangedCallback;
private readonly object syncRoot = new();
private bool disposed;
public RegistryWatcher(string name, EventHandler eventHandler)
public RegistryWatcher(string keyName, Action valueChangedCallback)
{
ArgumentException.ThrowIfNullOrEmpty(name);
ArgumentException.ThrowIfNullOrEmpty(keyName);
string[] pathArray = keyName.Split('\\');
InitRegistryKey(name);
RegChanged += eventHandler;
}
hKey = pathArray[0] switch
{
nameof(HKEY.HKEY_CLASSES_ROOT) => HKEY.HKEY_CLASSES_ROOT,
nameof(HKEY.HKEY_CURRENT_USER) => HKEY.HKEY_CURRENT_USER,
nameof(HKEY.HKEY_LOCAL_MACHINE) => HKEY.HKEY_LOCAL_MACHINE,
nameof(HKEY.HKEY_USERS) => HKEY.HKEY_USERS,
nameof(HKEY.HKEY_CURRENT_CONFIG) => HKEY.HKEY_CURRENT_CONFIG,
_ => throw new ArgumentException("The registry hive '" + pathArray[0] + "' is not supported", nameof(keyName)),
};
public event EventHandler? RegChanged;
public static RegistryWatcher Create(string name, EventHandler eventHandler)
{
return new RegistryWatcher(name, eventHandler);
subKey = string.Join("\\", pathArray[1..]);
this.valueChangedCallback = valueChangedCallback;
}
public void Start()
{
ObjectDisposedException.ThrowIf(disposed, this);
eventTerminate.Reset();
MonitorCoreAsync(cancellationTokenSource.Token).SafeForget();
WatchAsync(cancellationTokenSource.Token).SafeForget();
}
public void Dispose()
{
eventTerminate.Dispose();
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
disposed = true;
GC.SuppressFinalize(this);
}
private void InitRegistryKey(string name)
{
string[] nameParts = name.Split('\\');
switch (nameParts[0])
// Standard no-reentrancy pattern
if (disposed)
{
case "HKEY_CLASSES_ROOT":
case "HKCR":
hKey = HKEY.HKEY_CLASSES_ROOT;
break;
case "HKEY_CURRENT_USER":
case "HKCU":
hKey = HKEY.HKEY_CURRENT_USER;
break;
case "HKEY_LOCAL_MACHINE":
case "HKLM":
hKey = HKEY.HKEY_LOCAL_MACHINE;
break;
case "HKEY_USERS":
hKey = HKEY.HKEY_USERS;
break;
case "HKEY_CURRENT_CONFIG":
hKey = HKEY.HKEY_CURRENT_CONFIG;
break;
default:
hKey = HKEY.Null;
throw new ArgumentException("The registry hive '" + nameParts[0] + "' is not supported", nameof(name));
return;
}
subKey = string.Join("\\", nameParts, 1, nameParts.Length - 1);
lock (syncRoot)
{
if (disposed)
{
return;
}
// First cancel the outer while loop
cancellationTokenSource.Cancel();
// Then signal the inner while loop to exit
disposeEvent.Set();
// Wait for both loops to exit
disposeEvent.WaitOne();
disposeEvent.Dispose();
cancellationTokenSource.Dispose();
disposed = true;
GC.SuppressFinalize(this);
}
}
private async ValueTask MonitorCoreAsync(CancellationToken token)
[SuppressMessage("", "SH002")]
private static unsafe void UnsafeRegOpenKeyEx(HKEY hKey, string subKey, uint ulOptions, REG_SAM_FLAGS samDesired, out HKEY result)
{
using (PeriodicTimer timer = new(TimeSpan.FromSeconds(1)))
fixed (HKEY* resultPtr = &result)
{
try
HRESULT hResult = HRESULT_FROM_WIN32(RegOpenKeyEx(hKey, subKey, ulOptions, samDesired, resultPtr));
Marshal.ThrowExceptionForHR(hResult);
}
}
[SuppressMessage("", "SH002")]
private static unsafe void UnsafeRegNotifyChangeKeyValue(HKEY hKey, BOOL bWatchSubtree, REG_NOTIFY_FILTER dwNotifyFilter, HANDLE hEvent, BOOL fAsynchronous)
{
HRESULT hRESULT = HRESULT_FROM_WIN32(RegNotifyChangeKeyValue(hKey, bWatchSubtree, dwNotifyFilter, hEvent, fAsynchronous));
Marshal.ThrowExceptionForHR(hRESULT);
}
private async ValueTask WatchAsync(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false))
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
UnsafeRegOpenKeyEx(hKey, subKey, 0, RegSamFlags, out HKEY registryKey);
using (ManualResetEvent notifyEvent = new(false))
{
if (token.IsCancellationRequested)
{
break;
}
WIN32_ERROR result = PInvoke.RegOpenKeyEx(hKey, subKey, 0, samFlags, out HKEY registryKey);
if (result != WIN32_ERROR.ERROR_SUCCESS)
{
throw new Win32Exception((int)result);
}
AutoResetEvent eventNotify = new(false);
HANDLE hEvent = (HANDLE)notifyEvent.SafeWaitHandle.DangerousGetHandle();
try
{
WaitHandle[] waitHandles = [eventNotify, eventTerminate];
while (!eventTerminate.WaitOne(0, true))
// If terminateEvent is signaled, the Dispose method
// has been called and the object is shutting down.
// The outer token has already canceled, so we can
// skip both loops and exit the method.
while (!disposeEvent.WaitOne(0, true))
{
result = PInvoke.RegNotifyChangeKeyValue(registryKey, true, notiftFilters, (HANDLE)eventNotify.SafeWaitHandle.DangerousGetHandle(), true);
if (result != 0)
{
throw new Win32Exception((int)result);
}
UnsafeRegNotifyChangeKeyValue(registryKey, true, RegNotifyFilters, hEvent, true);
if (WaitHandle.WaitAny(waitHandles) == 0)
if (WaitHandle.WaitAny([notifyEvent, disposeEvent]) is 0)
{
RegChanged?.Invoke(this, EventArgs.Empty);
valueChangedCallback();
notifyEvent.Reset();
}
}
}
finally
{
if (registryKey != IntPtr.Zero)
{
PInvoke.RegCloseKey(registryKey);
}
eventNotify.Dispose();
RegCloseKey(registryKey);
}
}
}
catch (OperationCanceledException)
{
}
// Before exiting, signal the Dispose method.
disposeEvent.Reset();
}
catch (OperationCanceledException)
{
}
}
}
}