OPC客户端应用程序的C++实现
在Visual C++或C++BUILDER环境中实现OPC客户应用程序,首先必须从OPC国际基金会官方网站下载OPC头文件("opcda_i.c"、"opcda.h" 、"opccomn_i.c"、"opccomn.h"),并在Visual C++工程的“Tool”→“Options”→“Directories”加载头文件。然后再进行登陆COM、连接服务器、数据读写等操作。在实际开发中,本文创建一个OPC通信类COPCComm,在需要通信的地方定义COPCComm类对象,然后进行相应的读写操作,其实现流程如图6所示:
下面详细介绍通信类COPCComm的创建过程,并给出关键源代码:
1 创建OPCComm.h
第一步:在OPCComm.h中预定义_WIN32_DCOM,
#ifndef _WIN32_DCOM
#define _WIN32_DCOM
#endif
包含如下头文件:"opcda_i.c"、"opcda.h"、 "opccomn_i.c"、"opccomn.h"
第二步:申明Iunknown、IOPCServer、IOPCItemMgt、IOPCSyncIO等关键变量为公共变量(public):
字串8
IUnknown *pUnknown;
IOPCServer *pServer;
IOPCItemMgt *pOPCItemMgt;
IOPCSyncIO *pOPCSync;
HRESULT *pErrors;
3.4.2创建OPCComm.cpp
第一步:在构造函数COPCComm::COPCComm()中登录COM。
函数CoInitislize()可以完成此功能。从函数CoGetMalloc()得到一个指向COM内存管理接口的指针。
HRESULT rl;
rl =CoInitialize(NULL);
rl =CoGetMalloc(MEMCTX_TASK,&g_pIMalloc);
第二步:添加函数HRESULT COPCComm::ConnectToServer(LPOLESTR ProgID, BOOL IsRemote, IUnknown **ppUnknown),并在函数中实现两个功能:
(1) 将ProgID变换CLSID,每COM服务器有一个字符串类型的ProgID,通过它可以得到全球唯一CLISID。用CLSIDFromProgID()函数可以实现该转换。
CLSID OPCCLSID;
HRESULT hRet=CLSIDFromProgID(ProgID,&OPCCLSID);
//如本系统中ProgID的值是“RSI.RSView 32OPCTagServer”。
(2) 建立与OPC服务器的连接,CoCreateInstance()函数创建一个OPC Sever类实例,其CLSID值设定如下:
hRet=CoCreateInstance(OPCCLSID,NULL,CLSCTX_LOCAL_SERVER,IID_IUnknown,(void **)ppUnknown);
字串2
return hRet;
该段程序的结果是得到一个指向服务器对象Iunkown接口的指针变量ppUnknown。
第三步:添加函数int COPCComm::Initial Communication(),并在函数中实现如下几步:
(1) 请求其他接口指针:
从Iunkown接口,通过QueryInterface()方法得到一个指向服务器对象IOPCSever接口的指针变量pServer:
hRet=pUnknown->QueryInterface(IID_IOPCServer,(void **)&pServer);
// 得到一个指向服务器对象IOPCSVerser接口的指针(变量pServer)。
(2) 创建OPC组,
用IOPCSever接口方法AddGroup()实现:
hRet=pServer->AddGroup(L"",TRUE,500,1235,&lTimeBias,&fTemp,0,&hOPCServerGroup, &dwActualRate,IID_IUnknown,&pUnknown);
// 创建一个有指向名称和属性的组。在返回的参数中,有一个指向所需要的进程组对象IOPCItemMgt接口的指针(变量pUnknown )。
(3) 添加项:用IOPCItemMgt接口的AddItems()方法添加具有特殊属性的指定数量的项:
hRet=pOPCItemMgt->AddItems(ItemNumber,ItemArray,(OPCITEMRESULT**)&pItemResult,(HRESULT **)&pErrors);
(4) 用OPC项执行所需的操作,本系统采用同步通信,就需要指向IOPCSyncIO接口指针。
字串8
hRet=pUnknown->QueryInterface(IID_IOPCSyncIO,(void **)&pOPCSync);
第四步:添加COPCComm::ReadFromRsview(int AIItemNumber, float fData【】),实现读取数据。
HRESULT hRet;
hRet=pOPCSync->Read(OPC_DS_CACHE, AIItemNumber, hServerAI, &pItemValue, &pErrors);
// OPC项的数据被送到客户程序的IadviseSink接口。
for(int i=0;i
fData【i】=V_R4(&pItemValue【i】.vDataValue);
其中,AIItemNumber为一次读入数据的总个数,pItemValue为服务器保存数据的数组,fData为客户端读取数据的数组。
第五步:添加COPCComm::WriteRsview(CString strSend, int iMark),实现数据写入。
COleVariant WriteValue;
HRESULT hRet;
WriteValue = strSend;// strSend为待写的数据
WriteValue.ChangeType(VT_R4);
hRet=pOPCSync->Write(1, &hServerAO【iMark】, WriteValue, &pErrors);
//写一个值,iMark表示执行器标号,可由用户自己定义。
第六步:在析构函数~COPCComm()中销毁对象,释放内存,在程序停止运行之前,必须删除已创建的OPC对象并释放内存。
pOPCSync->Release(); 字串9
pOPCItemMgt->Release();
pServer->Release();
pUnknown->Release();
创建完通讯类后,则可以在需要通信的地方,调用相应的函数(方法)即可。
4 结束语
OPC规范把硬件供应系统和软件开发者分离开来,使得软件开发者不需要过多的了解硬件的实质和操作过程,只要遵循OPC规范进行开发,就可以访问OPC服务器的数据。本文介绍的VC++环境下的客户端应用程序,能够发挥OPC的最佳性能,完全满足过程控制领域对数据实时、高效的要求。