目录
1 主要接口
1.1 OPCEventServer对象
1.2 OPCEventSubscription对象
1.3 OPCEventAreaBrowser对象(可选)
1.4 自定义接口开发注意
2 OPCEventServer
2.1 接口介绍
2.2 接口方法
3 IOPCEventServer2
3.1 接口介绍
3.2 接口方法
4 IConnectionPointContainer
4.1 IConnectionPointContainer
4.2 EnumConnectionPoints
4.3 FindConnectionPoint
4.4 IConnectionPoint
5 OPCEventAreaBrowser
5.1 接口介绍
5.2 接口方法
6 OPCEventSubscription
6.1 接口介绍
6.2 接口方法
7 IOPCEventSubscriptionMgt2
8 IOPCEventSink
8.1 接口介绍
8.2 IOPCEventSink方法
8.3 IOPCShutdown
8.4 IOPCShutdown方法
本文简单介绍OPC AE规范的接口相关知识。OPC官方说明文档
OPC AE规范描述了OPC事件服务器应该实现的对象和接口,实现在多个OPC客户端间共享事件和警报条件。
任何支持IOPCEventServer接口的COM对象都是OPC事件服务器。在许多情况下,OPC DA服务器还将暴露一个OPCEventServer对象,具备数据服务器和事件服务器的角色。在其他情况下,通常是专用的OPC事件服务器,而不是具备OPC DA的事件服务器。
1 主要接口
本规范定义了以下COM对象,下面将简要介绍这些对象:OPCEventServer、OPCEventSubscription和OPCEventAreaBrowser。
1.1 OPCEventServer对象
上图为OPC Event Server和IID_IOPCShutdown关闭对象的视图。这些对象是使用CoCreateInstance或CoCreateInstanceEx创建。如前所述,这可能是一个OPC DA服务器对象,它也实现IOPCEventServer接口。
IOPCCommon接口用于执行OPC服务器的通用功能,例如数据访问。例如:包括LocaleID的管理和检索错误字符串。
IOPCEventServer接口用于创建OPC事件订阅和OPC事件区域浏览器对象,查询事件类别和事件参数,并管理条件。
IConnectionPointContainer和IConnectionPoint接口是可连接对象的标准DCOM接口,用于处理服务器通知即将关闭客户端的回调。
1.2 OPCEventSubscription对象
上图为客户端使用IOPCEventServer::CreateEventSubscription方法时,OPC事件服务器创建的OPCEventSubscription和IID_IOPCEventSink对象的视图。
IOPCEventSubscriptionMgt接口用于配置OPC事件报告的过滤器和其他属性。
可选的IOPCEventSubscriptionMgt2接口用于设置或获取订阅的keep-alive时间。
IConnectionPointContainer和IConnectionPoint接口是的标准DCOM接口可连接对象,并用于处理事件通知的回调。
1.3 OPCEventAreaBrowser对象(可选)
上图是当客户端调用IOPCEventServer::CreateAreaBrowser方法时,由OPC事件服务器创建的OPCEventAreaBrowser对象的视图。
IOPCEventAreaBrowser接口为客户端提供了一种浏览由服务器实现的过程区域组织结构。服务器提供的事件条件被组织成一个或多个过程区域,并且客户端可以根据指定的过程区域筛选事件订阅。
此对象是可选的,简单事件服务器可能不支持该接口。
1.4 自定义接口开发注意
为了正确操作,枚举器是从对象上的方法创建和返回的,而不是而不是通过QueryInterface。
根据COM规范,客户端必须释放与“out”或“in/out”参数关联的所有内存。
根据COM规范,所有方法都必须在每个接口上实现。方法不需要的可以根据情况返回E_ NOTIMPL或S_OK。
COM不允许为Out或In/Out参数传递NULL。
2 OPCEventServer
2.1 接口介绍
OPCEventServer对象是OPC事件服务器公开的主要对象。该接口对象提供的包括:
• IUnknown
• IOPCCommon
• IOPCEventServer
• IConnectionPointContainer
IUnknown:服务器必须提供标准的IUnknown接口。请参阅OLE程序员参考资料。并且按照Microsoft的要求实现所有功能。
IOPCCommon:数据访问等其他OPC服务器共享此接口设计。它提供了对特定客户端/服务器会话有效的LocaleID设置和查询。也就是说,就像组定义,一个客户端的操作不会影响任何其他客户端。
下面提供了此接口的快速参考。更详细的讨论见OPC通用规范。
• HRESULT SetLocaleID ( [in] LCID dwLcid );
• HRESULT GetLocaleID ( [out] LCID *pdwLcid );
• HRESULT QueryAvailableLocaleIDs ( [out] DWORD *pdwCount, [out, sizeis(dwCount)] LCID *pdwLcid );
• HRESULT GetErrorString( [in] HRESULT dwError, [out, string] LPWSTR *ppString );
• HRESULT SetClientName ( [in, string] LPCWSTR szName );
IOPCEventServer:这是OPC事件服务器的报警和事件功能的主要接口。此接口是用于创建OPC事件订阅对象、创建OPC事件区域浏览对象、查询事件类别和相关事件参数,管理条件,执行杂项诸如获取事件服务器状态之类的操作。
2.2 接口方法
1) HRESULT GetStatus (
[out] OPCEVENTSERVERSTATUS ** ppEventServerStatus
);
返回OPC事件服务器的当前状态。
2) HRESULT CreateEventSubscription(
[in] BOOL bActive,
[in] DWORD dwBufferTime,
[in] DWORD dwMaxSize,
[in] OPCHANDLE hClientSubscription,
[in] REFIID riid,
[out, iid_is(riid)] LPUNKNOWN * ppUnk
[out] DWORD * pdwRevisedBufferTime,
[out] DWORD * pdwRevisedMaxSize
);
将事件订阅对象添加到事件服务器。
为客户端创建一个OPCEventSubcription对象,并向客户端返回一个接口。
此对象将至少支持IUnknown、IOPCEventSubscriptionMgt和IConnectionPointContainer。客户端可以管理此接口的状态,包括筛选器和通过ConnectionPoints创建订阅。
3)HRESULT QueryAvailableFilters(
[out] DWORD * pdwFilterMask
);
QueryAvailableFilters方法为客户端提供了一种准确查找哪些由给定的事件服务器支持的筛选条件的方法。通常在对OPCEventSubscription对象配置筛选时调用。
预计在大多数情况下,查询结果将相当“稳定”。但是,服务器实际上允许在任何时候更改可用的选择。因此,客户应(至少允许作为选项)每次将选择呈现给最终用户时都进行新的查询。
4)HRESULT QueryEventCategories(
[in] DWORD dwEventType,
[out] DWORD* pdwCount,
[out, size_is(*pdwCount)] DWORD** ppdwEventCategories,
[out, size_is(,*pdwCount)] LPWSTR** ppEventCategoryDescs
);
QueryEventCategories方法为客户端提供了一种方法来查找给定服务器支持的事件。此方法通常会在指定事件筛选器前调用。服务器将能够自定义事件类别。
根据服务器的复杂程度,返回的事件类别的数量会有所不同,但对于大多数服务器来说,预计小于30,这使得该接口比自定义枚举器更合适。
预计在大多数情况下,查询结果将相当“稳定”。但是,服务器实际上允许在任何时候更改可用的选择。因此,客户应(至少允许作为选项)每次将选择呈现给最终用户时都进行新的查询。
5)HRESULT QueryConditionNames(
[in] DWORD dwEventCategory,
[out] DWORD* pdwCount,
[out, size_is(,*pdwCount)] LPWSTR** ppszConditionNames
);
QueryConditionNames方法为客户端提供了一种查找指定的事件类别的事件服务器的特定条件名称的方法。这种方法通常是在指定事件筛选器之前调用。条件名称是特定于服务器的。
根据服务器的复杂程度,返回的事件类别的数量会有所不同,但对于大多数服务器来说,预计小于30,这使得该接口比自定义枚举器更合适。
预计在大多数情况下,查询结果将相当“稳定”。但是,服务器实际上允许在任何时候更改可用的选择。因此,客户应(至少允许作为选项)每次将选择呈现给最终用户时都进行新的查询。
6)HRESULT QuerySubConditionNames(
[in] LPWSTR szConditionName,
[out] DWORD* pdwCount,
[out, size_is(,*pdwCount)] LPWSTR** ppszSubConditionNames
);
QuerySubConditionNames方法为客户端提供了一种查找相关条件的特定子条件的方法。条件名称是服务器特定的。
返回的子条件名称的数量将根据服务器的复杂程度而变化,但对于大多数服务器来说,预计小于10,这使得该接口比自定义枚举器更合适。
服务器上特定条件的可用子条件名称应为相当“稳定”,并且它们通常不会“在线”更改。但是,实际上允许服务器随时更改可用的选择。因此,客户应该这样做(或者至少允许作为一种选择)每次选择要呈现给最终用户时,都要做新的查询。
7)HRESULT QuerySourceConditions(
[in] LPWSTR szSource,
[out] DWORD* pdwCount,
[out, size_is(,*pdwCount)] LPWSTR** ppszConditionNames
);
QuerySourceConditions方法为客户端提供了一种查找与指定的源(例如FIC101)相关联的特定条件名称的方法。条件名称是服务器特定的。
根据服务器的复杂程度,返回的条件名称的数量会有所不同,但对于大多数服务器,预计小于10,这使得该界面比自定义界面更合适枚举器。
服务器上特定源的可用条件名称将相当“稳定”,并且它们通常不会“在线”更改。但是,实际上允许服务器随时更改可用的选择。因此,客户应该这样做(或者至少允许作为一种选择)每次选择要呈现给最终用户时,都要做新的查询。
8)HRESULT QueryEventAttributes(
[in] DWORD dwEventCategory,
[out] DWORD* pdwCount,
[out, size_is(,*pdwCount)] DWORD** ppdwAttrIDs,
[out, size_is(,*pdwCount)] LPWSTR** ppszAttrDescs
[out, size_is(,*pdwCount)] VARTYPE** ppvtAttrTypes
);
使用QueryEventCategories方法返回的EventCategories,客户端应用程序可以调用QueryEventAttributes方法以获取有关供应商特定属性的信息,服务器可以提供指定事件类别内的事件作为事件通知的一部分。简单服务器可能不支持某些甚至所有EventCategories的任何供应商属性。
特定事件类别的所有事件都有可能支持相同的属性信息。对于同一服务器中该类别的不同实例具有不同属性的事件类别,服务器应该返回所有属性的并集,而客户端必须允许事件中的某些事件通知属性为null。
9)HRESULT TranslateToItemIDs(
[in] LPWSTR szSource,
[in] DWORD dwEventCategory
[in] LPWSTR szConditionName,
[in] LPWSTR szSubconditionName,
[in] DWORD dwCount,
[in, size_is(dwCount)] DWORD* pdwAssocAttrIDs,
[out, size_is(,dwCount)] LPWSTR** ppszAttrItemIDs,
[out, size_is(,dwCount)] LPWSTR** ppszNodeNames,
[out, size_is(,dwCount)] CLSID** ppCLSIDs
);
许多报警和OPC事件服务器都与OPC DA服务器相关联。由于这些服务器可以向与事件相关联的一些或全部属性提供数据访问接口,应用程序需要能够确定特定ItemID给相关源ID的一个或多个特定属性ID代码,以便能够通过数据访问接口访问属性,TranslateToItemID执行所需的转换。此功能有助于客户端希望使用OPC数据访问接口订阅给定事件或警报相关实时数据的情况。
给定事件源和关联属性ID代码的数组,返回项目ID的数组与每个属性ID对应的字符串。事件源以及关联的属性ID作为IOPCEventSink::OnEvent回调机制的一部分返回。给定事件类别的属性ID代码和描述也可以通过IOPCEventServer::QueryEventAttributes函数获取。服务器必须为没有相应项目ID的属性ID返回NULL字符串。
10)HRESULT GetConditionState (
[in] LPWSTR szSource,
[in] LPWSTR szConditionName,
[in] DWORD dwNumEventAttrs,
[in, size_is(dwNumEventAttrs)] DWORD* pdwAttributeIDs,
[out] OPCCONDITIONSTATE ** ppConditionState
);
返回与szSource和szConditionName。客户端必须释放返回的结构。
一些服务器可能没有维护足够的条件状态信息来完全实现此方法。在这种情况下,服务器应该返回E_NOTIMPL。如果服务器选择实现此方法必须为OPCCONDITIONSTATE的每个成员返回有效信息。
11)HRESULT EnableConditionByArea(
[in] DWORD dwNumAreas,
[in, size_is(dwNumAreas)] LPWSTR* pszAreas
);
将指定的过程区域置于启用状态。服务器只能为源本身已启用并且不包含在禁用层次结构中的区域生成条件关联事件。
此方法的效果在事件服务器的范围内是全局的。如果服务器支持多个客户端,将为所有客户端启用条件,客户端将开始接收相关条件事件。
12)HRESULT EnableConditionByArea(
[in] DWORD dwNumAreas,
[in, size_is(dwNumAreas)] LPWSTR* pszAreas
);
将指定的过程区域置于启用状态。此方法的效果在事件服务器的范围内是全局的。因此,如果服务器支持多个客户端,为所有客户端启用条件,并且所有客户端将开始接收条件相关事件。
由于此方法的全局影响,一些事件服务器实现者可能会选择不实现它。在这种情况下,服务器应该返回E_NOTIMPL。
一个条件可能与多个来源相关。这些来源可能是分布于多个区域。启用一个区域的条件不会改变与其他区域中的源关联的同名条件的启用/禁用状态。例如,LevelAlarm”条件,在“区域1”中为源启用,而在“区域2”中为源禁用。
13)HRESULT EnableConditionBySource(
[in] DWORD dwNumSources,
[in, size_is(dwNumSources)] LPWSTR* pszSources
);
将指定事件源的所有条件置于启用状态。服务器将为这些条件生成与条件相关的事件。
此方法的效果在事件服务器的范围内是全局的。因此,如果服务器支持多个客户端,为所有客户端启用条件,所有客户端将开始接收条件相关事件。
由于此方法的全局影响,一些事件服务器实现者可能会选择不实现它。在这种情况下,服务器应该返回E_NOTIMPL。
一个条件可能与多个源相关联。启用一个源的相关条件,不会更改其他源相中相同名称条件的启用/禁用状态,例如,可以为“A100”启用“LevelAlarm”条件,为“FIC101”禁用“LevelAlarm”条件。
14)HRESULT DisableConditionByArea(
[in] DWORD dwNumAreas,
[in, size_is(dwNumAreas)] LPWSTR* pszAreas
);
将指定的过程区域置于禁用状态。服务器将停止为这些条件生成与条件相关的事件。
此方法的效果在事件服务器的范围内是全局的。因此,如果服务器支持多个客户端,所有客户端的条件都被禁用,所有客户端将停止接收条件相关事件。
由于此方法的全局影响,一些事件服务器实现者可能会选择不实现它。在这种情况下,服务器应该返回E_NOTIMPL。
一个条件可能与多个来源相关。这些来源可能是分布于多个区域。禁用一个区域的条件不会改变与其他区域中的源关联的同名条件的启用/禁用状态。例如,LevelAlarm”条件,在“区域1”中为源禁用,而在“区域2”中为源启用。
15)HRESULT DisableConditionBySource(
[in] DWORD dwNumSources,
[in, size_is(dwNumSources)] LPWSTR* pszSources
);
将指定事件源的所有条件置于禁用状态。服务器将停止为这些条件生成与条件相关的事件。
此方法的效果在事件服务器的范围内是全局的。因此,如果服务器支持多个客户端,所有客户端的条件都被禁用,所有客户端将停止接收条件相关事件。
由于此方法的全局影响,一些事件服务器实现者可能会选择不实现它。在这种情况下,服务器应该返回E_NOTIMPL。
一个条件可能与多个源相关联。禁用一个源的相关条件,不会更改其他源相中相同名称条件的启用/禁用状态,例如,可以为“A100” 禁用 “LevelAlarm”条件,为“FIC101” 启用“LevelAlarm”条件。
16)HRESULT AckCondition(
[in] DWORD dwCount
[in, string] LPWSTR szAcknowledgerID,
[in, string] LPWSTR szComment,
[in, size_is(dwCount)] LPWSTR* pszSource,
[in, size_is(dwCount)] LPWSTR* pszConditionName,
[in, size_is(dwCount)] FILETIME* pftActiveTime,
[in, size_is(dwCount)] DWORD* pdwCookie,
[out, size_is(,dwCount)] HRESULT **ppErrors
);
客户端使用AckCondition方法来确认事件服务器中的一个或多个条件。客户端通过IOPCEventSink::OnEvent回调接收来自条件的事件通知。此AckCondition方法专门确认条件处于活动状态或正在转换转换成不同的子条件(并且没有该条件的其他状态转换)。一个或多个条件属于特定的事件源——事件通知的源。对于每个相关条件事件通知,相应的源、条件名称、活动时间和Cookie作为OnEvent回调参数的一部分由客户端接收。
17) HRESULT CreateAreaBrowser(
[in] REFIID riid,
[out, iid_is(riid) LPUNKNOWN* ppUnk
);
为客户端创建一个OPCEventAreaBrowser对象,并将接口返回给客户端。此对象将支持IUnknown和IOPCEventAreaBrowser接口。客户端可以使用此接口浏览服务器上可用的过程区域。
3 IOPCEventServer2
3.1 接口介绍
IOPCEventServer2是一个可选接口,用于在区域或源的基础上管理条件的启用状态。它扩展了在IOPCEventServer上定义的相应方法。方法定义允许客户端查询区域或源列表的当前启用状态。
为了进一步说明启用状态方法的用法,下面的示例区域模型作为启动条件。禁用的对象显示为高亮显示。
1)客户端为A11调用GetEnableStateByArea,对于当前状态,服务器均返回FALSE(pbEnabled)和有效状态(pbEffectivelyEnabled)。
2)客户端调用A11的EnableConditionByArea2,服务器返回S_OK。客户端为S1、S2和S3调用EnableConditionBySource2,区域模型将更新,如下图所示。
3)客户端为A11调用GetEnableStateByArea,对于当前状态,服务器均返回TRUE(pbEnabled)和有效状态(pbEffectivelyEnabled)。
4)客户端调用S1的DisableConditionBySource2,服务器返回S_OK。
5)客户端为A11调用GetEnableStateByArea,对于当前状态,服务器均返回TRUE(pbEnabled)和有效状态(pbEffectivelyEnabled)。
3.2 接口方法
【接口说明】
1)HRESULT EnableConditionByArea2(
[in] DWORD dwNumAreas,
[in, string, size_is(dwNumAreas)] LPWSTR* pszAreas,
[out, size_is(,dwNumAreas)] HRESULT **ppErrors
);
将指定的过程区域置于启用状态。服务器生成源本身已启用且其层次结构中的包含区域没有被禁用的条件相关事件。
此方法的效果在事件服务器的范围内是全局的。如果服务器支持多个客户端,为所有客户端启用条件,所有客户端将开始接收条件相关事件。
由于此方法的全局影响,一些事件服务器实现者可能会选择不实现它。在这种情况下,如果客户端通过QueryInterface请求IOPCEventServer2,服务器应该返回E_NOINTERFACE 。
一个条件可能与多个来源相关。这些来源可能是分布于多个区域。启用一个区域的条件不会改变其他区域中关联源的同名条件的启用/禁用状态区域。例如,可以为“区域1”中的源启用“LevelAlarm”条件,而在“区域2”中的源禁用“LevelAlarm”条件。
如果源的条件状态设置为已启用,并且包含区域的层次结构中的所有区域都已启,则源启用。
如果HRESULT为S_OK,则可以忽略ppErrors(其中的所有结果都保证为S_OK)。
如果HRESULT是FAILED代码,则服务器应返回NULL的OUT指针参数及ppErrors。
调用方必须释放返回的ppErrors数组。
2)HRESULT EnableConditionBySource2(
[in] DWORD dwNumSources,
[in, string, size_is(dwNumSources)] LPWSTR* pszSources,
[out, size_is(,dwNumSources)] HRESULT **ppErrors
);
将指定事件源的所有条件置于启用状态。服务器将为这些源生成与条件相关的事件。
此方法的效果在事件服务器的范围内是全局的。如果服务器支持多个客户端,为所有客户端启用条件,所有客户端将开始接收条件相关事件。
由于此方法的全局影响,一些事件服务器实现者可能会选择不实现它。在这种情况下,如果客户端通过QueryInterface请求IOPCEventServer2,服务器应该返回E_NOINTERFACE。
一个条件可能与多个源相关联。启用一个源的相关条件不会更改其他源关联的相同名称的条件的启用/禁用状态。例如,可以为“A100”启用“LevelAlarm”条件,为“FIC101”禁用“LevelAlarm”条件。
如果源的条件状态设置为已启用,并且包含区域的层次结构中的所有区域都已启,则源启用。
如果HRESULT为S_OK,则可以忽略ppErrors(其中的所有结果都保证为S_OK)。
如果HRESULT是FAILED代码,则服务器应返回NULL的OUT指针参数及ppErrors。
调用方必须释放返回的ppErrors数组。
3)HRESULT DisableConditionByArea(
[in] DWORD dwNumAreas,
[in, string, size_is(dwNumAreas)] LPWSTR* pszAreas,
[out, size_is(,dwNumAreas)] HRESULT **ppErrors
);
将指定的过程区域置于禁用状态。服务器将停止为这些条件生成与条件相关的事件。
此方法的效果在事件服务器的范围内是全局的。如果服务器支持多个客户端,所有客户端的条件都被禁用,所有客户端将停止接收条件相关事件。
由于此方法的全局影响,一些事件服务器实现者可能会选择不实现它。在这种情况下,如果客户端通过QueryInterface请求IOPCEventServer2,服务器应该返回E_NOINTERFACE。
一个条件可能与多个来源相关。这些来源可能是分布于多个区域。禁用一个区域中的条件不会改变与其他区域中关联源的同名条件的启用/禁用状态。例如,可以为“区域1”中的源启用“LevelAlarm”条件,而在“区域2”中的源禁用“LevelAlarm”条件。
如果源的条件状态设置为禁用或包含区域的层次结构中的任何区域被禁用,则该源将被禁用。
如果HRESULT为S_OK,则可以忽略ppErrors(其中的所有结果都保证为S_OK)。
如果HRESULT是FAILED代码,则服务器应返回NULL的OUT指针参数及ppErrors。
调用方必须释放返回的ppErrors数组。
4)HRESULT DisableConditionBySource2(
[in] DWORD dwNumSources,
[in, string, size_is(dwNumSources)] LPWSTR* pszSources,
[out, size_is(,dwNumSources)] HRESULT **ppErrors
);
将指定事件源的所有条件置于禁用状态。服务器将不再为这些源生成与条件相关的事件。
此方法的效果在事件服务器的范围内是全局的。如果服务器支持多个客户端,所有客户端的条件都被禁用,所有客户端将停止接收条件相关事件。
由于此方法的全局影响,一些事件服务器实现者可能会选择不实现它。在这种情况下,如果客户端通过QueryInterface请求IOPCEventServer2,服务器应该返回E_NOINTERFAC。
一个条件可能与多个源相关联。禁用一个源相关联的条件不会改变其它源相关联的相同名称的条件的启用/禁用状态。例如,在“A100”启用“LevelAlarm”条件,在“FIC101”禁用“LevelAlarm”条件。
如果源的条件状态设置为禁用或包含区域的层次结构中的任何区域被禁用,则该源将被禁用。
如果HRESULT为S_OK,则可以忽略ppErrors(其中的所有结果都保证为S_OK)。
如果HRESULT是FAILED代码,则服务器应返回NULL的OUT指针参数及ppErrors。
调用方必须释放返回的ppErrors数组。
5)HRESULT GetEnableStateByArea(
[in] DWORD dwNumAreas,
[in, string, size_is(dwNumAreas)] LPWSTR* pszAreas,
[out, size_is(,dwNumAreas)] BOOL **pbEnabled,
[out, size_is(,dwNumAreas)] BOOL **pbEffectivelyEnabled, [out, size_is(,dwNumAreas)] HRESULT **ppErrors
);
返回pszAreas中指定的每个区域的当前启用状态和有效启用状态。
如果HRESULT为S_OK,则可以忽略ppErrors(其中的所有结果都保证为S_OK)。
如果HRESULT是FAILED代码,则服务器应返回NULL的OUT指针参数及ppErrors。
调用方必须释放返回的ppErrors数组。
6)HRESULT GetEnableStateBySource(
[in] DWORD dwNumSources,
[in, string, size_is(dwNumSources)] LPWSTR* pszSources,
[out, size_is(,dwNumSources)] BOOL **pbEnabled,
[out, size_is(,dwNumSources)] BOOL **pbEffectivelyEnabled, [out, size_is(,dwNumSources)] HRESULT **ppErrors
);
返回pszSources中指定的每个源的当前启用状态和有效启用状态。
如果HRESULT为S_OK,则可以忽略ppErrors(其中的所有结果都保证为S_OK)。
如果HRESULT是FAILED代码,则服务器应返回NULL的OUT指针参数及ppErrors。
调用方必须释放返回的ppErrors数组。
4 IConnectionPointContainer
4.1 IConnectionPointContainer
这里没有讨论连接点的一般原则,因为它们在Microsoft文档。假定读者熟悉这项技术。相关连接https://learn.microsoft.com/zh-cn/windows/win32/com/using-iconnectionpointcontainer
同样,IEnumConnectionPoints、IConnectionPoint和IEnumConnections的详细信息接口及其在本文中的正确使用由Microsoft进行了很好的定义,在此不进行讨论。
这里讨论的IConnectionPointContainer接口是在OPCEventServer上实现的对象。理论上,可以在IOPCEventServer接口内实现连接点的订阅和取消订阅的方法。然而,使用单独的ConnectionPoint实现更符合最先进的Microsoft实现。
假设客户端应用程序实现的IOPCShutdown回调对象为单个事件服务器,因为没有标识信息传递给客户端。
注:符合OPC标准的服务器不需要支持每个事件服务器之间的多个连接。鉴于此,预计单个连接将足以实现所有应用程序。因此(根据Microsoft建议)对于调用IOPCShutdown::ShutdownRequest的IConnectionPoint接口的EnumConnections方法,允许返回E_NOTIMPL。
4.2 EnumConnectionPoints
有关此方法的说明,请参阅Microsoft文档。
OPC事件服务器必须返回一个包含IOPCShutdown的枚举器。其他供应商还允许特定的回调。
4.3 FindConnectionPoint
有关此方法的说明,请参阅Microsoft文档。
OPC事件服务器必须支持IID_ IOPCShutdown。其他供应商还允许特定的回调。
4.4 IConnectionPoint
从事件服务器的ConnectionPointContainer返回IOPCShutdown的一个IConnectionPoint。有关其他信息,请参阅此界面的Microsoft文档关于其方法的信息,其中包括订阅和取消订阅。
5 OPCEventAreaBrowser
5.1 接口介绍
OPCEventAreaBrowser是OPC事件服务器提供的对象,用于管理浏览服务器的过程区域空间。此对象提供的接口包括:
• IUnknown
• IOPCEventAreaBrowser
此对象是可选的,简单事件服务器可能不支持。
IOPCEventAreaBrowser接口为客户端提供了一种浏览由服务器实现的过程区域组织结构。服务器中可用的事件和条件被组织在一个或更多过程区域中。客户端事件订阅可以通过指定过程区域来筛选服务器发送的事件通知。这些区域用于指定事件筛选器。它们在逻辑上独立于OPC数据访问接口和相关ItemID的IOPCBrowserServerAddressSpace。这个服务器地址空间和服务器过程区域空间之间的关系完全取决于服务器实现。
请注意,使其成为一组方法而不是ActiveX控件的原因是允许更容易与客户端可能已经使用的其他浏览方法和地址空间集成。
请注意,此接口的行为非常像枚举器,因为它在“后台”创建一个对象,并代表客户端维护状态信息(地址层次结构中的当前位置)。
以下是如何使用此接口的概述:
浏览位置最初设置为区域空间的“根”。
客户端可以通过调用ChangeBrowsePosition来选择起点。对于层次结构空间,客户端可以传递任何部分路径(尽管客户端通常会传递NULL字符串来指示根)。这设置了向上或向下浏览的初始位置。
客户可以通过BrowseOPCAreas浏览当前位置下面(包含在)的项目。对于层次空间,可以指定AREA(仅返回该级别上的区域)或SOURCE(仅返回那个级别上的源)。返回一个字符串枚举器。
此浏览也可以通过特定于供应商的筛选字符串进行筛选。
请注意,在层次结构中,枚举器将返回“短”字符串;“child”的名字。这些短字符串通常不足以用于事件订阅筛选器的区域列表数组。
客户端应始终通过GetQualifiedAreaName或GetQualifiedSourceName将此短字符串转换为“完全限定”字符串。例如,短字符串可能是EACTOR5;完全限定的字符串可能是AREA1.REACTOR5。
如果客户端浏览了AREA,则结果(短字符串)可能会传递给ChangeBrowsePosition以向下移动。此方法还可以向上移动,在这种情况下不使用短字符串。
5.2 接口方法
1) HRESULT ChangeBrowsePosition(
[in] OPCAEBROWSEDIRECTION dwBrowseDirection,
[in, string] LPCWSTR szString
);
提供在层次空间中从当前位置向上或向下移动的方法,或移动到区域空间树中特定位置的方法。目标szString必须表示一个区域,而不是一个源。
如果传递的字符串不表示区域,则返回错误。
从“根”上移应返回E_FAIL。
2) HRESULT BrowseOPCAreas(
[in] OPCAEBROWSETYPE dwBrowseFilterType,
[in, string] LPCWSTR szFilterCriteria,
[out] LPENUMSTRING * ppIEnumString
);
返回由传递的参数确定的区域列表的IEnumString。可以通过ChangeBrowsePosition设置完成浏览的位置。
如果没有区域或源满足筛选器约束,则返回的枚举器可能为空。枚举器返回的字符串表示当前级别中包含的区域或源。它们不包括??和分隔符或“父”名称。
客户端可以创建并持有多个枚举器,以便一次维护多个“浏览位置”。更改一个枚举器中的浏览位置不会影响客户端创建的任何其他枚举器。客户端必须在完成每个枚举器后释放它。
3) HRESULT GetQualifiedAreaName(
[in] LPCWSTR szAreaName,
[out , string] LPWSTR *pszQualifiedAreaName
);
提供一种在层次空间中汇编完全限定的区域名称的机制。这是必需的,因为在每一点上,都只浏览当前节点下方的名称。
服务器必须为IOPCEventSubscriptionMgt::SetFilter方法返回可以添加到pszAreaList的字符串,并且可以在IOPCEventAreaBrowser::ChangeBrowsePosition方法中使用以移动到过程区域空间树中的特定位置。
4)HRESULT GetQualifiedSourceName(
[in] LPCWSTR szSourceName,
[out , string] LPWSTR *pszQualifiedSourceName
);
提供一种在层次空间中汇编完全限定的源名称的机制。这是必需的,因为在每一点上,都只浏览当前节点下方的名称。
服务器必须为IOPCEventServer::EnableConditionBySource方法返回可以添加到pszSources的字符串。
6 OPCEventSubscription
6.1 接口介绍
OPCEventSubscription对象是OPC事件服务器为管理单个事件订阅而提供的对象。它是通过调用IOPCEventServer::CreateEventSubscription创建的。此对象提供以下接口:
• IUnknown
• IOPCEventSubscriptionMgt
• IConnectionPointContainer
此外,OPCEventSubscription包含一个IID_IOPCEventSink对象,该对象支持IConnectionPoint接口。
客户端和服务器之间的每个订阅都只有一个筛选器,尽管该筛选器可以包括多个条件。客户端可以使用多个订阅实现多个筛选器,每个订阅都有自己的筛选器。建立订阅后,将创建一个默认过滤器,该过滤器相当于“无过滤”,即发送所有事件通知。
定义筛选器范围的标准是消除客户端没有感兴趣的大多数事件,而不必详尽无遗。过滤器的主要原因是减少不必要的通信开销并提高性能。客户端可以对接收到的事件通知进行额外的筛选,进一步精确地自定义显示或存储事件通知。
这些接口中的每一个提供的功能都在本节中进行了定义。
IOPCEventSubscriptionMgt接口指定如何管理对OPC事件信息的特定订阅。它用于指定选择感兴趣事件的标准,指定要在事件通知中返回的供应商特定信息,以及请求刷新所选条件。
6.2 接口方法
IOPCEventSubscriptionMgt接口说明如下。
1)HRESULT SetFilter(
[in] DWORD dwEventType,
[in] DWORD dwNumCategories,
[in, size_is(dwNumCategories)] DWORD* pdwEventCategories, [in] DWORD dwLowSeverity,
[in] DWORD dwHighSeverity,
[in] DWORD dwNumAreas,
[in, size_is(dwNumAreas)] LPWSTR* pszAreaList,
[in] DWORD dwNumSources,
[in, size_is(dwNumSources] LPWSTR* pszSourceList
);
设置要用于事件订阅的筛选条件。
可以使用以下标准选择事件:
•事件类型,即简单、条件或跟踪。
•事件类别
•最低严重性,即严重性大于或等于指定严重性的所有事件。
•最高严重性,即严重性小于或等于指定严重性的所有事件。
•过程区域
•事件源
单个标准的值列表在逻辑上被“或”运算在一起(例如,如果两个事件类别指定,将接收两个类别的事件通知)。如果指定了多个标准,它们将被逻辑地“与”在一起,即仅那些满足所有标准的事件将被选择。例如,指定最低严重性和最高严重性将导致选择事件严重性介于两个值之间。
OPCEventSubscription对象只有一个筛选器。
服务器负责将其内部严重性级别映射为均匀分布在1..1000范围内。希望接收所有严重性事件的客户端应设置dwLowSeverity=1和dwHighSeverity=1000。
服务器可能不支持所有各种筛选条件。给定服务器支持的特定筛选条件可以通过IOPCEventServer::QueryAvailableFilters方法确定。如果指定了服务器不支持的筛选条件,它将忽略该筛选条件并返回S_FALSE。
注意,对于给定条件,如果与确认或恢复正常相对应的事件通知具有与该条件变为活动的事件通知不同的严重性级别,则由于按严重性进行过滤,客户端可能会接收一组通知,但不会接收其他通知。
2)HRESULT GetFilter(
[out] DWORD* pdwEventType,
[out] DWORD* pdwNumCategories,
[out, size_is(,*pdwNumCategories)] DWORD** ppdwEventCategories,
[out] DWORD* pdwLowSeverity,
[out] DWORD* pdwHighSeverity,
[out] DWORD* pdwNumAreas,
[out, size_is(,*pdwNumAreas)] LPWSTR** ppszAreaList
[out] DWORD* pdwNumSources,
[out, size_is(,*pdwNumSources)] LPWSTR** ppszSourceList
);
返回当前用于事件订阅的筛选器。
如果服务器不支持SetFilter中请求的一个或多个筛选条件,它将为列表返回空数组,并返回指示未对非列表项进行筛选的值。在这些情况下,它不会返回SetFilter中可能已请求但已被忽略的任何筛选器。
3)HRESULT SelectReturnedAttributes(
[in] DWORD dwEventCategory,
[in] DWORD dwCount,
[in, size_is(dwCount)] DWORD* dwAttributeIDs,
);
对于每个事件类别,SelectReturnedAttributes设置要随IOPCEventSink::OnEvent回调中的通知事件一起返回的属性。
可以多次调用此方法,以便为每个唯一值指定要返回的属性事件类型和事件类别对。对于给定的事件类型和事件类别对,属性可以通过将dwCount参数设置为零来“清除”返回的值。如果多次调用相同的事件类型和事件类别对,则最新的调用将生效。
4)HRESULT GetReturnedAttributes(
[in] DWORD dwEventCategory,
[out] DWORD * pdwCount,
[out, size_is(,pdwCount)] DWORD* pdwAttributeIDs
);
对于每个事件类别,GetReturnedAttributes检索当前指定要在IOPCEventSink::OnEvent回调中随事件通知返回的属性。所有检索到的属性都已由以前对SelectReturnedAttributes的调用指定。
5)HRESULT Refresh(
[in] DWORD dwConnection
);
强制刷新事件通知与事件订阅的筛选器匹配的所有活动条件和非活动、未确认的条件。
客户端通常需要从服务器获取当前状况信息,尤其是在客户端启动时,例如当前警报摘要。OPC事件服务器通过重新发送最新的事件通知来支持这一要求,这些事件通知满足事件订阅中的过滤器,并且与活动和/或未确认的条件有关。然后,客户端可以从“刷新”的事件通知中导出当前条件状态。
当客户端需要刷新的活动条件列表时,它将向服务器请求“刷新”。服务器将向该特定客户端发送事件通知,指示它们正在“刷新”“原始”事件通知的数量。由于客户端只需要获取的当前状态信息条件,则只刷新条件事件。注意:“刷新”不是一般的“重播”功能,因为服务器不需要维护事件历史记录。刷新仅用于更新客户端的活动或未确认条件的状态信息。
除了刷新指示符之外,原始事件通知和刷新事件通知之间还可能存在其他差异。具体地,由于在原始事件通知时可用的一些属性信息在刷新时可能不可用,因此刷新中的一些属性可能为空。
刷新事件通知和原始事件通知不会混合在事件回调的同一调用中,尽管刷新和原始事件回调调用可能是交错的。因此,客户端有责任检查事件通知上的时间戳,并将其按正确的顺序排列,以确保获得正确的条件状态。
根据IOPCEventServer::CreateEventSubscription方法中的规范,客户端每次回调将接收最大数量的事件通知。发送刷新事件通知时,服务器将指示是否还有更多刷新事件通知要发送。
此方法仅适用于与条件相关的事件。简单事件和跟踪事件的通知不会返回,即使它们满足事件订阅的过滤器。
此方法在订阅处于活动状态和非活动状态时都适用。
6)HRESULT CancelRefresh(
[in] DWORD dwConnection
);
取消正在进行的事件订阅刷新。
如果正在进行刷新,服务器应该发送一个最后的回调,其中设置了最后一个刷新标志,并且事件数等于零。
7)HRESULT GetState(
[out] BOOL * pbActive,
[out] DWORD * pdwBufferTime,
[out] DWORD * pdwMaxSize,
[out] OPCHANDLE * phClientSubscription,
);
获取订阅的当前状态。客户端将指针传递到要保存信息的位置。
在调用SetState之前,通常会调用此函数以获取此信息的当前值。这些信息都是在创建订阅时由客户端提供的。此函数对于调试也很有用。
8)HRESULT GetState(
[out] BOOL * pbActive,
[out] DWORD * pdwBufferTime,
[out] DWORD * pdwMaxSize,
[out] OPCHANDLE * phClientSubscription,
);
获取订阅的当前状态。客户端将指针传递到要保存信息的位置。
在调用SetState之前,通常会调用此函数以获取此信息的当前值。这些信息都是在创建订阅时由客户端提供的。此函数对于调试也很有用。
9)HRESULT SetState(
[unique, in] BOOL * pbActive,
[unique, in] DWORD * pdwBufferTime,
[unique, in] DWORD * pdwMaxSize,
[in] OPCHANDLE hClientSubscription
[out] DWORD * pdwRevisedBufferTime,
[out] DWORD * pdwRevisedMaxSize
);
客户端可以设置事件订阅的各种属性。使用指向项的指针,这样客户端就可以通过传递空指针来省略他不想更改的属性。
请注意,将事件订阅设置为“活动”并不意味着服务器应该对客户端进行隐式刷新。发送“刷新”事件的唯一时间是直接响应客户端对refresh()的显式调用。这与OPC数据访问接口不同,在OPC数据访问界面中,使项目处于活动状态会导致服务器进行隐式刷新。
7 IOPCEventSubscriptionMgt2
可选的IOPCEventSubscriptionMgt2接口用于设置/获取订阅当订阅的保活时间为非零时,服务器将确保客户端即使在没有要报告的新事件,通过以最小已知频率提供回调,客户端可以确保服务器和订阅的健康,而无需“ping服务器”(定期调用服务器方法的常见做法,例如IOPCEventServer::GetStatus())。
IOPCEventSubscriptionMgt2的方法如下:
1)HRESULT SetKeepAlive(
[in] DWORD dwKeepAliveTime,
[out] DWORD *pdwRevisedKeepAliveTime
);
客户端可以设置订阅的保持活动时间,以使服务器在没有要报告的新事件时提供订阅的客户端回调。然后,客户端可以确保服务器和订阅的健康,而无需通过调用GetStatus()来ping服务器。
使用此功能,客户端可以期望在指定的保活时间内进行回调(数据或保活)。
当发送真实数据时,服务器应重置其保活定时器(即,无论数据回调如何,都不能在等于保活时间的固定周期内不断发送保活回调)。
keep-alive回调包含对IOPCEventSink::OnEvent()的调用,dwCount设置为零。
当订阅处于非活动状态时,将不会发生保持活动回调。
保持活动回调不会影响IOPCEventServer::GetStatus()返回的OPCEVENTSERVERSTATUS::ftLastUpdateTime的值。
2)HRESULT GetKeepAlive(
[out] DWORD *pdwKeepAliveTime
);
返回订阅的当前活动保持活动时间。
8 IOPCEventSink
8.1 接口介绍
为了使用连接点,客户端必须创建一个同时支持IUnknown以及IOPCEventSink接口。客户端将传递一个指向IUnknown接口的指针(而不是IOPCEventSink)转换为事件订阅中正确IConnectionPoint的Advise方法(如从IConnectionPointContainer::FindConnectionPoint或EnumConnectionPoints获得)。这个事件服务器将调用客户端对象上的QueryInterface以获取IOPCEventSink接口。注意:事务必须以这种方式执行才能使接口编组工作适用于本地或远程服务器。
事件服务器调用OnEvent方法向客户端通知满足特定事件订阅的筛选条件的事件。
客户端只需要提供OnEvent的完整实现。IOPCEventSink没有其他方法。
请注意,回调可能有两个原因:事件通知或刷新。可以编写一个服务器,使其并行执行其中的几个操作。在这种情况下,客户端可以通过检查OnEvent回调中的bRefresh参数来确定特定回调的“原因”。
8.2 IOPCEventSink方法
1)HRESULT OnEvent(
[in] OPCHANDLE hClientSubscription,
[in] BOOL bRefresh,
[in] BOOL bLastRefresh,
[in] DWORD dwCount,
[in, size_is(dwCount)] ONEVENTSTRUCT* pEvents
);
此方法由客户端提供,用于处理来自OPCEventSubscription的事件通知。无论是刷新还是标准事件通知,都可以调用此方法。
客户端从该函数返回后,服务器需要释放pEvents。
此外,根据COM规范,客户端在回调中允许哪些函数方面受到限制。例如,不能调用阻塞函数。
回调可能由于以下原因之一而发生:
• 发生了一个或多个新事件。
• 这是对刷新的响应。
8.3 IOPCShutdown
为了使用此连接点,客户端必须创建一个同时支持IUnknown和IOPCShutdown接口。客户端将传递一个指向IUnknown接口的指针(而不是IOPCShutdown)转换为服务器中正确的IConnectionPoint的Advise方法(从IConnectionPointContainer::FindConnectionPoint或EnumConnectionPoints)。服务器将调用对客户端对象执行QueryInterface以获取IOPCShutdown接口。请注意,交易必须以这种方式执行,才能使接口编组在本地或远程服务器。
当事件服务器需要关闭时,将调用此接口上的ShutdownRequest方法。客户端应该释放此事件服务器的所有连接和接口。
连接到多个服务器(例如事件服务器和/或其他服务器,例如来自一个或多个供应商的数据访问服务器)的客户端应该为每个对象维护单独的关闭回调,因为任何服务器都可以独立于其他服务器关闭。
8.4 IOPCShutdown方法
1)HRESULT ShutdownRequest (
[in, string] LCPWSTR szReason
);
此方法由客户端提供,以便服务器可以请求客户端断开与服务器的连接。客户端应取消所有连接并释放所有接口。
关闭连接点是基于“每个服务器对象”的。也就是说,它与CoCreate创建的对象有关…如果一个客户端连接到多个服务器对象,那么它应该单独监控每个对象(使用单独的回调)以获取关闭请求。