基于OPC规范的客户应用程序实现


1 OPC的基本结构

OPC 由两套接口组成: OPC 定制接口和 OPC 自动化接口,如图 1 所示。 OPC 服务器必须实现定制接口,可选择实现自动化接口。这两套标准接口的制定极大地方便了服务器和用不同语言开发的客户应用之间的通信,使用户对开发工具的选择有了较大的自由。
 
OPC 接口可以潜在地应用在许多应用程序中。它们可以用于从最低层设备中读取未加工的数据,再转化至 SCADA 或者 DCS 系统;也可以用于从 SCADA 或者 DCS 系统中采集数据输入到应用程序中。 OPC 是为从某一网络节点中的某一服务器中采集数据而设计的,同时又能够形成 OPC 服务器。该服务器允许客户应用软件在由许多不同的 OPC 供应商提供的服务器中传输数据,并可通过单一的对象在不同的节点上运行
 
2  OPC定制接口
C C ++编写 OPC 客户应用程序时可以使用定制接口,也可以使用自动化接口。由于定制接口具有更高的性能,建议尽可能使用定制接口。本文在 VC 下实现的客户应用程序采用的是 OPC 定制接口 1.0 OPC 定制接口的类模式可以根据其接口及其方法划分为 3 组,依次呈包含关系.
 
2.1  OPC Server对象
OPC Server OPC 启动服务器,通过它获得其他对象和服务的起始类,并用于返回 OPC Group 类对象。 OPC Server 级别有多种属性,其中包含一个 OPC 服务器对象的状态和版本等信息。这种级别中的对象由客户应用创建。 IOPCServer 接口包含管理 OPC Group 级别中的对象的方法。如将组加入服务器或从服务器中删除组的方法( " AddGroup " " RemoveGroup " )。 IOPCBrowseServerAddressSpace 接口包含查找服务器地址空间的方法。 IOPCCommon 接口方法用于通知服务器语言的设置和客户机的名称。同时还存在以下接口:图 4 说明了 OPC Server 对象及其定制接口。
4  OPC Server 对象
2.2  OPC Group对象
OPC Group 存储由若干 OPC Item 组成的 Group 信息,并用于返回 OPC Item 类对象。 OPC Group 级别管理被称为 OPC Item 的各个过程变量。 IOPCItemMgt 接口提供将项加入组或从组中删除项的方法( " AddItem " " RemoveItem " )。 IOPCGroupStateMgt 接口的方法用于处理组专用的参数或复制组。同时还存在以下接口:图 5 说明了 OPC Group 对象及其定制接口。
5  OPC Group 对象
2.3  OPC Item对象
    OPC Item 存储具体 Item 的定义、数据值、状态值等信息。 OPC Item 级别的一个对象代表与一个过程变量的连接。该对象的唯一接口是 OPCItemDisp 。关于 OPC Item 的信息可以在属性表中找到,例如数值( " Value " )属性或存取路径( " AccessPath " )属性。图 6 说明了 " OPC Item " 对象及其接口。
6  OPC Item 对象
由于本文使用定制接口实现 OPC 客户应用程序,所以不使用 IOPCItemDisp 接口,而是使用枚举器对象 EnumOPCItemAttributes IEnumOPCItemAttributes 接口枚举服务器中的所有 OPC Item 。如图 7 所示。
7  EnumOPCItemAttributes 对象
3 OPC客户应用程序的实现
3.1  操作OPC的类模型
按照 OPC 的类模型,当对象方法调用 OPC 对象时必须遵循一定的顺序。如果要创建一个 OPC Item 类的实例,则首先需要一个 OPC Group 对象。而要创建一个 OPC Group 对象的前提是存在一个 OPC Server 类的实例,并建立一个与该服务器的连接。图 8 说明了操作 OPC 类模型的流程。
操作 OPC 类模型的流程
3.2  编程顺序
本文在 Visual C++ 环境中实现的 OPC 客户应用程序包括所有通常在典型客户应用下都会有的部分,如:建立与服务器的连接,初始化变量的组,以及为一个项读写数据。下面详细介绍一下在 VC 环境下 OPC 应用的基本结构。
第一步:登陆 COM
如果程序要调用 COM 库的某一函数,必须先登陆 COM 。函数 CoInitialize() 可以完成此功能。从函数 CoGetMalloc() 可以得到一个指向 COM 内存管理接口的指针。
HRESULT r1;
r1= CoInitialize(NULL);
r1= CoGetMalloc(MEMCTX_TASK,&g_pIMalloc);
第二步:将 ProgID 变换为 CLSID
每个 COM 服务器有一个字符串类型的 ProgID ,通过它可以得到一个全球唯一的 CLSID 。用 CLSIDFromProgID() 函数可以实现这个转换。 ProgID 用变量 szName 进行参数传递。
r1= CLSIDFromProgID(szName,&clsid);
第三步:建立与 OPC 服务器的连接
CoCreateInstance() 函数创建一个 OPC Server 类实例,其 CLSID 值设定如下。
r2=CoCreateInstance(clsid,NULL,CLSCTX_LOCAL_SERVER,IID_IUnkown,(void**)&pUNK);
这段程序的结果是得到一个指向服务器对象 IUnkown 接口的指针(变量 pUNK )。
第四步:请求其它接口指针
IUnkown 接口,通过 QueryInterface() 方法可以得到其它接口的指针。
HRESULT r3;
r3=punk->QueryInterface(IID_IOPCServer,(void**)&m_pOPC);
这段程序的结果是得到一个指向服务器对象 IOPCSever 接口的指针(变量 m_pOPC )。
第五步:创建 OPC
IOPCServer 接口的 AddGroup() 方法可以创建 OPC 组。
HRESULT r1;
r1=m_pOPC->AddGroup(szName,TRUE,500,&TimeBias,&PercDeadband,dwLCID,&m_GrpServerHandle,
&RevUpRate,IID_IOPCItemMgt,(LPUNKNOWN*)&m_pItemMgt);
这段程序的执行结果是创建一个有指定名称和属性的组。在返回的参数中,有一个指向所需要的进程组对象 IOPCItemMgt 接口的指针(变量 m_pItemMgt )。
第六步:添加项
IOPCItemMgt 接口的 AddItems() 方法可以添加 OPC 项。
HRESULT r1;
r1=m_pItemMgt->AddItems(NumItems,pItems,&m_pItResult,&pErrors);
这段程序的结果是添加具有特殊属性的指定数量的项。除此之外,事件结构变量 m_pItResult (服务器句柄,目标系统上的项数据类型等)也被赋值。
第七步:用 OPC 项执行所需的操作
用于执行所需操作的指针需要通过现有的指向 IOPCItemMgt 接口的指针得到。如:如果用户要进行异步通信,就需要指向 IOPCAsyncIO 接口的指针。
HRESULT r1;
r1=m_pItemMgt->QueryInterface(IID_IOPCAsyncIO,(void**)&pAsyncIO);
通过该接口的 Read() Write() 两个方法,就可以读写项的数值。
HRESULT r2;
r2=pAsyncIO->Read(m_dwConnection,OPC_DS_CACHE,dwNumItems,phServer,&m_TransactionID,
&pErrors);
这段程序的执行结果是, OPC 项的数据被送到客户程序的 IAdviseSink 接口。
HRESULT r3;
r3=pAsyncIO->Write((m_dwConnection,dwNumItems,phServer, pItemValues,&m_TransactionID,&pErrors);
这段程序的执行结果是, OPC 服务器代替 OPC 客户刷新物理设备的数据。
第八步:删除对象,释放内存
在程序停止运行之前,必须删除已创建的 OPC 对象并释放内存。到目前为止,用到的接口都有相应的函数。
r1=m_pItemMgt->RemoveItems(dwNumItems,phServer,&pErrors);
r1=m_pOPC->RemoveGroup(m_GrpServerHandle,TRUE);
m_pItemMgt->Release();
m_pOPC->Release();
3.3  异步通信的说明
OPC 客户和 OPC 服务器进行数据交换可以有两种不同的方式,即同步方式和异步方式。同步方式实现较为简单,当客户数目较少而且同服务器交互的数据量也比较少的时候可以采用这种方式;异步方式实现较为复杂,需要在客户程序中实现服务器回调函数。然而当有大量客户和大量数据交互时,异步方式的效率更高,能够避免客户数据请求的阻塞,并可以最大限度地节省 CPU 和网络资源。本文中用 VC 实现的 OPC 客户程序可以执行异步读写数据,异步意味着程序继续执行后面的操作,只要读或写的任务送达马上申请读写,并由 OPC 服务器返回回调函数的执行结果。为了实现异步通信,客户程序必须提供 IAdviseSink 接口与服务器方的 IDataObject 接口通信。下面详细描述一下用 VC 实现 OPC 客户应用程序中异步通讯的基本步骤。
第一步:得到指向 IDataObject 接口的指针
可以用这个接口在客户和服务器之间建立连接。为此目的,可以在程序中创建由 IAdviseSink 接口派生的 COPCData 类,并建立一个此类的实例 m_pOPCIData ,指向 IDataObject 接口的指针临时保存在这个类的 m_pDataObject 成员变量中。
m_pOPCIData=new COPCData(pWnd);
..........
r1=m_pItemMgt->QueryInterface(IID_IDataObject,(LPVOID*)&m_pOPCIData->m_pDataObject);
第二步:取得指向 IAdviseSink 接口的指针
客户必须能够和服务器建立联系,借此可以收到服务器的通知。 IAdviseSink 接口可以完成这个任务。通过 QueryInterface() 方法可以得到这个接口的指针并把它赋给变量 pAdviseSink
m_pOPCIData-> QueryInterface(IID_IAdviseSink,(LPVOID*)&pAdviseSink);
第三步:建立连接
如果有关接口都存在,就可以建立连接。这可由 IDataObject 接口的 DAdvise() 方法完成,在这个方法中指向 IAdviseSink 接口的指针被传递给服务器。
r1=m_pOPCIData->m_pDataObject->DAdviseSink(&formatEtc,ADVF_PRIMEFIRST,pAdviseSink,
&m_dwConnection);
第四步:收到服务器的通知
如果数据发生变化,服务程序将调用 IAdviseSink 接口的 OnDataChange() 方法。这个方法是在客户应用程序中执行的。 OPC 项的实际数据被赋值给用户定义的成员变量 m_ItemValues, 这就意味着在客户应用程序中可以获得这些想要的数据。
r2=VariantChangeType(&(g_pOPCServer->m_ItemValues[hItClient]),&Value,0,VT_BSTR);
第五步:显示读出的数据
在这里, OnDataChange() 方法发送 Windows 消息 WM_DATA_CHANGE 启动下一步的处理。
SendMessage(*m_pWnd,WM_DATA_CHANGE,0,0);
通过在 OPC 客户应用程序的显示程序中定义消息映射,如果收到上述消息,则调用 OnDataChange() 事件过程。
ON_MESSAGE(WM_DATA_CHANGE,OnDataChange);
这个事件过程很简单,它只是调用用户自定义的 ItemsView() 函数在 OPC 客户应用程序的显示界面上显示实际的数据。
ItemView(g_pOPCServer->m_pItAttr,g_pOPCServer->m_ItemValues,4);
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值