Last active
July 3, 2025 18:08
-
-
Save jnm2/5dcd7c8f9392da62ddcc0b42aca48984 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Win32; | |
using Microsoft.Win32.SafeHandles; | |
using System; | |
using System.ComponentModel; | |
using System.Threading; | |
using Windows.Win32; | |
using Windows.Win32.Foundation; | |
using Windows.Win32.System.Registry; | |
/// <summary> | |
/// <para> | |
/// Raises the <see cref="Changed"/> event on changes to the attributes or contents of a specified registry key. | |
/// </para> | |
/// <para> | |
/// See the documentation for the <see href="https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regnotifychangekeyvalue"><c>RegNotifyChangeKeyValue </c></see> Windows API for details. | |
/// </para> | |
/// </summary> | |
public sealed class RegistryWatcher : IDisposable | |
{ | |
private readonly object lockObject = new(); | |
private bool isDisposed; | |
private AutoResetEvent? changedEvent; | |
private RegisteredWaitHandle? registeredWaitHandle; | |
private EventHandler? changed; | |
private readonly SafeRegistryHandle keyHandle; | |
private readonly bool watchSubtree; | |
/// <param name="registryKey"> | |
/// <para> | |
/// The registry key to watch for changes. | |
/// </para> | |
/// <para> | |
/// <paramref name="registryKey"/> may be disposed after the constructor returns. Its handle is duplicated for change subscription. | |
/// </para> | |
/// </param> | |
/// <param name="watchSubtree"> | |
/// If <see langword="true"/>, the function reports changes in the specified key and its subkeys. If <see langword="false"/>, the function reports changes only in the specified key. | |
/// </param> | |
public RegistryWatcher(RegistryKey registryKey, bool watchSubtree) | |
{ | |
keyHandle = DuplicateHandle(registryKey.Handle); | |
this.watchSubtree = watchSubtree; | |
} | |
private static SafeRegistryHandle DuplicateHandle(SafeRegistryHandle handle) | |
{ | |
var currentProcessHandle = PInvoke.GetCurrentProcess(); | |
HANDLE duplicatedHandle; | |
var addRef = false; | |
try | |
{ | |
handle.DangerousAddRef(ref addRef); | |
unsafe | |
{ | |
if (!PInvoke.DuplicateHandle( | |
hSourceProcessHandle: currentProcessHandle, | |
(HANDLE)handle.DangerousGetHandle(), | |
hTargetProcessHandle: currentProcessHandle, | |
&duplicatedHandle, | |
dwDesiredAccess: 0, | |
bInheritHandle: false, | |
DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
} | |
finally | |
{ | |
if (addRef) handle.DangerousRelease(); | |
} | |
return new SafeRegistryHandle(duplicatedHandle, ownsHandle: true); | |
} | |
public event EventHandler? Changed | |
{ | |
add | |
{ | |
lock (lockObject) | |
{ | |
if (isDisposed) throw new ObjectDisposedException(nameof(RegistryWatcher)); | |
changed += value; | |
if (registeredWaitHandle is null) | |
{ | |
changedEvent = new AutoResetEvent(initialState: false); | |
registeredWaitHandle = ThreadPool.RegisterWaitForSingleObject(changedEvent, OnRegistryChange, state: null, Timeout.InfiniteTimeSpan, executeOnlyOnce: false); | |
SubscribeToNextRegistryChange(); | |
} | |
} | |
} | |
remove | |
{ | |
lock (lockObject) | |
{ | |
changed -= value; | |
} | |
} | |
} | |
private void SubscribeToNextRegistryChange() | |
{ | |
var error = PInvoke.RegNotifyChangeKeyValue( | |
keyHandle, | |
watchSubtree, | |
REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_FILTER.REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_FILTER.REG_NOTIFY_THREAD_AGNOSTIC, | |
changedEvent!.SafeWaitHandle, | |
fAsynchronous: true); | |
if (error != 0) | |
throw new Win32Exception((int)error); | |
} | |
private void OnRegistryChange(object? state, bool timedOut) | |
{ | |
SubscribeToNextRegistryChange(); | |
changed?.Invoke(this, EventArgs.Empty); | |
} | |
public void Dispose() | |
{ | |
lock (lockObject) | |
{ | |
registeredWaitHandle?.Unregister(null); | |
changedEvent?.Dispose(); | |
isDisposed = true; | |
} | |
// This cancels any pending wait. | |
keyHandle.Dispose(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment