SENS:系统事件通知服务 和 WMI 支持灵活而有效的移动网络计算

时间:2022-08-30 15:58:12

SENS:系统事件通知服务 和 WMI 支持灵活而有效的移动网络计算

发布日期: 8/26/2004 | 更新日期: 8/26/2004

Aspi Havewala

本文假设您熟悉 WMI、ATL 和 C++

下载本文的代码: SENS.exe (107 KB)

摘要 联网应用程序必须处理大量连接问题,这些问题的范围涉及到超时、拥塞和不可用等。如果这些应用程序可以检查当前连接状态,并且在断开连接时能够缓存传输,则它们可以变得更为高效。值得庆幸的是,系统事件通知系统 (SENS) 和 Windows 管理规范 (WMI) 都可以发送通知,以使应用程序可以了解网络状态。

在本文中,作者介绍了多个 SENS 接口的用法,包括 ISensNetwork 和 ISensLogon — 它们分别为连接/断开连接和登录/注销触发事件。作者随后说明了如何预订上述各个事件,并且紧接着讨论了何时可以改而使用 WMI 事件。

SENS:系统事件通知服务 和 WMI 支持灵活而有效的移动网络计算

移动计算正在变得越来越流行;与此同时,几乎所有有用的信息都通过网络流入计算机。用户可以将便携式计算机连接到低带宽、高可用性无线网络(如 CDPD 和 Motorola 的 RD-LAP),或者连接到带宽较高、可用性较低的网络(如办公室中的有线插接站或无线局域网)。由于存在诸如开销、缺少可用性和位置等限制性因素,因此移动计算机无法永久性地连接到网络。所以,用于移动 PC 的应用程序必须能够在瞬态网络中正常工作。在本文中,我将讨论两种 Microsoft 技术 — 系统事件通知系统 (SENS) 和 Windows 管理规范 (WMI),这些技术可以确保应用程序在移动情况下正常工作。

可以通过本文下载一个示例,它预订 SENS 通知,并且还注册和处理 WMI 事件(参见本文顶部的链接)。当各个通知发生时,它在其唯一窗口中的列表框中显示相应的通知,并显示时间戳。对于要求您注销的通知,您还可以将该示例作为服务运行。当作为服务运行时,该示例会将所有通知记录到一个名为 Sensometer.log 的文件中。

瞬态网络应用程序

瞬态网络的速度和可靠性决定了移动设备上的应用程序的可感可用性和响应性。让我们考察几个与将在瞬态网络环境中使用的应用程序有关的设计注意事项。

首先,您应该期望网络连接畅通无阻。需要特别提到的是,打开网络连接的程序应该能够发送和接收数据。发送和接收数据时发生的问题的原因可能是网络拥塞或不可用、用户的网卡存在问题或者设备离开有效范围。传统应用程序通常不够健壮,因而无法在发生连接断开、应用程序关闭或要求用户响应的错误消息时继续正常运行。如果应用程序在发送或接收数据时超时,则您可以假设网络连接因为用户离开有效范围而丢失,但您实际上只能进行猜测。等待超时通常意味着您在做出决定之前必须让整个超时值到期。通常,持续的网络拥塞迫使您要求更长的超时,因为您不希望过早地放弃连接。因而,这就意味着应用程序在断定连接丢失之前,必须等待一段时间。

更常见的情况是,每个远程位置都可能在单独的子网上。移动用户需要在游历这些子网的同时维持到网络的持续连接。虚拟专用网络 (VPN) 是一种比较流行的穿越多个子网的方法;移动 IP 是一种普及程度略低的选择。由一些无线 LAN (WLAN) 供应商提供的“跨路由器漫游”功能正在受到越来越多的欢迎,这些供应商包括 Proxim (http://www.proxim.com) 和 Symbol (http://www.symbol.com) 等。

如果网络协议不是以透明方式支持跨子网漫游,则应用程序可能发现设备位于有效范围内并且网络可用,但它的 IP 地址可能在新的子网上无效。换句话说,您可能在无线(链路)层连接,而不是在 TCP/IP(网络)层连接。较新的操作系统如 Windows 2000 和 Windows XP 等会很有用,因为它们能够感知网络媒体或连接是否存在。这一功能可用于通过域主机配置协议 (DHCP) 更新 IP 地址。

瞬态环境中的另一个设计注意事项是设备的电源状态。WLAN PC 卡和调制解调器会消耗大量电源,因此移动用户必须优先考虑电池节电的问题。应用程序可以通过尽可能地对网络活动进行批处理来缓解这一问题,这样设备就可以在不使用时进入低电源消耗状态。

支持瞬态网络的应用程序还需要知道用户何时与其计算机进行交互。如果您的应用程序在用户与计算机进行交互的同时收集数据,则系统将会显得非常缓慢。

要避免这种情况,您只能在确保计算机空闲的情况下执行后台处理。只要用户开始与设备进行交互,就立即停止后台处理。这将需要有某种快速启动和停止后台处理的方法,以及一种保存当前状态以供以后进行处理的方法。

ISensNetwork

正如您可能从其名称中猜到的,SENS 是各种系统事件的事件发布程序。SENS 仅在 Windows 2000 和 Windows XP 中可用(有一个例外,我将稍后予以说明)。SENS 使用 COM+ 事件通知来投递其事件。基于一种松耦合的发布程序-订户模型,COM+ 事件是对 COM 中流行的旧的连接点技术的改进。

三个主要接口负责处理 SENS 所激发的通知:ISensNetwork 负责处理网络连接/断开连接通知;ISensLogon 管理交互性用户通知;最后,ISensOnNow 处理电源状态通知。

ISensNetwork 在网络被连接或断开连接时以及在目标变得可访问时引发事件。这些事件通过 ISensNetwork 接口传递给订户。与 SENS 的其他组件不同,ISensNetwork 通知可在任何 32 位 Windows 平台上使用(前提是您安装了 Microsoft Internet Explorer 5.5 或更高版本)。

与引发的每个事件一起,ISensNetwork 可以提供任何可用的优质连接 (QOC) 信息。QOC 信息包括网络链接的速度。因为移动计算机在任一给定时间都可能连接到不同的网络,从而产生具有不同速度的链接,所以可以使用链接速度信息来最明智地决定传输数据的方式。

例如,如果低速连接可用,则应用程序可能决定在发送信息之前将所有信息进行压缩。在这种情况下,虽然要花费一些时间来压缩和解压缩信息,但总比阻塞低带宽网络要好。另一方面,可以使用高速连接发送未压缩的信息。

让我们更详细地考察一下 ISensNetwork 接口:

HRESULT ISensNetwork::ConnectionMade (BSTR bstrConnection,
ULONG ulType, LPSENS_QOCINFO pQOCInfo);

该方法在建立网络连接时调用。第一个参数命名该连接,例如“LAN Connection”。第二个参数将返回 0 (LAN) 或 1 (WAN)。QOC 数据作为指向第三个和最后一个参数的指针传入。SENS_QOCINFO 结构如下所示:

typedef struct _SENS_QOCINFO
{
  DWORD dwSize;
  DWORD dwFlags;
  DWORD dwOutSpeed;
  DWORD dwInSpeed;
} SENS_QOCINFO;

dwFlags 成员应返回对网络速度的定性描述,即:高、中或低。不过,返回该信息的方式未见说明。成员 dwOutSpeed 和 dwInSpeed 返回传出和传入的带宽(单位是字节/秒)。当没有任何可用的 QOC 信息时,将调用 ISensNetwork 的以下成员:

HRESULT ConnectionMadeNoQCInfo
(BSTR bstrConnection, ULONG ulType);

在试用 ISensNetwork 之后,我发现该接口的工作方式使其几乎无法在无线网络中使用(后面将有更为详细的说明)。幸而,可以使用一个优秀的替代接口,我也将对其进行讨论。

ISensLogon

ISensLogon 概述了用户如何与系统进行交互。ISensLogon 在用户登录或注销、屏幕锁定或解除锁定、屏幕保护程序启动或停止、外壳程序启动时通知订户。

所有 ISensLogon 方法都只向订户提供一个参数:用户的登录名。下面是 ISensLogon 的大量方法的列表:

HRESULT ISensLogon::Logon (BSTR bstrUserName);
HRESULT ISensLogon::Logoff (BSTR bstrUserName);
HRESULT ISensLogon::DisplayLock (BSTR bstrUserName);
HRESULT ISensLogon::DisplayUnLock (BSTR bstrUserName);
HRESULT ISensLogon::StartScreenSaver (BSTR bstrUserName);
HRESULT ISensLogon::StopScreenSaver (BSTR bstrUserName);
HRESULT ISensLogon::StartShell (BSTR bstrUserName);

上面列出的每个通知都在触发它的事件已经发生后发送。例如,StartScreenSaver 方法在系统已经启动屏幕保护程序后调用。如果您希望在事件发生之前将其捕获,则该通知将一点用处也没有。这适用于所有 ISensLogon 通知。

DisplayLock 和 DisplayUnLock 事件每当屏幕显示被用户锁定时发送。用户通常通过按 Ctrl-Alt-Del 按键并随后单击 Lock Computer 按钮来保护显示。然后,可以通过使用用户名和密码重新登录来将显示解除锁定。

ISensOnNow

ISensOnNow 提供了三个与电源状态有关的通知:当计算机切换到 AC 或电池电源时,或者当电池电量不足时。这些通知是通过 ISensOnNow 接口传递的。

HRESULT OnACPower(void);

每当计算机从电池电源切换到 AC 电源时,将调用该方法。如果您的膝上型电脑位于网络中,则它很可能使用 PCMCIA 卡调制解调器或无线 NIC 卡。典型的 WLAN PCMCIA 卡在传输无线信号时一般使用 350mA,而在接收无线信号时一般使用 250mA。一种好方法是在使用电池电源时,尽可能地避免使用 WLAN 卡,以便减小电源消耗。

当计算机使用电池电源时,应用程序可以使用该信息,并且决定在本地缓存要通过网络发送的数据。当计算机切换到 AC 电源时,应用程序就可以发送或接收数据。

无法保证用户将切换到 AC 电源。并且即使该用户确实这样做了,也无法知道计算机是否仍然在无线网络的有效范围内。编写此类优化代码时,要求详细了解客户在移动网络中的移动方式以及他们处理其数据的方式。

当用户切换到电池时,将使用计算机的活动电池中剩余的电池电量的百分比来调用该方法:

HRESULT OnBatteryPower(DWORD dwBatteryLifePercent);

BatteryLow 方法在用户已经从 AC 切换到电池电源后定期调用:

HRESULT BatteryLow (DWORD dwBatteryLifePercent);

但是,我从未能够从 SensOnNow 得到该通知,即使在我的电池电量耗尽时。

接收通知

既然我们已经讨论了 SENS 通知以及如何使用它们来优化移动应用程序,那么我将介绍一下如何设置订户以接收这些事件。

COM+ 订阅可以是持久的,也可以是短暂的。持久订阅将在重新启动后得到维持,而短暂订阅则不会得到维持。我将不会详述 COM+ 发布程序-订户模型的特定方面。相反,我将向您介绍 David Platt 撰写的文章“The COM+ Event Service Eases the Pain of Publishing and Subscribing to Data”(MSJ 1999 年 9 月刊)。

为了预订 SENS 通知,我将设置一个短暂订阅,并且向您演练为 ISensLogon 接口创建对象的步骤。我将使用 ATL,它使我可以在框架许可的范围内尽可能地接近 COM。

第一步是创建一个简单的 ATL 可执行应用程序。在 New Projects 列表中选择 ATL Com AppWizard,并使用 Visual Studio 中的 Project Wizard(参见 1)。

SENS:系统事件通知服务 和 WMI 支持灵活而有效的移动网络计算

1 ATL COM AppWizard

接下来,在该项目中插入一个简单的 ATL 对象。我调用了我自己的 SensLogonSpy(参见 2)。该过程在我的项目中创建了一个名为 CSensLogonSpy 的类。

SENS:系统事件通知服务 和 WMI 支持灵活而有效的移动网络计算

2 SensLogonSpy

从 Visual Studio 中的类查看器中,右键单击 CSensLogonSpy 并选择 Implement Interface 菜单选项。您将看到一个警告对话框,表明 Visual Studio 无法找到该项目的类型库。单击 OK 关闭该对话框并继续。

SENS:系统事件通知服务 和 WMI 支持灵活而有效的移动网络计算

3 浏览类型库

接下来,您将看到一个使您可以浏览可用类型库的对话框(参见 3)。如果您在向类中添加接口之前编译该项目,您将看到的 Implement Interface 对话框的 SENSOMETERLib 选项卡中不会列出任何内容(参见 4)。单击右侧的 Add Typelib 按钮,将显示 Browse Type Libraries 对话框。向下滚动,直到您找到 SENS Events Type Library (1.0)。请注意,该对话框告诉您该类型库是由 C:/Winnt/System/Sens.dll 实现的。请选中 SENS Events Type Library 旁边的框,并单击 OK。

SENS:系统事件通知服务 和 WMI 支持灵活而有效的移动网络计算

4 SENSOMETERLib

SensEvents 选项卡现在将被添加到 Implement Interface 对话框中。前面讨论过的三个接口都将列出来。选中 ISensLogon 接口旁边的复选框,并单击 OK。这将在 CSensLogonSpy 中插入 ISensLogon 接口的方法实现。每个方法都是空的占位符并且返回 E_NOTIMPL。您将需要根据您希望处理每个事件的方式来填写各个方法。

要将该类附加到接口,您将必须向 IDL 文件中添加此处指示的两行代码:

library SENSOMETERLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");
    import "SensEvts.idl"; //add this line
    coclass SensLogonSpy
    {
        [default] interface ISensLogonSpy;
        interface ISensLogon; //add this line
    };
};

必须将 ISensLogon 接口添加到 coclass 中,以表明 CSensLogonSpy 类将实现 ISensLogon 接口。要导入 ISensLogon(实际上还包括任何 SENS 接口)的定义,您还必须导入 SensEvts.idl,它与 Microsoft Platform SDK 一起发行。文件 SensLogonSpy.cpp 和 SensLogonSpy.h(可以通过本文下载)包含实现 ISensLogon 接口所需的所有源代码。

在设置您的接口以后,您就可以注册它以便接收 ISensLogon 事件了。要接收通知,您的接口必须预订 SENS 事件。

预订事件

对 SENS 事件的订阅是通过 IEventSystem 接口添加的。该接口是由 EventSystem 对象 (CLSID_CEventSystem) 实现的,而该对象在 Platform SDK 文件 EventSys.h 中定义。

IEventSystem 的 Store 方法添加了一个订阅。

   HRESULT Store (BSTR ProgID,
      IUnknown *pInterface);

该方法的 ProgID 参数总是被设置为 PROGID_EventSubscription,并且接口指针被设置为 IEventSubscription。该接口在系统类 (CLSID_EventSubscription) 中实现,并且用于封装订户和发布程序之间的关系。EventSubscription 对象也在文件 EventSys.h 中定义。

您需要向 IEventSubscription 提供订阅的说明性名称、发布程序的 GUID、指向订户接口的指针。我将简要说明上述各项。

订阅的说明性名称是一个用于标识订阅的名称;它通过 IEventSubscription 设置:

HRESULT IEventSubscription::put_SubscriptionName
(BSTR bstrSubscriptionName);

您可以通过 put_EventClassID 方法提供将激发事件的发布程序的 GUID:

HRESULT IEventSubscription::put_EventClassID
(BSTR bstrEventClassID);

用于标识类 ID 的 BSTR 是发布程序的 GUID 的字符串表示形式。对于 ISensLogon 通知,它被设置为 SENSGUID_EVENTCLASS_LOGON。该常数在 Platform SDK 的文件 Sens.h 节中定义。

最后,指向订户接口的指针必须被传递给 IEventSubscription。实现 ISensLogon 接口的 coclass 被初始化,并且它的接口指针在调用中传递,如下面的代码所示:

HRESULT IEventSubscription:: put_SubscriberInterface
(IUnknown ifpSubscriberInterface);

IEventSubscription 还有另外一个标识订户接口的方法,值得顺便提一下:

HRESULT IEventSubscription:: put_SubscribeCLSID
(BSTR bstrSubscriberCLSID);

在指定短暂订阅时,总是使用第一个方法。CLSID 必须为 NULL;永远不得进行第二次调用。您还可以通过调用第二个方法并指定 CLSID 来设置持久订阅。在这种情况下,必须将订户接口设置为 NULL。在本文的示例代码中,短暂订阅是使用第一个方法建立的。

这就是所有的步骤。在准备好 IEventSubscription 对象之后,就可以将其传递给前面介绍的 EventSystem 对象的 Store 方法。如果该过程中的所有 HRESULT 都返回成功,则您就设置了一个短暂订阅并且做好开始接收 ISensLogon 通知的准备。

对于 SENS 中提供的其他两个接口,需要遵循完全相同的过程。Sens.h 中定义的其余两个接口的 GUID 常数是 SENSGUID_EVENTCLASS_NETWORK 和 SENSGUID_EVENTCLASS_ONNOW。

我的示例文件 RegisterSensEvents.cpp 和 RegisterSensEvents.h(可通过本文顶部的链接下载)包含用于注册所有 SENS 通知的源代码。

ISensNetwork 存在的问题

遗憾的是,ISensNetwork 的工作方式如此奇特,以至于许多人认为通知根本就不工作。它们的确能够工作;只是它们通常需要很长时间才能激发(最多需要五分钟)。当连接是拨号连接时,这些通知实际上接近于即时,并且非常便于使用。对于 LAN 和 WAN 连接,等待时间太长,以至于无法在程序中使用,因为移动用户在得知其连接断开之前,不会特别耐心地等待那么长时间。

当我测试 ISensNetwork 通知时,我得到了混合的结果。通常,经过五分钟之后,通知仍然尚未激发。事实上,半小时之后,它仍然未能激发。启动 Internet Explorer 5.5(这会导致在用户计算机上安装 ISensNetwork 事件)时,总是会导致通知被激发。

如果 Internet Explorer 正在运行,则我会得到一个 ConnectionLost 通知,但不会得到连续的通知,而且我根本不会得到 ConnectionMade 通知。对于 ConnectionMade,仅仅将 Internet Explorer 打开是不够的。我必须在每次希望得到通知时启动它。无庸讳言,采用变通办法是不可能的。

那么,如果 ISensNetwork 至关重要而又不能工作,还有什么其他选择呢?还有另外一个选择,但它要求仔细研究 Microsoft Windows Device Driver Kit (DDK)。

Windows 管理规范

WBEM 是一个开放式倡仪,它指定了组件如何提供统一的企业管理。WBEM 实际上是一组标准,它指定了如何定义数据(使用公共信息模型 — CIM)、如何对数据进行编码 (xmlCIM) 以及如何传输数据(HTTP 上的 CIM 操作)。将这些支持 Web 的规范结合起来,可以使管理工具能够以标准方式从多种支持 Internet 的设备中请求和获取数据,并将其呈现给用户。

WMI 是 Microsoft 对 WBEM 的实现。设备驱动程序可以使用该服务向用户模式应用程序提供所有种类的数据。WMI 提供了一种发现和获取数据的标准方式。以这种方式公开的数据可以分为三个主要类别:信息、配置和通知。我将重点讨论第三个类别。

该数据到底是如何公开的?驱动程序定义包含数据项的 WMI 数据块。数据块以托管对象格式 (MOF) 定义,并被编译为驱动程序的二进制文件的资源部分。每个 WMI 数据块都由一个 GUID 唯一标识。

当驱动程序初始化时,它的所有数据块都被注册到 WMI。WMI 将已经注册的数据块添加到公共信息模型对象管理器 (CIMOM) 数据库中。用户模式 WMI 客户端可以访问 CIMOM 中的数据块。只有驱动程序和 WMI 客户端才知道各个数据块的格式。WMI 将数据以透明方式从驱动程序传递到客户端。客户端也可以有条件地注册事件。

既然数据格式未知,客户端如何注册特定事件或某个事件的特定条件?答案可以在 WMI 查询语言 (WQL) 中找到。WQL 基于 ANSI SQL。

例如,要在计算机中启用了某个适配器时得到通知,可以使用下面的 WQL 语句:

SELECT * FROM MSNdis_NotifyAdapterArrival

这是一个事件查询。WQL 还支持数据查询和架构查询。数据查询用于检索类实例及其数据,而架构查询用于检索类定义和相关架构。

事件查询可以使用 WHERE 子句进一步简化。使用 WHERE,您可以告诉发布程序在某个类中的某个项取特定值时通知您。考虑一下 MSNdis_StatusLinkSpeedChange,每当网络连接的链接速度更改时,都会传递它。WLAN 网络的一个典型特征是:随着两个同步节点之间的距离增加,数据速率会下降。如果链接速度下降至某个固定速度以下,应用程序可能希望停止传输大型文件。例如,您可以编写一个查询,使其在入站链接速度下降至 10KB 以下时通知您:

SELECT * FROM MSNdis_StatusLinkSpeedChange WHERE Inbound < 10000

您可以使用 OR 和 AND 运算符将多个子句组合起来。您还可以使用 NOT 运算符;要进行比较,您可以使用 =、<、<=、>、>= 和 !=。有关其他选项,请参阅 WQL 文档资料。

筛选查询始终是一个好主意。记住,从发布程序到订户的调用最少要经过系统引入的一级间接寻址。接收 WMI 通知的应用程序可以充当发布程序,并且判断您对您已经注册的某个事件的特定方面不感兴趣,从而避免向订户发送通知。

执行 WQL 查询

向 WMI 提交查询是通过 WMI 服务完成的,这些服务被以编程方式包装在 IWbemServices 接口中。该接口的方法之一使用户可以使用 WQL 查询来注册异步事件通知:

HRESULT IWbemServices::ExecNotificationQueryAsync(
  const BSTR strQueryLanguage,
  const BSTR strQuery,
  long lFlags,
  IWbemContext* pCtx,
  IWbemObjectSink* pResponseHandler);

IWbemObjectSink 是一个一般性接收器接口,可用于接收 WMI 通知。通常情况下,您的接收器将把 IWbemObjectSink 作为它的基类。您将需要包括 DDK include 目录中的 basetsd.h。Visual Studio 6.0 随附的或 Platform SDK 中包含的 basetsd.h 不包含 IWbemObjectSink 的定义。链接器将需要包含 Wbemuuid.lib 以解析该接口。

在下面的代码中,我进行了一个调用,并且告诉它等待一个将注册所有适配器到达事件的 WQL 查询。适配器到达事件被传递给 m_pWMISink 对象:

HRESULT hr = m_ifpWbemServices->ExecNotificationQueryAsync
((_bstr_t) _T("WQL"), bstrQuery, 0, 0, m_pWMISink);
m_pWMISink 对象的类型是 CWMISink,它继承自 IWbemObjectSink:
  class CWMISink : public IWbemObjectSink
  {...};

执行格式错误的查询时将返回值为 0x80041058 的 HRESULT (WBEM_E_UNPARSABLE_QUERY)。执行带有错误类名的查询时将返回值为 0x80041002 的 HRESULT (WBEM_E_NOT_FOUND)。错误代码可以在 WBEMCLI.H 中找到,该文件随附在 DDK 中。如果您执行的查询带有永远不会被满足的条件,则您将不会得到任何错误。WBEM 服务没有办法知道在运行时可能发生什么。

我的示例文件 RegisterNDISEvents.cpp 和 RegisterNDISEvents.h 包含用于注册 NDIS WMI 通知的源代码。文件 WMISink.cpp 和 WMISink.h 实现了接收器接口。

5 列出了您可以从 NDIS 收到的事件。它们列在 Platform SDK 随附的 WMICORE.MOF 文件中。在该文件中,您将发现大量由 NDIS 提供的 WMI 信息。通过查找所有从 WMIEvent 继承的类,您可以找到 NDIS 通知。在向您说明如何阅读和解释该文件之前,我将解释一下什么是 MOF 文件。

托管对象格式

MOF 代表托管对象格式。驱动程序可以公开数据块,以便说明在进行与该数据块关联的 WMI 请求时,将返回哪些信息。驱动程序还可以公开事件块,以便说明当特定事件发生时将发送给订户的数据块。这些块共同构成了驱动程序的 WMI 架构。MOF 是用于描述该架构的语言。

驱动程序架构的 MOF 描述被写入一个文本文件中,该文件随后被使用 MOF 编译器 mofcomp.exe 编译为该驱动程序的资源部分。输出文件具有 .bmf 扩展名,并且可以包含在该驱动程序的资源脚本文件中,如下所示:

MofResource MOFDATA mymofschema.bmf

然后,在其资源部分中包含 MOF 数据的驱动程序将作为 WMI 数据的提供程序注册,方法是:首先调用 IoWMIRegistrationControl,然后处理由 WMI 发送的 IRP_MN_REGINFO。我希望在这里详细说明其工作方式。作为订户,好消息是一旦完成该工作,您就可以*地在任何用户模式应用程序中接收 NDIS 事件。

了解如何脚本化 MOF 文件是非常重要的,因为这使您可以深入研究 WMICORE.MOF 的内容,并且能够了解公开了哪种 WMI 数据。该数据在我可以找到的所有地方都未见说明,因此我将向您提供足够的信息以增长您的才干。

在MOF 文件内部,WMI 数据块由以下类定义描述:

Class qualifiers [,Class qualifiers]
class MOFClass : [BaseClass]
{
[key, read]
string InstanceName;
[read]
Boolean Active;
[Property qualifiers]
};

每个类都可以从一个基类继承。例如,NDIS 通知继承自 WMIEvent 类。类名必须在整个 WMI 命名空间中唯一。允许抽象类具有空体,这样就可以使用它将某个类隔离到特定的命名空间中(通过让该类从该抽象类继承)。WMIEvent 就是抽象类的示例。

在类描述中,类名和继承信息的后面必须跟两个强制项。InstanceName 和 Active 都是必须由 MOF 作者定义的项。驱动程序应该不理会这些项,因为这些项由 WMI 在内部使用。实际上,您可以忽略它们。

作为示例,让我们考察一下 MOF 中的 MSNdis_AdapterArrival 数据块(参见 6)。第一行表示一个提供 WMI 数据的动态 WMI 类。类还可以是静态的,在此情况下,将直接从 WMI 数据库中提取数据块。按照定义,事件必须是动态类。

GUID 字段将一个唯一的 GUID 与数据块相关联。每个数据块都必须具有它自己的唯一 ID,以便 WMI 可以在驱动程序的资源部分的 MOF 区域中识别其定义。区域设置字段包含 0x409(美国英语区域设置),因为数据块可能具有特定于区域设置的信息。

WmiExpense 字段声明了提供该块中的数据所需的 CPU 周期个数。该值可被应用程序用来确定它们在进行某些特定的 WMI 调用时引起的开销。最后,说明字段是一个字符串,用于描述该数据块的用途。

MSNdis_NotifyAdapterArrival 类继承自 WMIEvent,该类被标记为通知。在该类内部,有一个名为 DeviceName 的数据项,它返回与该通知相关的设备的名称。

可以使用其他多个属性限定符来修饰各个数据项。例如,您可以使用一个名为 Values 的限定符来为该项指定一系列可能的数据值。有关详细信息,请参阅 WMI 属性限定符文档资料。

最后,DeviceName 项本身被定义为字符串。如果未指定 MaxLen 属性限定符(就像该示例中一样),则该字符串的长度不固定。MOF 数据项可以是以下值:布尔值;字符串;8、16、32 和 64 位有符号和无符号整数;或日期/时间变量。有关在 MOF 下提供的数据类型的完整补充资料,请在 MSDN(MOF 数据类型)上查找驱动程序定义的 WMI 数据项。

例如,MSNdis_AdapterArrival 通知将 DeviceName 设置为与适配器或网卡相关联的服务名。该服务名是在系统中配置 NIC 时在内部分配的一个 GUID。在我的膝上型电脑上,它是 /Device/ {9EB6E23E-BCD3-4AD4-8CF3-9239EC52DDB2}。 7 显示了注册表中的这一信息。现在,您可以通读各个 NDIS 事件的 MOF 描述,并了解这些数据块的定义方式。

SENS:系统事件通知服务 和 WMI 支持灵活而有效的移动网络计算

7 注册表中的 MOF 信息

通过将可从 SENS 和 NDIS 中获取的通知结合起来,您可以监控许多影响移动计算的条件。借助于这一信息,您的应用程序将成为移动世界中引人注目的模范公民。