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

COM+订阅查看器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (10投票s)

2001年6月13日

5分钟阅读

viewsIcon

103098

downloadIcon

1160

本示例演示了如何使用 C# 检索在事件系统存储中注册的订阅。

引言

发布/订阅松耦合事件 (LCE) 系统通知是 COM+ 服务的一部分。该服务基于事件类和位于事件系统中的订阅的元数据。在运行时,发布者和事件系统会使用元数据来调用订阅者的事件方法。订阅(与事件类的逻辑连接)可以通过编程方式创建,也可以通过组件服务管理控制台 (MMC) 以管理方式创建。请注意,通过编程方式创建的订阅在 MMC 中是不可见的。MMC 中没有查看它们的特性。本文通过一个简单的示例,介绍了如何使用 C# 语言从事件系统中检索订阅。

设计

可以使用以下设计技术从事件系统存储中检索订阅:

  • 基于用户请求的轮询机制
  • 基于其更改的事件驱动机制

下图显示了这两种设计模式

事件系统内置了一个发布者,用于通知订阅者有关更改,例如 ChangedSubscription, ChangedEventClass 和 ChangedPublisher。要接收此通知,订阅者需要创建一个适当的订阅并将其注册到事件系统。可以通过其接口 IEventSystem 合约访问事件系统(属性、对象集合等)。

上述设计中的订阅者具有以下任务:

  • 创建和注册/注销订阅以接收事件系统中更改的通知
  • EventObjectCollection 中检索所有订阅的属性,并将它们格式化为 XML 字符串
  • EventObjectCollection 对象中删除指定的订阅。
  • 使用事件/委托设计模式将从事件系统发布者接收的事件调用传递给用户端

用户端是一个简单的 Windows 窗体,其中包含一个树形视图控件,用于以异步方式显示订阅的属性。有两个按钮;第一个按钮用于刷新树形视图节点,另一个按钮用于从事件系统中删除选定的订阅。

设计中有两种场景。第一种场景是通过请求检索订阅 - 按下 Refresh 按钮(这是轮询功能)。另一种场景是基于事件系统通知触发刷新。此通知将以与第一种场景相同的方式刷新树形视图节点。请注意,活动发布者和订阅者是紧密耦合的,并且通知过程会序列化到所有订阅者,因此用户窗体以异步和隔离的方式处理通知。

实现

上述设计的实现分为两部分:订阅者和用户界面。这两个组件都位于托管代码中。在以下代码片段中,我将描述其实现的一些重要部分。

事件系统

访问事件系统(打包到组件 es.dll 中的非托管代码)基于 COM 互操作功能,这是 .Net Framework 的一个组成部分。订阅者需要将其所在的程序集导入事件系统的类型库 EventSystemLib。此元数据允许使用早期绑定的反射模式设计完全透明地访问非托管代码。以下代码片段显示了与事件系统的契约

  using EventSystemLib;    // created by tlbimp es.dll

  [ComImport, Guid("4E14FBA2-2E22-11D1-9964-00C04FBBB345")]
  class EventSystem {}
    
  [ComImport, Guid("7542E960-79C7-11D1-88F9-0080C7D771BF")]
  class EventSubcription {}
    
  [ComImport, Guid("AB944620-79C6-11d1-88F9-0080C7D771BF")]
  class EventPublisher {}
    
  [ComImport, Guid("cdbec9c0-7a68-11d1-88f9-0080c7d771bf")]
  class EventClass {}

请注意,必须通过重构方法论以编程方式定义类的元数据。

订阅

订阅者激活需要执行事件系统和用户界面(窗体)之间的桥接。此操作通过将委托处理程序注册到事件对象(连接过程)来完成,另一方面,通过创建订阅并将其注册到事件系统来完成。请注意,此订阅的事件类必须从事件系统中获取,在此设计中,订阅是瞬态类型,因此 sub.SubscriberInterface = this;

  public void Activate(Object wire) 
  {
    // activate subscriber
    try 
    { // register delegate into the event object
      notify += wire as EventSystemNotificationHandler;
      //
      //access to Even System (LCE)
      IEventSystem es = new EventSystem() as IEventSystem;
      //create and populate a subscription object
      IEventSubscription sub = new EventSubcription() as IEventSubscription;
      sub.Description = SubscriptionViewerDesc;
      sub.SubscriptionName = SubscriptionViewerName;
      sub.SubscriptionID = SubscriptionViewerID;
      sub.methodName = "ChangedSubscription";
      sub.SubscriberInterface = this;
      sub.EventClassID = es.EventObjectChangeEventClassID;
      es.Store("EventSystem.EventSubscription", sub);
    }
    catch(Exception ex)
    {
      string strText = ex.GetType() + ":" + ex.Message;
      Trace.Assert(false, strText, 
       "Caught exception at SubscriberViewer.Subscriber.Activite");
    }
  }

 

订阅者刷新

Refresh 方法是订阅者的关键功能。它提供了实际的业务逻辑,用于检索事件系统集合对象中订阅的属性,并将它们格式化为 XML 字符串作为返回值。其实现使用 XMLTextWriter 类来填充 XML 节点。请注意,COM 对象必须在最后使用 Marshal.ReleaseComObject 函数释放。

  public string Refresh()
  { 
    // access to ES subscriptions
    IEventObjectCollection evntObjColl = null;
    IEnumEventObject evnObj = null;
    IEventSubscription2  subscr = null;
    // build a string writer for XmlTextWriter class
    string xmlRetVal = "";
    StringBuilder xmlStringBuilder = new StringBuilder();
    StringWriter  xmlStringWriter = new StringWriter(xmlStringBuilder);
    XmlTextWriter tw = new XmlTextWriter(xmlStringWriter);

    try 
    {    
      int errorIndex = 0;
      // interface to the Event System (LCE)
      IEventSystem es = new EventSystem() as IEventSystem;
      // get the collection of the subscription
      evntObjColl = es.Query("EventSystem.EventSubscriptionCollection",
                             "ALL", out errorIndex ) 
            as IEventObjectCollection;
      evnObj = evntObjColl.NewEnum as IEnumEventObject;
      //
      //
      int numberOfObj = evntObjColl.Count;        
      //create XML string
      tw.WriteStartDocument();                // Start Document
      tw.WriteComment(
    "Catalog of all Subscription registered in the Event System Store");
      tw.WriteStartElement("Subscriptions");  // Start Root
      //
      while(true)
      {    
        object objSubscr = new object();
        uint retCount = 0;
        evnObj.Next(1, out objSubscr, out retCount);
        if(retCount == 0) break;
        subscr = objSubscr as IEventSubscription2;
        tw.WriteStartElement("Subscription"); // start Node
        tw.WriteAttributeString("SubscriptionName", subscr.SubscriptionName);
        tw.WriteAttributeString("SubscriptionID", subscr.SubscriptionID);
        // Elements
        tw.WriteElementString("Description", "", subscr.Description);
        tw.WriteElementString("Enabled", "", subscr.Enabled.ToString()); 
        tw.WriteElementString("EventClassID", "", 
           Type.GetTypeFromCLSID(new Guid(subscr.EventClassID)).ToString());
        tw.WriteElementString("InterfaceID", "", subscr.InterfaceID);
        tw.WriteElementString("methodName", "", subscr.methodName);
        tw.WriteElementString("MachineName", "", subscr.MachineName);
        tw.WriteElementString("PerUser", "", subscr.PerUser.ToString());
        tw.WriteElementString("OwnerSID", "", subscr.OwnerSID);
        tw.WriteElementString("PublisherID", "", subscr.PublisherID);
        tw.WriteElementString("SubscriberMoniker", "", 
                 subscr.SubscriberMoniker);
        tw.WriteElementString("FilterCriteria", "", subscr.FilterCriteria);
        if(subscr.SubscriberCLSID != null)
          tw.WriteElementString("SubscriberCLSID", "", 
Type.GetTypeFromCLSID(new Guid(subscr.SubscriberCLSID)).ToString());
        else
          tw.WriteElementString("SubscriberCLSID", "", "null");
        if(subscr.SubscriberInterface != null)
          tw.WriteElementString("SubscriberInterface", "", 
                subscr.SubscriberInterface.ToString());
        else
          tw.WriteElementString("SubscriberInterface", "", "null");
        tw.WriteEndElement();                     // end Node
        // cleam-up
        Marshal.ReleaseComObject(subscr);  // release IEventSubscription;
        subscr = null;
      }    
      tw.WriteEndElement();                       // End Root        
      tw.WriteEndDocument();                      // End Document
      //
      xmlRetVal = xmlStringBuilder.ToString();    // export to string
    }
    catch(Exception ex)
    {
      string strText = ex.GetType() + ":" + ex.Message;
      Trace.Assert(false, strText, 
          "Caught exception at SubscriberViewer.Subscriber.Refresh");
    }
    finally
    {
      // dereference COM interfaces
      if(evntObjColl != null)
        Marshal.ReleaseComObject(evntObjColl);    
      if(evnObj != null)
        Marshal.ReleaseComObject(evnObj);
      if(subscr != null)
        Marshal.ReleaseComObject(subscr);
    }
                
    return xmlRetVal;
  }

 

IEventObjectChange

这是用于通知订阅者接收器有关事件系统更改的回调接口契约。在此示例中,我只使用了一种通知 - ChangedSubscription。此回调方法将通知委托给用户端,请参阅以下代码片段

  /// <summary>
  /// IEventObjectChange 
  /// </summary>
  public void ChangedSubscription(EOC_ChangeType dwChangeType, string str)
  {
    string strText = dwChangeType.GetType()+ "=" + dwChangeType.ToString()
                                           + "\n" + "CLSID=" + str; 
    notify(this, strText);
  }
  public void ChangedEventClass(EOC_ChangeType dwChangeType, string str)
  {
    // not implemented
  }
  public void ChangedPublisher(EOC_ChangeType dwChangeType, string str)
  {
    // not implemented
  }

Windows 窗体

这是订阅者的一个简单用户界面。有一个关键方法,它调用订阅者的 Refresh 方法以获取 XML 字符串 - strRetVal 并将其映射到树形视图节点。使用 XML 类和树形视图控件,实现是直接且可读的。

  protected void OnRefresh (object sender, System.EventArgs e)
  {    
    try 
    {
      treeView.Nodes.Clear();
      // ask agent for all subscriptions stored in the ES
      string strRetVal = agent.Refresh();
      // xml message -> treeview
      XmlDocument doc = new XmlDocument();
      doc.LoadXml(strRetVal);
      XmlNodeList NodeList = doc.GetElementsByTagName("Subscription");
        
      for(int ii = 0; ii < NodeList.Count; ii++)
      {
        XmlNode subscription = NodeList.Item(ii);
        string strNodeName = subscription.Attributes.Item(1).Value + ", ";
        strNodeName += subscription.Attributes.Item(0).Value;
        TreeNode arrChild = new TreeNode(strNodeName);
        foreach(XmlNode iNode in subscription)
        {
          if(iNode.NodeType == XmlNodeType.Element)
          {
            string iNameValue = iNode.Name + "=";
            iNameValue += iNode.InnerText;
            arrChild.Nodes.Add(iNameValue);
          }
        }
        if(treeView.InvokeRequired == true)        
              // check if we running within the same thread
          treeView.Invoke(new AddToTreeView(treeView.Nodes.Add), 
                   new object[] {arrChild});
        else
          treeView.Nodes.Add(arrChild); 
      }
    }
    catch(Exception ex)
    {
      String str = ex.GetType() + " : " + ex.Message;
      Trace.Assert(false, str, "Caught exception at OnRefresh");
    }
        
    buttonRemove.Hide();
  }

 

Windows 窗体回调函数

这是发布者通知调用的终结点。如前所述,发布者需要快速执行此调用,并尽量减少阻塞,因此线程已通过称为“立即执行,无需等待”的 BeginInvoke/EndInvoke 设计模式交换到线程池。

  /// <summary>
  /// Callback function hooked to the subscriber agent
  /// </summary>
  protected void EventSystemNotification(object sender, Object e) 
  {    
    // spawn thread
    RefreshProc proc = new RefreshProc(OnRefresh);
    IAsyncResult result = proc.BeginInvoke(sender, null, 
                    new AsyncCallback(OnRefreshDone), null);
  }
  protected void OnRefreshDone(IAsyncResult ar)
  {
    // cleanup
    RefreshProc proc = ((AsyncResult)ar).AsyncDelegate as RefreshProc;
    proc.EndInvoke(ar);
  }

用户界面

下图显示了树形视图控件中的订阅属性。通过单击 Remove 按钮,可以从事件系统中删除此订阅。事件系统中订阅者集合的任何实时更改都将导致其刷新。请注意,每个订阅者都有自己唯一的 ID(生成的随机 Guid),这允许同时运行更多独立的查看器实例。

结论

在 .Net 应用程序中使用 LCE COM+ 服务是完全透明的。在这个简单的示例中,我们展示了如何基于接口契约与事件系统的非托管代码进行“对话”。订阅查看器(此精简版)是在开发或生产阶段监视事件系统存储的宝贵实用程序。

© . All rights reserved.