本主题将概述连接点并介绍通常用于描述 COM 事件的事件相关术语。
连接点在 COM 环境中建立客户端和服务器之间的双向通信。利用这一机制,COM 服务器可以在发生某一事件时回叫客户端。例如,服务器(如 Microsoft Internet Explorer)可以引发一个事件,以报告对其客户端程序的更改(如标题更改)。客户端创建一个称作事件接收器的内部 COM 对象来为传入通知做准备。一旦收到通知,客户端就可以执行与该事件关联的操作。
事件接收器提供用于向服务器公开事件相关方法的接口。服务器通过调用这些事件相关方法来引发事件。客户端会将事件接收接口当作普通的 COM 接口来实现。服务器将该接口声明为输出接口;COM 服务器的创作者将 source 属性应用于 COM 服务器类型库中的接口。服务器使用来自事件接收接口的定义来标识 sink 和 invoke 方法。
实现事件接收接口的 COM 客户端通常称作事件接收器,或简称为接收器。在下图中,接收器实现 ISinkEvents 接口。引发事件的服务器称作源。
连接点事件模型
当建立事件接收接口后,接收器必须连接到源上。连接点机制将使用以下协议来连接接收器和源:
-
接收器对服务器对象查询 IConnectionPointContainer 接口。如果该对象支持连接点,它将返回一个指针。
-
接收器使用容器对象的方法来查找表示特定连接点的 IConnectionPoint 接口。由于服务器可以支持多个输出接口,客户端必须使其接收器匹配特定连接点接口的接口标识符 (IID)。
-
获取正确的连接点对象后,接收器将调用 IConnectionPoint::Advise 来注册其接收接口指针。服务器(源)将保持连接(并向其引发事件),直至客户端通过调用 IConnectionPoint::Unadvise 中断连接。
Visual Basic 会隐藏连接点的大多数详细信息;但是,您必须在客户端的服务器声明中包括 WithEvents 指令。在 C++ 中,客户端代码必须查询 IConnectionPointContainer 和 IConnectionPoint 并对它们调用方法。服务器实现这两种接口。
请参见
任务
如何:处理 COM 源引发的事件
其他资源
关键字:连接点 COM 事件 IDispEventImpl
接收事件是COM客户端中非常重要的功能。但是除了在MFC中能够接收控件[1]所发出的事件以外,VC++不能自动生成接收事件的代码。为了接收普通COM的事件,必须自行编写接收事件的代码。本文通过一个具体的实例说明如何在VC++程序中通过ATL编写接收COM事件的代码,当然,程序本身不必是ATL项目。
1 COM事件原理
COM对象通过连接点实现发送事件的功能。每个连接点都规定了接收事件用的接口,而客户程序通过实现了这个接口的对象接收事件。
如下图所示,假设COM对象A的连接点要求事件接收对象实现IEventSink接口,整个事件接收过程是这样的:首先,客户程序创建COM对象A和用于接收COM对象A事件的事件接收对象B。接下来,客户程序在COM对象A中注册COM对象B的事件接收接口IEventSink。然后,COM对象A就可以通过这个接口发送事件给客户端了。
目前,大多数的连接点都要求通过IDispatch接口发送事件, ATL自动生成的连接点和通过MFC及VB制作的控件都是通过IDispatch接口传递事件。虽然通过IDispatch发送事件可以产生一致的事件接收接口,但由于IDispatch接口的困难,也造成了接收事件代码实现起来更加复杂。
本文中的例子通过ATL的IDispEventImpl模板简化了事件接收对象的编码。同时,本文中的例子也说明了如何在非ATL项目中使用ATL的功能。
2 使用ATL实现连接点
接收事件的就必须实现一个专用的COM对象,这也是实现上最困难的地方。而且,很多连接点都要求通过IDispatch接口传递事件,这样就造成了更大的困难。
我们按照步骤说明如何编写代码在普通的应用程序中接收通过IDispatch接口发送的事件。
2.1 添加ATL头文件
使用ATL前必须包含以下头文件:atlbase.h和atlcom.h,并且定义_Module变量[2]。把以下代码加到stdafx.h是最方便的:
#include
extern CComModule _Module;
#include
在cpp文件中要对_Module进行定义。如下:
CcomModule _Module;
在添加了这两行之后,我们就可以使用ATL的功能了。而不必创建ATL的项目。
当然,如果项目是通过ATL项目向导产生的就不必再次添加头文件了。
2.2 ATL初始化
使用ATL之前必须初始化,使用完之后还要终止ATL。
对于控制台程序,初始化ATL的方法是:
int main(int argc, char * argv[], char ** env)
{
_Module.Init(NULL, (HINSTANCE)GetModuleHandle(NULL));
...
如果是Windows程序或动态连接库,使用传入的Instance句柄初始化。
int WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
_Module.Init(NULL, hInstance);
...
终止ATL使用以下方法:
_Module.Term();
初始化ATL必须在任何ATL操作前进行,而终止则必须在所有ATL操作结束后进行。
2.3 事件接收器
2.3.1 引入COM对象
为了方便,本例使用#import引入COM类,也可以通过其它方式引入COM对象,但其它代码要做相应修改。
#import "[COM或TLB的路径]" named_guids, no_namespace
注意,必须要添加named_guids属性。否则,#import就不会生成TLB类的GUID定义。
2.3.2 事件接受器类
A. 类定义
事件接收器是一个类,从IDispEventImpl继承。IDispEventImpl是ATL中的一个模板,专用于接收IDispatch接口的事件。IDispEventImpl的定义是:
class IDispEventImpl
{
};
nID可以设成0,T是事件接收器类的名字,pdiid是事件接口的GUID,plibid是类型库GUID的指针,MajorVer和MinorVer分别是主、次版本号,tihClass是用于处理TLB的类。TihClass可以使用缺省值。
事件接收器类的代码如下:
class EventReceiver :
public IDispEventImpl<0,
EventReceiver,
&DIID__ConnectPointInterface,
&LIBID_xxxLib, 1, 0>
{
...
};
B. 事件响应函数
对于每个要响应的事件,要在事件响应类中添加相应的事件响应函数。事件响应函数的名称可以自己选择,但参数和返回值必须和COM对象的定义一致。
事件响应函数是标准的接口方法,应该使用STDMETHOD和STDMETHODIMP声明。
本例中,我们接收一个事件,具有一个整形参数。首先,在事件响应类中添加函数定义:
class EventReceiver : public ...
{
...
public:
STDMETHOD(EventNotify)(int i);
...
}
然后添加具体的事件响应代码:
STDMETHODIMP EventReceiver::EventNotify(int i)
{
printf("EventReceiver : %d/n", i);
return S_OK;
}
C. 事件对应表
事件响应类中要定义事件对应表,说明哪个函数响应哪个事件。事件对应表通过三个宏实现,它们是:BEGIN_SINK_MAP,SINK_ENTRY_EX,END_SINK_MAP。
BEGIN_SINK_MAP有一个参数,是事件接收类的名称。SINK_ENTRY_EX有四个参数,分别是nID、diid、EventId和FuncName。nID是0、diid是事件接口的GUID、EventId是事件编号、FuncName是响应函数的名称。
以下代码中的事件响应表声明事件编号是1的事件通过EventNotify函数响应。可以写多个SINK_ENTRY_EX来响应多个事件。
class EventReceiver : public ...
{
...
public:
BEGIN_SINK_MAP(EventReceiver)
SINK_ENTRY_EX(0, DIID__IConnectionPointTestObjEvents, 1, EventNotify)
END_SINK_MAP()
...
}
2.4 连接对象和关闭连接
事件连接类实现完成之后,要连接到具体的COM对象才可以接收事件。IDispEventImpl通过DispEventAdvise方法连接到COM对象。DispEventAdvise使用要连接的COM对象指针作为参数。连接前要先生成事件接收类的实例,代码如下:
EventReceiver * pReceiver = new EventReceiver;
pReceiver->DispEventAdvise(pObj);
如果不需要在接收事件,应该使用DispEventUnadvise函数关闭连接,代码如下:
pReceiver->DispEventUnadvise(pObj);
3 实例
本文的实例可以发邮件到[email protected] 索取。例子中包含了发送事件COM对象和接收事件的控制台程序。并且在Virtual Studio 6.0 SP5 + Platform SDK下编译运行成功。
[1] 控件是一种特殊的COM对象。
[2] 变量名称必须是_Module
黄海荣 田作华
上海交通大学B9703-1信箱(200030)
2009-02-16
摘 要: 探讨了在使用Visual C++编程时利用Microsoft Communications Control控件编写串行通信程序的方法,并给出了例程,具有一定的实用意义。
关键词: Visual C++ 串行通信 ActiveX
在开发微机控制系统的过程中,我们经常需要通过RS-232串行接口与外部设备进行通信。例如分级控制系统中上位机与下位机的数据交换以及数据采集系统中计算机与数字仪表的通信等。在DOS时代,编写串行通信程序是一件相当复杂的工作,程序员需要具备相当的硬件知识,对可编程串行通信接口芯片的内部寄存器定义、工作方式、指令字等相关内容有所了解,才有可能着手编写程序,大量的时间和精力都花在了如何与硬件打交道上,而不是花在我们的主要目的——获取与处理数据上;在Windows下,Win32 API提供了使用CreateFile/WriteFile等文件I/O函数进行串行口操作的方法,但是在实现上仍然是相当烦琐的。幸运的是,Windows平台先进的ActiveX技术使我们在对串行口编程时不再需要处理烦琐的细节。利用已有的ActiveX控件,我们只需要编写少量的代码,就可以轻松高效地完成任务。本文以Windows 98下用Visual C++ 6.0开发PT650C秤重显示器的通信模块为例,探讨了使用Microsoft Communications Control 控件进行串行通信的方法。
1 ActiveX控件介绍
ActiveX是Windows下进行应用程序开发的崭新技术,它的核心内容是组件对象模型COM(Component Object Model)。ActiveX控件包括一系列的属性、方法和事件,使用ActiveX控件的应用程序和ActiveX控件之间的工作方式是客户/服务器方式,即应用程序通过ActiveX控件提供的接口来访问ActiveX控件的功能。
Microsoft Communications Control(以下简称MSComm)是Microsoft公司提供的简化Windows下串行通信编程的ActiveX控件,它为应用程序提供了通过串行接口收发数据的简便方法。具体的来说,它提供了两种处理通信问题的方法:一是事件驱动(Event-driven)方法,一是查询法。
1.1 事件驱动法
在使用事件驱动法设计程序时,每当有新字符到达,或端口状态改变,或发生错误时,MSComm控件将解发OnComm事件,而应用程序在捕获该事件后,通过检查MSComm控件的CommEvent属性可以获知所发生的事件或错误,从而采取相应的操作。这种方法的优点是程序响应及时,可靠性高。
1.2 查询法
这种方法适合于较小的应用程序。在这种情况下,每当应用程序执行完某一串行口操作后,将不断检查MSComm控件的CommEvent属性以检查执行结果或者检查某一事件是否发生。例如,当程序向串行设备发送了某个命令后,可能只是在等待收到一个特定的响应字符串,而不是对收到的每一个字符都立刻响应并处理。
MSComm控件有许多重要的属性,其中首要的几个如表1所示。
2 编程实现
在使用MSComm控件开发PT650C秤重显示器通信程序时,采用了事件驱动法,主要是在comEvReceive(接收到数据)事件发生时响应并获取缓冲区中的数据。以下具体介绍实现方法。
打开Visual C++ 6.0集成开发环境,创建一个基于对话框的MFC应用程序项目,命名为MyCOM,记住在设置项目选项时必须选上ActiveX Controls,其他的按照缺省设置。完成这一步后,选择菜单项Project/Add to project/Components and Controls……,将弹出一个对话框以选择系统中已有的组件(Components)和控件(Controls)。选择Registered ActiveX Controls文件夹下的Microsoft Communications Control项并按下Insert按钮,将MSComm控件支持加入到本项目中。这时将生成一个名为CMSComm的C++类,并且在对话框编辑器里的工具栏将出现MSComm控件图标。CMSComm类是由MSComm控件导出的一系列接口函数构成的,利用它将可以访问MSComm控件的属性(Property)和方法(Method)。
假设PT650C秤重显示器接在计算机COM1口上,那么打开资源编辑器,在程序主对话框(资源ID为IDD_MYCOM_DIALOG)上面放置一个MSComm控件,并用Class Wizard为该对话框类添加对应该控件的成员变量m_wndCOM1。由于PT650C秤重显示器与计算机进行串行通信时采用7个数据位、1个停止位、偶校验方式,并且波特率为2400/4800/9600可选,这里我采用9600波特率,在对话框编辑器中设置MSComm控件的属性如下:
ID:IDC_COM1(资源ID)
CommPort:1 (COM1)
Settings:9600,e,7,1(波特率9600,偶校验,7个数据位,1个停止位)
RThreshold:1(每接收到1个字符就触发一个接收数据事件)
SThreshold:0(不触发发送缓冲区空事件)
InputLen:1(每次读操作从缓冲区中取一个字符)
其他选项按照缺省设置或者根据具体设备的要求进行设置。如果需要通过多个串行口与多台设备通信,那么每一个串行口对应于一个单独的MSComm控件。串行口的设置参数既可以在对话框编辑器里设定,也可以在程序代码中通过调用CMSComm类的成员函数设定。例如,我们可以在MyCOMDlg类的OnInitDialog成员函数中初始化MSComm控件的参数,代码如下:
BOOL CMyCOMDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//以上为MFC框架自动生成的代码,在此不列出
//TODO:Add extra initialization here
m_wndCOM1.SetCommPort(1);
m_wndCOM1.SetSettings(″9600,e,7,1″);
m_wndCOM1.SetRThreshold(1);
m_wndCOM1.SetSThreshold(0);
m_wndCOM1.SetInputLen(1);
m_wndCOM1.SetPortOpen(TRUE); //打开通信口
return TRUE;//return TRUE unless you set the focus to a control
}
接下来为程序主对话框建立响应MSComm事件的处理函数,每当MSComm控件触发事件时该函数将被调用。在对话框编辑器中用鼠标左键双击MSComm控件图标,在弹出的对话框中输入函数名OnCommCom1,该事件处理函数的原型定义和消息映射入口将自动被添加到CMyCOMDlg类中,我们所要做的只是在OnCommCom1函数中给出具体的数据处理程序段,代码示例如下:
void CMyCOMDlg::OnCommCom1()
{
//TODO:Add your control notification handler code here
CString sInput;
switch(m_wndCOM1.GetCommEvent())
{
case 1: //comEvSend事件
/*如有数据要发送,可采用以下代码:
VARIANT varOut;
VariantInit(&varOut);
varOut.vt=VT_BSTR;
USES_CONVERSION;
varOut.bstrVal=SysAllocString(T2OLE(″My data″));
if(varOut.bstrVal){
m_wndCOM1.SetOutput(varOut);
SysFreeString(varOut.bstrVal);
}
*/
break;
case 2: //comEvReceiv事件,有数据到达
sInput=m_wndCOM1.GetInput().bstrVal;
//对接收到的数据做必要处理
break;
case 1009://comEventRxParity事件,奇偶校验错误
//错误处理代码
break;
default:
break;
}
在这里必须注意的一点是在发送字符数据时,必须向MSComm控件提供Unicode格式的字符串,在以上代码中用到了USES_CONVERSION和T2OLE宏进行ANSI字符串到Unicode字符串的转换,具体内容可参考Visual C++ 6.0所带的MSDN文档,在此不加赘述。
本文对Windows 98下Visual C++ 程序中使用MSComm串行通信ActiveX控件编程的方法做了探讨,显示了ActiveX技术的强大功能、充分的灵活性和易用性,具有一定的实践意义。
参考文献
1 Microsoft公司.Microsoft Development Network.
2 Kate Gregory.Special Edition Using Visual C++5.
感谢:刘鑫
import win32com
import win32com.client
import pythoncom
import time
class EventHandler:
global bVisibleEventFired
bVisibleEventFired = 1
def OnDownloadBegin(self):
print "DownloadBegin"
def OnDownloadComplete(self):
print "DownloadComplete"
def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing):
print "documentComplete of %s" % URL
ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler)
ie.Visible = 1
ie.Navigate(" www.aawns.com")
#这里是等待事件的发生
pythoncom.PumpMessages()
ie.Quit()
import win32gui
import win32com
import win32com.client
import pythoncom
import time
def runtest(self):
print 'tuntest'
class EventHandler:
global bVisibleEventFired
bVisibleEventFired = 1
def OnDownloadBegin(self):
print "DownloadBegin"
def OnDownloadComplete(self):
print "DownloadComplete"
def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing):
print "documentComplete of %s" % URL
#在这里我们再调用test的runtest方法,看是否继承成功。
self.runtest()
def __init__(self):
ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler)
ie.Visible = 1
ie.Navigate(" www.aawns.com")
#这里调用test的runtest方法,看是否继承成功。
self.runtest()
pythoncom.PumpMessages()
ie.Quit()
a=runcom()
运行结果是错误的。
DownloadBegin
DownloadComplete
DownloadBegin
DownloadComplete
documentComplete of http://www.aawns.com/
pythoncom error: Python error invoking COM method.
Traceback (most recent call last):
File "C:/Python23/Lib/site-packages/win32com/server/policy.py", line 283, in _
Invoke_
return self._invoke_(dispid, lcid, wFlags, args)
File "C:/Python23/Lib/site-packages/win32com/server/policy.py", line 288, in _
invoke_
return S_OK, -1, self._invokeex_(dispid, lcid, wFlags, args, None, None)
File "C:/Python23/Lib/site-packages/win32com/server/policy.py", line 550, in _
invokeex_
return func(*args)
File "D:/python/test.py", line 24, in OnDocumentComplete
self.runtest()
File "C:/Python23/Lib/site-packages/win32com/client/__init__.py", line 454, in
__getattr__
raise AttributeError, "'%s' object has no attribute '%s'" % (repr(self), att
r)
exceptions.AttributeError: '' object has no attribute 'runtest'
import win32gui
import win32com
import win32com.client
import pythoncom
import time
class EventHandler:
global bVisibleEventFired
bVisibleEventFired = 1
def OnDownloadBegin(self):
print "DownloadBegin"
#先继承全局变量增加一个字符串
global testlist
testlist.append("DownloadBegin")
def OnDownloadComplete(self):
print "DownloadComplete"
#先继承全局变量增加一个字符串
global testlist
testlist.append("DownloadComplete")
def OnDocumentComplete(self, pDisp = pythoncom.Missing , URL = pythoncom.Missing):
print "documentComplete of %s" % URL
#先继承全局变量再打印
global testlist
print testlist
def __init__(self):
global testlist
ie = win32com.client.DispatchWithEvents("InternetExplorer.Application", EventHandler)
ie.Visible = 1
ie.Navigate(" www.aawns.com")
#打印全局变量
print testlist
pythoncom.PumpMessages()
ie.Quit()
testlist=[]
a=runcom()