65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .NET 中使用强类型 WMI 类实现异步注册表通知

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (17投票s)

2008年11月2日

CPOL

6分钟阅读

viewsIcon

138066

downloadIcon

4612

如何使用 WMI 接收有关注册表更改的异步事件。

Sample Image - maximum width is 600 pixels

目录

引言

您是否希望您的应用程序在 Windows 注册表发生更改时收到通知?您是否需要监视注册表中的特定值并在其更改/删除/重命名时收到通知?如果是,您可以使用本文中介绍的组件。

背景

本文中的组件使用 WMI 事件来获取系统注册表更改时的通知,因此需要对 WMI 和 WMI 中的事件有基本的了解。

组件工作原理

WMI 类

注册表提供程序为系统注册表中的事件提供四个事件类。它们都位于 root\DEFAULT 命名空间中。WMI 中的注册表事件类有:RegistryEventRegistryTreeChangeEventRegistryKeyChangeEventRegistryValueChangeEvent。以下是这些类的简要说明:RegistryEvent 是注册表更改的抽象基类。RegistryTreeChangeEvent 是一个监视键层次结构更改的类。RegistryKeyChangeEvent 监视单个键的更改。RegistryValueChangeEvent 监视单个值的更改。所有这些类都有一个名为 Hive 的属性,用于标识要监视更改的键层次结构。这些类不支持 HKEY_CLASSES_ROOTHKEY_CURRENT_USER,并且无法检测到更改。

在 .NET 中处理 WMI 事件

为了在 .NET 中处理 WMI 事件,我们可以使用 System.Management 命名空间中的类。例如,这是 WMI Code Creator 生成的一段代码

using System;
using System.Management;
using System.Windows.Forms;

namespace WMISample
{
  public class WMIReceiveEvent
  {
    public static void Main()
    {
      try
      {
        //Construct the query. Keypath specifies the key in the registry to watch.
        //Note the KeyPath should be must have backslashes escaped. Otherwise 
        //you will get ManagementException.
        WqlEventQuery query = new WqlEventQuery(
                  "SELECT * FROM RegistryKeyChangeEvent WHERE " +
                  "Hive = 'HKEY_LOCAL_MACHINE'" +
                 @"AND KeyPath = 'SOFTWARE\\Microsoft\\.NETFramework'");

         ManagementEventWatcher watcher = new ManagementEventWatcher(query);
         Console.WriteLine("Waiting for an event...");

         ManagementBaseObject eventObj = watcher.WaitForNextEvent();

         Console.WriteLine("{0} event occurred.", eventObj["__CLASS"]);

         // Cancel the event subscription
         watcher.Stop();
         return;
      }
      catch(ManagementException err)
      {
          MessageBox.Show("An error occurred while trying to receive an event: " + 
        err.Message);
      }
    }
  }
}

上面的代码有效,但有两个主要缺点

  • 事件同步接收
  • 使用了弱类型 WMI 类

变为异步

为了异步接收事件,您应该订阅 ManagementEventWatcher 类的 EventArrived 事件,而不是调用会阻塞线程的 WaitForNextEvent 方法。订阅事件后,您应该调用 ManagementEventWatcher 类的 Start 方法。调用 Stop 方法将停止监听事件。当新的 WMI 事件到达时,事件会触发。有关事件的数据可以通过 EventArrivedEventArgs 类的 NewEvent 属性检索。以下是它的工作原理

using System;
using System.Management;
using System.Windows.Forms;

namespace WMISample
{
  public class WMIReceiveEvent
  {
    public WMIReceiveEvent()
    {
      try
      {
        //Construct the query. Keypath specifies the key in the registry to watch.
        //Note the KeyPath should be must have backslashes escaped. Otherwise you 
        //will get ManagementException.
        WqlEventQuery query = new WqlEventQuery(
                "SELECT * FROM RegistryKeyChangeEvent WHERE " +
               "Hive = 'HKEY_LOCAL_MACHINE'" +
              @"AND KeyPath = 'SOFTWARE\\Microsoft\\.NETFramework'");

        ManagementEventWatcher watcher = new ManagementEventWatcher(query);
        Console.WriteLine("Waiting for an event...");

        watcher.EventArrived += new EventArrivedEventHandler(HandleEvent);

        // Start listening for events
        watcher.Start();

        // Do something while waiting for events
        System.Threading.Thread.Sleep(10000);

        // Stop listening for events
        watcher.Stop();
        return;
      }
      catch(ManagementException err)
      {
        MessageBox.Show("An error occurred while trying to receive an event: " + 
        err.Message);
      }
    }
        
    private void HandleEvent(object sender,
            EventArrivedEventArgs e)
    {
      Console.WriteLine("RegistryKeyChangeEvent event occurred.");
    }

    public static void Main()
    {
      WMIReceiveEvent receiveEvent = new WMIReceiveEvent();
      return;
    }
  }
}

从弱类型类到强类型 WMI 类

强类型 WMI 类可以通过使用 .NET Framework SDK 中包含的 Mgmtclassgen.exe 工具生成。WMI 类中的每个属性和每个方法在生成的类中都有一个等效的属性和方法。这些是由该工具生成的类:RegistryEventRegistryKeyChangeEventRegistryTreeChangeEventRegistryValueChangeEvent。它们可以在 WMI classes 文件夹中找到。

组件

为了在注册表更改时收到通知,您无需自己创建查询并订阅事件。您只需将要监视的 HiveKeyPath 传递给本文中介绍的组件。该组件包含一个抽象类 RegistryChangeBase,它继承自 ManagementEventWatcher。有三个类继承自 RegistryChangeBaseRegistryKeyChangeRegistryTreeChangeRegistryValueChange。所有这些类都提供一个事件,当 WMI 检测到相应事件时会触发该事件。以下是它的工作原理

public class RegistryKeyChange : RegistryChangeBase
{
  public event EventHandler<RegistryKeyChangedEventArgs> RegistryKeyChanged;

  //Template for the query
  private const string queryString = 
    "SELECT * FROM RegistryKeyChangeEvent WHERE Hive = '{0}' AND ({1})";

  public RegistryKeyChange(string Hive, string KeyPath)
      : this(Hive, new List<string>(new string[] { KeyPath }))
  { }

  //Base class constructor does basic validation of the parameters passed.
  public RegistryKeyChange(string Hive, List<string> KeyPathCollection)
      : base(Hive, KeyPathCollection)
  {
    this.Query.QueryString = BuildQueryString(Hive, KeyPathCollection);

    this.EventArrived += new EventArrivedEventHandler(RegistryKeyChange_EventArrived);
  }

  private void RegistryKeyChange_EventArrived(object sender, EventArrivedEventArgs e)
  {
    //Retrieve data about the event and create instance of strongly typed WMI class.
    //Then raise the event.
    RegistryKeyChangeEvent RegValueChange = new RegistryKeyChangeEvent(e.NewEvent);

    OnRegistryKeyChanged(RegValueChange);
  }

  protected virtual void OnRegistryKeyChanged(RegistryKeyChangeEvent RegValueChange)
  {
    if (RegistryKeyChanged != null)
    {
      RegistryKeyChanged(this, new RegistryKeyChangedEventArgs(RegValueChange));
    }
  }
}

如您从上面的代码中可以看到,类的构造函数调用一个方法来构建查询字符串并设置 EventArrived 事件的事件处理程序。在事件处理程序中,它使用事件数据创建 RegistryKeyChangeEvent 类的新实例,该类是 WMI 类的强类型包装器。之后,如果有任何订阅者,它会触发事件。继承自 RegistryChangeBase 的其他类也以相同的方式设计。

创建合适的 WHERE 子句

当构建用于接收 WMI 事件的查询时,where 子句必须包含指定事件类中每个属性的值。此外,系统注册表提供程序必须能够为每个属性构建一个可能的取值列表。以下是一些示例

SELECT * FROM RegistryTreeChangeEvent 
         WHERE Hive = 'HKEY_LOCAL_MACHINE' AND Rootpath = 'Software'

SELECT * FROM RegistryTreeChangeEvent WHERE (hive = 'hkey_local_machine' 
         AND rootpath = 'software') OR 
         (hive = 'hkey_current_user' AND rootpath = 'console')

SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_LOCAL_MACHINE' AND 
         KeyPath = 'SOFTWARE\\MICROSOFT\\WBEM\\CIMOM' AND 
         (ValueName = 'Backup Interval Threshold' OR ValueName = 'Logging')

这显示了一个不起作用的查询

SELECT * FROM RegistryTreeChangeEvent 
         WHERE hive = hkey_local_machine' OR rootpath ='software'

您可以在此处找到详细信息:为注册表提供程序创建合适的 WHERE 子句

RegistryChangeBase 类的构造函数确保在构建查询时满足这些条件。

使组件在 XP 上运行

当我最初发布这篇文章时,组件在 Vista 上可以运行但在 XP 上不行。这真的很奇怪,因为通常情况是在 Vista 上出问题而在 XP 上运行,而不是反过来。在他的评论中,Uros Calakovic 发布了如何在 XP 中使组件工作。我唯一更改的是在 RegistryChangeBase 类的构造函数中添加了以下行

this.Scope.Path.NamespacePath = @"root\default";

Using the Code

使用这些类应该非常简单。只需根据您需要接收的通知类型选择所需的类,创建实例,订阅 RegistryXXXChanged 事件,然后调用 Start 方法开始接收通知。以下是一个示例

Subscribe()
{
  RegistryKeyChange keychange = 
    new RegistryKeyChange("HKEY_LOCAL_MACHINE", @"SOFTWARE\\Microsoft");
  keychange.RegistryKeyChanged += 
    new EventHandler<RegistryKeyChangedEventArgs>(keychange_RegistryKeyChanged);
  keychange.Start();
}

void keychange_RegistryKeyChanged(object sender, RegistryKeyChangedEventArgs e)
{
  lsbDisplay.Invoke((MethodInvoker)(() =>

  {
    lsbDisplay.Items.Add(string.Format("RegistryKeyChangeEvent detected at {0} 
                                        Event data: Hive: {1} KeyPath: {2}",
             DateTime.FromFileTime((long)e.RegistryKeyChangeData.TIME_CREATED).ToString(),
             e.RegistryKeyChangeData.Hive,
             e.RegistryKeyChangeData.KeyPath));
  }));
}

由于事件是异步触发的,因此您无法直接访问由其他线程创建的控件。请注意,事件处理程序的参数是强类型 WMI 类的实例。另请注意使用 TIME_CREATED 属性检索事件生成的时间。要停止接收通知,只需调用 Stop 方法。

接受 List<String> 作为参数之一的构造函数可用于构建如下查询

SELECT * FROM RegistryKeyChangeEvent WHERE 
   hive = 'hkey_local_machine' AND (keypath = 'software' OR keypath = 'console')

RegistryValueChange 类允许构建如下查询

SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_LOCAL_MACHINE' AND 
       KeyPath = 'SOFTWARE\\MICROSOFT\\WBEM\\CIMOM' AND 
       (ValueName = 'Backup Interval Threshold' OR ValueName = 'Logging')

NSFAQ(不那么常见的问题)

这里有一些还没有人问过,但我怀疑他们会问的问题。

  • 是否可以找出哪个应用程序触发了注册表事件?
  • 此组件不支持。

  • 是否可以取消事件?
  • 此组件不支持。

  • 当注册表更改时,是否有其他方法可以收到通知?
  • 是的,有。请看这篇文章:RegistryMonitor - RegNotifyChangeKeyValue 的 .NET 包装类

  • 我需要在 HKEY_CURRENT_USER 配置单元发生更改时收到通知,但此组件不支持。我该怎么办?
  • 阅读上一个问题的答案。

  • 支持哪些平台?
  • 我已经在 64 位 Vista Ultimate SP1 和 XP 上测试了此组件(参见历史记录)。它也应该在存在 WMI 的其他平台上运行。

  • 当我重命名注册表中的一个键时,我会收到两个事件。为什么?
  • 当您重命名一个键时,它会被删除并创建一个新名称的键。

参考文献

历史

  • 2008 年 11 月 2 日 - 初始发布。
  • 2008 年 11 月 14 日 - 版本 1.1
    • 现在也可以在 XP 上运行。感谢 Uros Calakovic - Urke 在评论中发布了相关内容
© . All rights reserved.