将COM+ Events进行到底(一)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
Article last modified on 2002-7-4
----------------------------------------------------------------
The information in this article applies to:
- Microsoft Windows 2000 Advanced Server
- Microsoft Windows 2000 Server
- Microsoft Windows 2000 Professional
- Microsoft Component Service 1.0
----------------------------------------------------------------
通常,我们在组件和客户程序之间实现事件通知与订阅时,会采用这两种方法:回调(Callback),和连接点(ConnectionPoint)。他们的本质其实都是,订阅者将接口传递给发布者,当有事情发生时,发布者调用接口的方法。这种类型的事件叫Tightly Coupled Event(TCE),属于Request-Reply方式。
TCE的限制在于:
1. 订阅者必须知道它所请求的通知来自于哪一个发布者。发布者和订阅者紧紧绑定在一起,双方程序代码依赖于接口的定义,我们必须在编译时刻知道对方的信息(CLSID,或ProgID);
2. 要求订阅者和发布者必须同时在线。也就是说,在生存周期上必须重叠(overlap);
3. 不包含过滤或截取机制。
COM+提供了一种全新的服务:COM+ Events或者Loosely Coupled Events(LCE)。不过此事件非彼事件。用微软的话说就是“COM+ 事件不与传统 ConnectionPoints 事件关联,并且在完全不同的方案中使用。”
它的思路是,发布者亲自维护一个外部数据库(我们管它叫EventList),其中存储了订阅的所有事件的列表。发布者通过这个表知道该如何发送事件通知。订阅者也可以读入此列表,并选择它感兴趣的事件。发布者再维护另一个数据库(我们管它叫SubscribtionList),其中存储了订阅者的CLSID。从概念上讲,这个数据库相当于一个邮件列表。订阅者可以向这个库中添加自己的CLSID。发布者想要激发事件时,会检查这个数据库,找到所有订阅这个事件的订阅者的CLSID,创建每一个相关类的新对象,并调用对象的方法。
这个属于Publisher-Subscriber方式。
(图1可参看http://www.microsoft.com/CHINA/msdn/library/techart/com+agvb4.gif)
具体执行的顺序,图2说得非常明白:
(图2可参看http://www.idevresource.com/images/articles/com+eventsintro1.gif)
Fig 2 shows a typical COM+ events life cycle.
LCE的优点就在于:
n 订阅者不必知道上层事件处理的细节;
n 发布者不在运行时,订阅者也可以发出订阅请求;发布者不用理会订阅者是否在家,就可以发布通知;
n 过滤或者截取。订阅者可以选择订阅事件中哪些他更感兴趣,比如,一个订户订阅了出版社的新书通知,但是他还可以告诉出版社只有当国外作家的新书到达时才给他通知,或者只有当价格低于50元的新书到达时候才通知他。
更多细节
发布者的事件方法执行情况:
当一个事件的方法最终返回时,
非QC的订阅者中:
临时订阅者的情况:订阅者对象已经被调用,而且方法已经返回!
永久订阅者的情况:订阅者对象已经被创建、调用、返回,而且已经被释放!
QC订阅者的情况:
所有对QC订阅者的调用已经被记录下来;
对于永久订阅者,这些记录已经被送到队列准备传送出去。
所以非QC订阅者的执行情况会影响到发布者,而QC不会。比如非QC的订阅者中如果在接收到事件通知的方法中Sleep个一分钟,那发布者的事件方法就必须老老实实等1分钟,然后再调用下一个订阅者对象。
订阅者的疑问一:
如果我的订阅者程序已经在处理一个事件了,这时候又来了一个事件通知,那么这第二个事件会被谁接收呢?
是新创建一个订阅者对象呢?还是等着当前这个事件处理完?
我认为是后者。
发布者的疑问一:
发布者可不可以位于不同的机器?
发布者分布到远程服务器上,但是事件通知仍然在中心服务器上。这种情况可以通过下面两种方法做到:
u 将EventClass所在的COM+应用导出为应用程序代理。在远程服务器上安装这个代理。
u 在远程服务器上调用该EventClass发布事件时,调用代码改为:CreateObject(“%YourEventClassProgID%”, “\\%YourCenterServerName%”)。
这样,在远程服务器上的事件发布行为,都会被转到中心服务器上的EventClass应用。
订阅者的疑问二:
订阅者是否可以位于不同的机器呢?
下面的文字是引用MSDN的《COM+ Events Architecture》:
Note This version of COM+ Events does not support a distributed event store. A subscriber must subscribe to an event on each computer from which it wants to receive notification. As an alternative, you can register the EventClass and subscriptions on a central computer and instantiate this EventClass from the remote computers on which you will publish events. Delivery of events is provided either by DCOM or queued components. For more information on using queued components, see Composing Events with Queued Components.
曾经有人做过下面的步骤试图远程订阅:
ü 远程订阅者创建一个“COMAdmin.COMAdminCatalog”的实例;
ü 远程订阅者调用COMAdmin.COMAdminCatalog的Connect方法连结至中心服务器;
ü 远程订阅者通过向中心服务器的临时订阅数据库中添加一条记录来订阅;
但是,结果是中心服务器的其他本地订阅者都接收到了事件通知,而远程订阅者却没有。
建议如果真的想远程订阅的话,可以自己实现DCOM,或者用QC。
EventClass组件的调试:
如果在VC IDE或者VB IDE中设断点调试EventClass组件,那么当事件发布时,订阅者将接收不到事件通知。
这是由于在COM+应用中EventClass组件的注册地址已经被更改为:
D:\Program Files\Microsoft Visual Studio\VB98\VB6DEBUG.DLL了!
虽然,EventCalss组件的CLSID没有变,但是不是和订阅数据库中记录的不一样了呢,所以没有通知订阅者。
制作实录:
第一步,先做一个EventClass组件:
新建一个VB DLL,Project Name为TomoTrace,Class Module Name为Trace。
代码极其简单,为:
Public Function Publish(ByVal strAuthor As String, _
ByVal strTraceType As String, _
ByVal strEventCategory As String, _
ByVal nEventID As Integer, _
ByVal nOther As Integer, _
ByVal strTraceContent As String) As Integer
' Do Nothing
End Function
在组件服务中新建一个COM+应用,在该应用下新建组件TomoTrace.Trace,注意要选择安装为新的事件类。
记录下这个接口的CLSID。
第二步,做一个订阅者:
新建一个VB EXE,Project Name为Trace2File。
首先要引用TomoTrace组件,并实现它的Trace接口:
Implements Trace
声明一些全局变量:
‘ g_oAdmin将是COMAdmin.COMAdminCatalog的实例对象:
Dim g_oAdmin As Object
‘ 使用ICatalogObject接口将允许读写COM+ Catalog中的对象暴露出来的属性:
Dim SubObj As ICatalogObject
Const TransientSubscription = "Transient Subscription"
‘ 这是TomoTrace.Trace事件接口的ClassID:
Const EVENTCLSID = "{8EDBE260-A7C7-4000-BF95-CA4CC8FCA6C2}"
Dim g_CascadeApp, g_TransID
‘ 你可以用ICatalogCollection接口枚举、添加、删除和获得Collection中的条目:
Dim Subcoll As ICatalogCollection
在Form初始化时,提交订阅请求:
Private Sub Form_Load()
' Create COMAdmin object
Set g_oAdmin = CreateObject("COMAdmin.COMAdminCatalog.1")
' Get the TRANSIENTSUBSCRIPTIONS collection
Set Subcoll = g_oAdmin.GetCollection("TransientSubscriptions")
' Add a new subscription
Set SubObj = Subcoll.Add
‘ 将我们的EventClass的CLSID加进去
SubObj.Value("EventCLSID") = EVENTCLSID
‘ 临时订阅时,这个Name条目的值一定是“Transient Subscription”
SubObj.Value("Name") = TransientSubscription
SubObj.Value("SubscriberInterface") = Me
‘ 将所做的改变存入COM+ Catalog Data Store中:
Subcoll.SaveChanges
‘ 实际计算中g_TransID的值将为
’ {ADA4AFA1-6B54-4C4B-A74A-9EFFF4F7DC3F},这就是这次订阅的唯一标识
‘ 我们将在取消订阅时用到这个标识
g_TransID = SubObj.Value("ID")
End Sub
Form退出时,取消临时订阅:
Private Sub Form_Unload(Cancel As Integer)
On Error Resume Next
‘ 对于Collection中的所有对象,读取数据
Subcoll.Populate
' Remove the subscription
Dim k
For k = 0 To Subcoll.Count - 1
Dim oObject
Set oObject = Subcoll.Item(k)
‘ 如果Collection中的条目的ID和我们先前保存的ID一样,则删除
If oObject.Value("ID") = g_TransID Then
Subcoll.Remove (k)
Subcoll.SaveChanges
End If
Next
On Error GoTo 0
End Sub
实现事件的方法:
Private Function Trace_Publish(ByVal strAuthor As String, _
ByVal strTraceType As String, _
ByVal strEventCategory As String, _
ByVal nEventID As Integer, _
ByVal nOther As Integer, _
ByVal strTraceContent As String) As Integer
‘ 将Trace的内容添加到listbox中:
Dim lCount As Long
listTrace.AddItem Now & " :: " & strTraceType
If (Len(strTraceContent) < 60) Then
listTrace.AddItem "--------" & strTraceContent
Else
listTrace.AddItem "--------" & Left(strTraceContent, 60) & "..."
End If
lCount = listTrace.ListCount - 1
listTrace.ListIndex = lCount
If lCount > 20 Then
listTrace.RemoveItem 0
End If
End Function
第三步,做一个发布者:
新建一个VBScript脚本,它将调用EventClass组件TomoTrace.Trace:
Dim obj
Set obj = CreateObject("TomoTrace.Trace")
strAuthor = "TomoEvent"
strTraceType= "Information"
strEventCategory = "Application"
nEventID = 0
nOther = 0
strTraceContent = "这里我们的例子中,EventClass就是TomoTrace.DLL组件,订阅者是Trace2File.EXE。发布者就是本脚本。"
obj.Publish strAuthor, strTraceType, strEventCategory, nEventID, nOther, strTraceContent
PublisheràDistributoràSubscriber:
先启动几个Trace2File.EXE,然后运行VBScript脚本。你就会看到这几个Trace2File上显示出发布的Trace内容。
然后将这个脚本放置在远程机器上,修改一下脚本:CreateObject(“TomoTrace.Trace”) àCreateObject(“TomoTrace.Trace”,”\\zhengyun”)。这样就可以在其他机器上向中心服务器发布事件通知了。
Written by zhengyun (at) tomosoft.com