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
类模型的流程。
图
8
操作
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);