RegNotifyChangeKeyValue (advapi32)
Last changed: -162.111.235.14

.
Summary
Notifies the caller about changes to the attributes or contents of a specified registry key.

C# Signature:

[DllImport("Advapi32.dll")]
private static extern int RegNotifyChangeKeyValue(
   IntPtr        hKey,
   bool          watchSubtree,
   REG_NOTIFY_CHANGE notifyFilter,
   IntPtr        hEvent,
   bool          asynchronous
   );

[Flags]
public enum REG_NOTIFY_CHANGE : uint
{
   /// <summary>
   /// Notify the caller if a subkey is added or deleted
   /// </summary>
     NAME       = 0x1,
   /// <summary>
   /// Notify the caller of changes to the attributes of the key,
   /// such as the security descriptor information
   /// </summary>
     ATTRIBUTES = 0x2,
   /// <summary>
   /// Notify the caller of changes to a value of the key. This can
   /// include adding or deleting a value, or changing an existing value
   /// </summary>
     LAST_SET   = 0x4,
   /// <summary>
   /// Notify the caller of changes to the security descriptor of the key
   /// </summary>
     SECURITY   = 0x8
}

C++ Signature:

    LONG WINAPI RegNotifyChangeKeyValue(
    __in      HKEY hKey,
    __in      BOOL bWatchSubtree,
    __in      DWORD dwNotifyFilter,
    __in_opt  HANDLE hEvent,
    __in      BOOL fAsynchronous
       );

VB Signature:

Declare Function RegNotifyChangeKeyValue Lib "advapi32.dll" (hKey As IntPtr, _
   watchSubtree As Boolean, dwNotifyFilter As Integer, hEvent As IntPtr,
   fAsynchronous As Boolean) As Integer

Notes:

Starting with the .NET Framework 2.0, the IntPtr parameters could be defined as SafeHandle instead.

Tips & Tricks:

Please add some!

Sample Code:

// RegistryChangeMonitor.cs

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32;

namespace BBooth
{
    #region Delegates
    public delegate void RegistryChangeHandler(object sender, RegistryChangeEventArgs e);
    #endregion

    public class RegistryChangeMonitor : IDisposable
    {
    #region Fields
    private string _registryPath;
    private REG_NOTIFY_CHANGE _filter;
    private Thread _monitorThread;
    private RegistryKey _monitorKey;
    #endregion

    #region Imports
    [DllImport("Advapi32.dll")]
    private static extern int RegNotifyChangeKeyValue(
       IntPtr hKey,
       bool watchSubtree,
       REG_NOTIFY_CHANGE notifyFilter,
       IntPtr hEvent,
       bool asynchronous
       );
    #endregion

    #region Enumerations
    [Flags]
    public enum REG_NOTIFY_CHANGE : uint
    {
        NAME = 0x1,
        ATTRIBUTES = 0x2,
        LAST_SET = 0x4,
        SECURITY = 0x8
    }
    #endregion

    #region Constructors
    public RegistryChangeMonitor(string registryPath) : this(registryPath, REG_NOTIFY_CHANGE.LAST_SET) { ; }
    public RegistryChangeMonitor(string registryPath, REG_NOTIFY_CHANGE filter)
    {
        this._registryPath = registryPath.ToUpper();
        this._filter = filter;
    }
    ~RegistryChangeMonitor()
    {
        this.Dispose(false);
    }
    #endregion

    #region Methods
    private void Dispose(bool disposing)
    {
        if (disposing)
           GC.SuppressFinalize(this);

        this.Stop();
    }
    public void Dispose()
    {
        this.Dispose(true);
    }
    public void Start()
    {
        lock (this)
        {
           if (this._monitorThread == null)
           {
               ThreadStart ts = new ThreadStart(this.MonitorThread);
               this._monitorThread = new Thread(ts);
               this._monitorThread.IsBackground = true;
           }

           if (!this._monitorThread.IsAlive)
           {
               this._monitorThread.Start();
           }
        }
    }
    public void Stop()
    {
        lock (this)
        {
           this.Changed = null;
           this.Error = null;

           if (this._monitorThread != null)
           {
               this._monitorThread = null;
           }

           // The "Close()" will trigger RegNotifyChangeKeyValue if it is still listening
           if (this._monitorKey != null)
           {
               this._monitorKey.Close();
               this._monitorKey = null;
           }
        }
    }
    private void MonitorThread()
    {
        try
        {
        IntPtr ptr = IntPtr.Zero;

        lock (this)
        {
            if (this._registryPath.StartsWith("HKEY_CLASSES_ROOT"))
            this._monitorKey = Registry.ClassesRoot.OpenSubKey(this._registryPath.Substring(18));
            else if (this._registryPath.StartsWith("HKCR"))
            this._monitorKey = Registry.ClassesRoot.OpenSubKey(this._registryPath.Substring(5));
            else if (this._registryPath.StartsWith("HKEY_CURRENT_USER"))
            this._monitorKey = Registry.CurrentUser.OpenSubKey(this._registryPath.Substring(18));
            else if (this._registryPath.StartsWith("HKCU"))
            this._monitorKey = Registry.CurrentUser.OpenSubKey(this._registryPath.Substring(5));
            else if (this._registryPath.StartsWith("HKEY_LOCAL_MACHINE"))
            this._monitorKey = Registry.LocalMachine.OpenSubKey(this._registryPath.Substring(19));
            else if (this._registryPath.StartsWith("HKLM"))
            this._monitorKey = Registry.LocalMachine.OpenSubKey(this._registryPath.Substring(5));
            else if (this._registryPath.StartsWith("HKEY_USERS"))
            this._monitorKey = Registry.Users.OpenSubKey(this._registryPath.Substring(11));
            else if (this._registryPath.StartsWith("HKU"))
            this._monitorKey = Registry.Users.OpenSubKey(this._registryPath.Substring(4));
            else if (this._registryPath.StartsWith("HKEY_CURRENT_CONFIG"))
            this._monitorKey = Registry.CurrentConfig.OpenSubKey(this._registryPath.Substring(20));
            else if (this._registryPath.StartsWith("HKCC"))
            this._monitorKey = Registry.CurrentConfig.OpenSubKey(this._registryPath.Substring(5));

            // Fetch the native handle
            if (this._monitorKey != null)
            {
                object hkey = typeof(RegistryKey).InvokeMember(
                   "hkey",
                   BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic,
                   null,
                   this._monitorKey,
                   null
                   );

                ptr = (IntPtr)typeof(SafeHandle).InvokeMember(
                   "handle",
                   BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic,
                   null,
                   hkey,
                   null);
            }
        }

        if (ptr != IntPtr.Zero)
        {
            while (true)
            {
            // If this._monitorThread is null that probably means Dispose is being called. Don't monitor anymore.
            if ((this._monitorThread == null) || (this._monitorKey == null))
                break;

            // RegNotifyChangeKeyValue blocks until a change occurs.
            int result = RegNotifyChangeKeyValue(ptr, true, this._filter, IntPtr.Zero, false);

            if ((this._monitorThread == null) || (this._monitorKey == null))
                break;

            if (result == 0)
            {
                if (this.Changed != null)
                {
                 RegistryChangeEventArgs e = new RegistryChangeEventArgs(this);
                 this.Changed(this, e);

                 if (e.Stop) break;
                }
            }
            else
            {
                if (this.Error != null)
                {
                Win32Exception ex = new Win32Exception();

                // Unless the exception is thrown, nobody is nice enough to set a good stacktrace for us. Set it ourselves.
                typeof(Exception).InvokeMember(
                "_stackTrace",
                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.SetField,
                null,
                ex,
                new object[] { new StackTrace(true) }
                );

                RegistryChangeEventArgs e = new RegistryChangeEventArgs(this);
                e.Exception = ex;
                this.Error(this, e);
                }

                break;
            }
            }
        }
        }
        catch (Exception ex)
        {
        if (this.Error != null)
        {
            RegistryChangeEventArgs e = new RegistryChangeEventArgs(this);
            e.Exception = ex;
            this.Error(this, e);
        }
        }
        finally
        {
        this.Stop();
        }
    }
    #endregion

    #region Events
    public event RegistryChangeHandler Changed;
    public event RegistryChangeHandler Error;
    #endregion

    #region Properties
    public bool Monitoring
    {
        get
        {
        if (this._monitorThread != null)
            return this._monitorThread.IsAlive;

        return false;
        }
    }
    #endregion
    }
}

// RegistryChangeEventArgs.cs

using System;

namespace BBooth
{
    public class RegistryChangeEventArgs : EventArgs
    {
    #region Fields
    private bool _stop;
    private Exception _exception;
    private RegistryChangeMonitor _monitor;
    #endregion

    #region Constructor
    public RegistryChangeEventArgs(RegistryChangeMonitor monitor)
    {
        this._monitor = monitor;
    }
    #endregion

    #region Properties
    public RegistryChangeMonitor Monitor
    {
        get { return this._monitor; }
    }

    public Exception Exception
    {
        get { return this._exception; }
        set { this._exception = value; }
    }

    public bool Stop
    {
        get { return this._stop; }
        set { this._stop = value; }
    }
    #endregion
    }
}

Alternative Managed API:

Do you know one? Please contribute it!

Documentation