接收异步事件通知

异步事件通知是一种支持应用程序在不独占系统资源的情况下持续监视事件的技术。 异步事件通知具有与其他异步调用相同的安全限制。 可以改为进行半同步调用。 有关详细信息,请参阅调用方法

路由到客户端的异步事件队列可能会变得异常大。 因此,WMI 实现了系统范围的策略,以免内存不足。 当队列增长超过特定大小时,WMI 会减慢事件速度,或者开始从队列中删除事件。

WMI 使用 Win32_WMISetting 类的 LowThresholdOnEventsHighThresholdOnEvents 属性来设置避免内存不足的限制。 最小值指示 WMI 何时应开始减慢事件通知,最大值指示何时开始删除事件。 低阈值和高阈值的默认值为 1000000 (10 MB) 和 2000000 (20 MB)。 此外,可以设置 MaxWaitOnEvents 属性来描述 WMI 在删除事件之前应等待的时间。 MaxWaitOnEvents 的默认值为 2000,即 2 秒。

在 VBScript 中接收异步事件通知

用于接收事件通知的脚本调用本质上与具有相同安全问题的所有异步调用相同。 有关详细信息,请参阅使用 VBScript 进行异步调用

在 VBScript 中接收异步事件通知

  1. 通过调用 WScript.CreateObject 并指定“WbemScripting”的 progid 和 SWbemSink 的对象类型来创建接收器对象。 接收器对象接收通知。

  2. 为要处理的每个事件编写一个子例程。 下表列出了 SWbemSink 事件。

    事件 含义
    OnObjectReady 向接收器报告对象的返回。 使用此调用每次都返回一个对象,直到操作完成。
    OnCompleted 异步调用完成时进行报告。 如果操作不确定,则绝不会发生此事件。
    OnObjectPut 报告异步放置操作的完成。 此事件返回实例或已保存类的对象路径。
    OnProgress 报告正在进行的异步调用的状态。 并非所有提供程序都支持临时进度报告。
    取消 取消与此对象接收器关联的所有未完成的异步操作。

     

以下 VBScript 代码示例以 10 秒的轮询间隔通知进程删除事宜。 在此脚本中,子例程 SINK_OnObjectReady 处理事件发生。 在示例中,接收器对象命名为“Sink”,但可以根据需要命名该对象。

strComputer = "." 
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") 
Set MySink = WScript.CreateObject( _
    "WbemScripting.SWbemSink","SINK_")

objWMIservice.ExecNotificationQueryAsync MySink, _
    "SELECT * FROM __InstanceDeletionEvent" _
    & " WITHIN 10 WHERE TargetInstance ISA 'Win32_Process'"


WScript.Echo "Waiting for events..."

While (True)
    Wscript.Sleep(1000)
Wend

Sub SINK_OnObjectReady(objObject, objAsyncContext)
    Wscript.Echo "__InstanceDeletionEvent event has occurred."
End Sub

Sub SINK_OnCompleted(objObject, objAsyncContext)
    WScript.Echo "Event call complete."
End Sub

使用 C++ 接收异步事件通知

若要执行异步通知,可以创建一个单独的线程,专门用于监视和接收来自 Windows Management Instrumentation (WMI) 的事件。 当该线程收到消息时,线程会通知主应用程序。

通过创建单独的专用线程,便允许主进程在等待事件到达的同时执行其他活动。 异步传递通知可提高性能,但提供的安全性可能低于预期。 在 C++ 中,可以选择使用 IWbemUnsecuredApartment 接口或对安全描述符执行访问检查。 有关详细信息,请参阅设置异步调用的安全性

设置异步事件通知

  1. 在初始化任何异步通知前,请确保 Win32_WMISetting 中已正确设置避免内存不足参数。

  2. 确定想要接收的事件类型。

    WMI 支持内部和外部事件。 内部事件是由 WMI 预定义的事件,而外部事件是由第三方提供程序定义的事件。 有关详细信息,请参阅确定要接收的事件类型

以下过程描述了如何使用 C++ 接收异步事件通知。

使用 C++ 接收异步事件通知

  1. 通过调用 CoInitializeExCoInitializeSecurity 函数来设置应用程序。

    调用 CoInitializeEx 初始化 COM,而 CoInitializeSecurity 向 WMI 授予调用使用者进程的权限。 还可通过 CoInitializeEx 函数编写多线程应用程序,这是异步通知所必需的。 有关详细信息,请参阅维护 WMI 安全性

    本主题中的代码需要以下引用和 #include 语句才能正确编译。

    #define _WIN32_DCOM
    #include <iostream>
    using namespace std;
    #include <wbemidl.h>
    

    以下代码示例说明如何通过调用 CoInitializeExCoInitializeSecurity 来设置临时事件使用者。

    void main(int argc, char **argv)
    {
        HRESULT hr = 0;
        hr = CoInitializeEx (0, COINIT_MULTITHREADED);
        hr = CoInitializeSecurity (NULL, 
           -1, 
           NULL, 
           NULL,   
           RPC_C_AUTHN_LEVEL_NONE, 
           RPC_C_IMP_LEVEL_IMPERSONATE, 
           NULL,
           EOAC_NONE,
           NULL); 
    
        if (FAILED(hr))
        {
           CoUninitialize();
           cout << "Failed to initialize security. Error code = 0x"
               << hex << hr << endl;
           return;
        }
    
    // ...
    }
    
  2. 通过 IWbemObjectSink 接口创建接收器对象。

    WMI 使用 IWbemObjectSink 发送事件通知并报告异步操作或事件通知的状态。

  3. 通过调用 IWbemServices::ExecNotificationQueryAsync 方法注册事件使用者。

    确保 pResponseHandler 参数指向在上一步中创建的接收器对象。

    注册的目的是仅接收所需的通知。 接收多余的通知会浪费处理和交付时间;并没有充分利用 WMI 的筛选功能。

    但是,临时使用者可以接收多种类型的事件。 在此种情况下,临时使用者必须为每种事件类型分别调用 IWbemServices::ExecNotificationQueryAsync。 例如,使用者在创建新进程(实例创建事件或 __InstanceCreationEvent)和更改某些注册表项(注册表事件,例如 RegistryKeyChangeEvent)时,需要获取通知。 因此,使用者会调用 ExecNotificationQueryAsync 来进行实例创建事件注册,然后再调用 ExecNotificationQueryAsync 来进行注册表事件注册。

    如果选择创建注册多个事件的事件使用者,应避免在同一接收器上注册多个类。 请改为对每个类注册事件使用单独的接收器。 拥有专用接收器可简化处理且有助于维护,这样在取消一个注册时就不会影响其他注册。

  4. 在事件使用者中执行任何必要的活动。

    此步骤应包含大部分代码,并包括向用户界面显示事件之类的活动。

  5. 完成后,通过调用 IWbemServices::CancelAsyncCall 事件取消注册临时事件使用者。

    无论对 CancelAsyncCall 的调用是成功还是失败,在对象引用计数达到零之前,请勿删除接收器对象。 有关详细信息,请参阅调用方法