通常,我们在组件和客户程序之间实现事件通知与订阅时,会采用这两种方法:回调(Callback),和连接点(ConnectionPoint)。他们的本质其实都是,订阅者将接口传递给发布者,当有事情发生时,发布者调用接口的方法。这种类型的事件叫Tightly
Coupled
Event(TCE),属于Request-Reply方式。 TCE的限制在于: ü
订阅者必须知道它所请求的通知来自于哪一个发布者。发布者和订阅者紧紧绑定在一起,双方程序代码依赖于接口的定义,我们必须在编译时刻知道对方的信息(CLSID,或ProgID); ü
要求订阅者和发布者必须同时在线。也就是说,在生存周期上必须重叠(overlap); ü
不包含过滤或截取机制。
LCE的优点就在于: ü
订阅者不必知道上层事件处理的细节; ü
发布者不在运行时,订阅者也可以发出订阅请求;发布者不用理会订阅者是否在家,就可以发布通知; ü
过滤或者截取。订阅者可以选择订阅事件中哪些他更感兴趣,比如,一个订户订阅了出版社的新书通知,但是他还可以告诉出版社只有当国外作家的新书到达时才给他通知,或者只有当价格低于50元的新书到达时候才通知他。 更多细节 发布者的事件方法执行情况: 当一个事件的方法最终返回时,
非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
‘
你可以用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@tomosoft.com