现在的位置: 首页 > 综合 > 正文

VC读写OPC服务器数据小析-01

2012年11月25日 ⁄ 综合 ⁄ 共 13569字 ⁄ 字号 评论关闭

 

我机器上安装了Wincc OPC Server,KEPServerEx4.0等。

一.导入OPC官方文件。

我首先在VC6.0中建立一个基于对话框的项目,在项目中加入了4个文件

   "opcda_i.c"    OPC数据存取接口
   "opcda.h"      OPC数据存取2.0头文件
   "opccomn_i.c"  OPC公共接口定义
   "opccomn.h"    OPC公共头文件

文件可以从OPC基金会网站(网址:www.opcfoundation.org)

我把头文件写进了工程的stdafx.h文件中如下:

//opc官方sdk文件
#include "opcda_i.c"
#include "opcda.h"
#include "opccomn_i.c"
#include "opccomn.h"
这些都写在其他包含语句的后面。

 

二.声明几个要用到的OPC接口等成员变量

  建立了一个类COPCComm

  声明了几个要用的接口:

 IUnknown *m_pUnknown;
 IOPCServer *m_pServer;
 IOPCItemMgt *m_pOPCItemMgt;
 IOPCSyncIO *m_pOPCSync;
 IConnectionPointContainer *m_pIConnPtContainer;

 CWYDataSink20 *m_pWYDataSink20; //自己参照一个例子写的类,用来测试IOPCDataCallback

                                                                       //中数据改变事件函数,从而实现OPC服务器重数据改变时

                                                                       //才改变我这个客户端所显示的数据。
 HRESULT *m_pErrors;

 OPCHANDLE m_hServer;

 

三.连接到OPC服务器

1)

/*********************
//函数说明:连接到OPC服务器
//创建日期:2011-11-11
***********************/
int COPCComm::Connect()
{
 HRESULT hr;
//---初始化COM
 hr = CoInitializeEx(NULL,COINIT_MULTITHREADED);
 if(FAILED(hr))
 {
  return hr;
 }

//---连接指定的服务器
 CString strProgID= "KEPware.KEPServerEx.V4";
                               // "OPCServer.WinCC";

                               // 这些服务器的ID可以通过OPC提供的接口

                               //通过编程方式显示,也可在KEPServerEx4.0的Quick client中看到

 //把字符串形式的对象标识转化为CLSID结构形式,其实好像有多种方法可以转换。
 WCHAR wszProgID [512];
    _mbstowcsz (wszProgID, strProgID, sizeof (wszProgID) / sizeof (WCHAR));
 
 BSTR bstrProgID = strProgID.AllocSysString();
 LPOLESTR polestrProgID = bstrProgID;
 int iFlag = -1;
 iFlag = this->ConnectToServer(polestrProgID,FALSE,&m_pUnknown);
 if(iFlag!=0)
 {
  return iFlag;
 }
 
 return 0;
}

 

2)继续上面的连接

//*************************************************************************
//函    数:ConnectToServer
//所属类名:COPCComm
//输    入:
//输    出:
//功能描述:建立与指定OPC服务器的连接
//全局变量:
//调用模块:CWYOPCClientDlg
//作    者:吴毅
//日    期:2011年11月15日
//修 改 人:吴毅
//日    期:
//版    本:
//*************************************************************************
int COPCComm::ConnectToServer(LPOLESTR ProgID, BOOL IsRemote, IUnknown **ppUnknown)
{
//--------------------
 //--将ProgID变换CLSID,每COM服务器有一个字符串类型的ProgID,
 // 通过它可以得到全球唯一CLISID。用CLSIDFromProgID()函数可以实现该转换。
 //  本系统中ProgID的值是“KEPware.KEPServerEx.V4”。
 CLSID OPCCLSID;
 HRESULT hr=CLSIDFromProgID(ProgID,&OPCCLSID); //windows API
 if(FAILED(hr))
 {
  CoTaskMemFree(&OPCCLSID);     //COM 内存释放函数
  CoUninitialize();             //终止COM库功能服务
  return 16;                    //获取clsid失败
 }

 

//--------------------
 //--创建Server实例
 hr=CoCreateInstance(
  OPCCLSID,//[in]
  NULL, //[in]
  CLSCTX_LOCAL_SERVER, //[in]
  IID_IUnknown, //[in]
  (void **)ppUnknown //[out]
      );
 if(FAILED(hr))
 {
//  CoTaskMemFree(&OPCCLSID);
//  if(ppUnknown) ppUnknown->Release();//据说这里要释放
//  ppUnknown = NULL;
  CoUninitialize();
  return 32;                   //创建Server实例失败
 }

 

//--------------------
 //--请求服务器接口指针:
 // 从IUnkown接口,通过QueryInterface()方法
 // 得到一个指向服务器对象IOPCSever接口的指针成员变量m_pServer:
 hr=m_pUnknown->QueryInterface(IID_IOPCServer, //[in]
         (void **)&m_pServer //[out]
         );
 if(FAILED(hr))
 {
  if(m_pServer) m_pServer->Release();
  if(m_pUnknown) m_pUnknown->Release();
  m_pServer = NULL;
  m_pUnknown = NULL;
  return 64;                   //查询IOPCServer接口失败
 }

 return 0;
}

三.创建组,注册数据变化回调,增加项

//*************************************************************************
//函    数:InitCommunication
//所属类名:COPCComm
//输    入:
//输    出:
//功能描述:与OPC服务器建立通信
//全局变量:
//调用模块:CWYOPCClientDlg
//作    者:吴毅
//日    期:2011年11月15日
//修 改 人:吴毅
//日    期:
//版    本:
//*************************************************************************
int COPCComm::InitCommunication()
{
 HRESULT hr;
//--------------------
 //--创建OPC组
 LONG lTimeBias = 0;
 FLOAT fTemp = 0.00;
 OPCHANDLE hOPCServer;
 DWORD dwActualRate = 0;
 hr=m_pServer->AddGroup(
  L"",// [in] group name
  TRUE,// [in] active state
  10,// [in] requested update rate
  1235,// [in] our handle to this group
  &lTimeBias,// [unique,in] time bias
  &fTemp,// [in] percent deadband
  0,// [in] requested language ID
  &hOPCServer,// [out] server handle to this group
  &dwActualRate,// [out] revised update rate
  IID_IUnknown,// [in] REFIID riid,
  &m_pUnknown// [out, iid_is(riid)] LPUNKNOWN *pUNKgroup
  );
 if(FAILED(hr))  //加入组失败
 {
  CoTaskMemFree(&hOPCServer);
  CoTaskMemFree(&dwActualRate);
  if(m_pUnknown) m_pUnknown->Release();
  m_pUnknown=NULL;
  return 1;
 }

//--------------------
 //--注册数据变更回调接口
 SetDataChangeCallBack();

//--------------------
 //--查询IOPCItemMgt接口
 // 添加项:用IOPCItemMgt接口的AddItems()方法添加具有特殊属性的指定数量的项:
 ASSERT (m_pOPCItemMgt == NULL);
 hr = m_pUnknown->QueryInterface (
  IID_IOPCItemMgt, (void **) &(m_pOPCItemMgt));
 if(FAILED(hr))
 {
  CoTaskMemFree(&hOPCServer);
  CoTaskMemFree(&dwActualRate);
  if(m_pUnknown) m_pUnknown->Release();
  m_pUnknown=NULL;
  if(m_pOPCItemMgt) m_pOPCItemMgt->Release();
  m_pOPCItemMgt = NULL;
  return 2;  
 }

//--------------------
 //---增加项
 DWORD dwItemNumber = 1;​    //我这里只读去一个做下测试
 OPCITEMDEF itemArray;
 OPCHANDLE hitem;

//这里的ID要和你服务器上的一致哦

 CString strItemID=//"Q04";
                   ​    ​    ​    "Channel_0_User_Defined.Ramp.Ramp1";
                  ​    ​    ​     //"_System._Time";// "Channel_0_User_Defined.Ramp.Ramp1";
 //要读取到组的项ID 
 BSTR bstrItemID = strItemID.AllocSysString();

 itemArray.szAccessPath = NULL;
 itemArray.szItemID = bstrItemID;
 itemArray.bActive = true; // active state
 itemArray.hClient = hitem; // our handle to item
 itemArray.dwBlobSize = 0; // no blob support
 itemArray.pBlob = NULL;
 itemArray.vtRequestedDataType = VT_I4;

// pItemArray = (OPCITEMDEF *) CoTaskMemAlloc (sizeof (OPCITEMDEF));
 OPCITEMRESULT *pItemResult = NULL;
 HRESULT *pErrors = NULL;
 hr=m_pOPCItemMgt->AddItems(1,&itemArray,
  &pItemResult,&pErrors);

 if(FAILED(hr))
 {
  if(pItemResult) CoTaskMemFree(pItemResult);
  if(pErrors) CoTaskMemFree(pErrors);
  CoTaskMemFree(&hOPCServer);
//  CoTaskMemFree(itemArray);
 }
 m_hServer = pItemResult->hServer;

 //--用OPC项执行所需的操作
 // 本系统采用同步通信,就需要指向IOPCSyncIO接口指针。
 hr=m_pOPCItemMgt->QueryInterface(IID_IOPCSyncIO,(void **)&m_pOPCSync);

 return 0;
}

四.测试读取

​    读取数据有多种方式,我这次试验了使用数据变更回调和同步读取两种方式。

1.同步读取

在上面已经得到了一个m_pOPCSync,利用它来同步读取数据。

//*************************************************************************
//函    数:ReadFromServer
//所属类名:COPCComm
//输    入:
//输    出:
//功能描述:读取服务器数据
//全局变量:
//调用模块:CWYOPCClientDlg
//作    者:吴毅
//日    期:2011年11月15日
//修 改 人:吴毅
//日    期:
//版    本:
//*************************************************************************
int COPCComm::ReadFromServer(int iAIItemNumber, CString *pstrValue)
{
 HRESULT hr;
//--------------------
 //---增加项
 DWORD dwItemNumber = 1;
 OPCITEMSTATE *pItemValue;
 hr=m_pOPCSync->Read(
  OPC_DS_CACHE,
  dwItemNumber,
  &m_hServer, //这个前面得到的
  &pItemValue,
  &m_pErrors);//这个是类声明的成员变量

 if(FAILED(hr))
 {
//  if (m_hServer) CoTaskMemFree (m_hServer);
  if (pItemValue) CoTaskMemFree (pItemValue);
  if (m_pErrors) CoTaskMemFree (m_pErrors);
  VariantClear (&pItemValue[0].vDataValue);
  return 2;   //同步读数据时出错
 }

 //  数据转换-方式1
// VARIANTARG vtItemValue = pItemValue[0].vDataValue;
// this->VarToStr(vtItemValue,pstrValue);

 //  数据转换-方式2
 VARIANT vtValue = pItemValue[0].vDataValue;
 this->VariantToStr(&vtValue,pstrValue);  //这个转变函数可以自己写

......

 // 读取完后清除值
 VariantClear (&pItemValue[0].vDataValue);
 
  return 0;

}

向上面这样把读取的值转换成CString形式后就可以显示在对话框中的CEdit控件中了。

 

2.数据变更回调

​    当OPC服务器中你请求的数据(前面在新增项时设定的)值变化时,就会OPC机制

就会触发响应的事件响应函数,利用这个函数来使CEdit中的显示变化。

​    前面InitCommunication()中调用了一个函数SetDataChangeCallBack();

如下:代码参考了其他资料

//*************************************************************************
//函    数:SetDataChangeCallBack
//所属类名:COPCComm
//参    数:
//返 回 值:void
//功能描述:对组设置数据改变通知
//全局变量:
//调用模块:COPCComm
//作    者:吴毅
//日    期:2011年11月17日
//修 改 人:吴毅
//日    期:2011年11月17日
//版    本:
//*************************************************************************
void COPCComm::SetDataChangeCallBack()
{
 // Get IID_IConnectionPointContainer interface:
 HRESULT hr;
 ASSERT (m_pIConnPtContainer == NULL);
 hr = m_pUnknown->QueryInterface (
  IID_IConnectionPointContainer, (void **) &(m_pIConnPtContainer));
 
 // Get connection point (IID_IOPCDataCallback interface):
 IConnectionPoint *pCP = NULL;
 hr = m_pIConnPtContainer->FindConnectionPoint (IID_IOPCDataCallback, &pCP);
 
 // If we succeeded to get connection point interface, create
 // our data sink interface and advise server of it:
 if (SUCCEEDED (hr))
 {
  try
  {
   // Instantiate a new  CWYDataSink20 :

 ​  //这个类要自己写
   m_pWYDataSink20 = new CWYDataSink20 (m_pstrData);
   
   // Add ourselves to its reference count:
   m_pWYDataSink20->AddRef ();
   
   // Advise the server of our data sink:
   DWORD dwCookieDataSink20;
   hr = pCP->Advise (m_pWYDataSink20, &dwCookieDataSink20);
   
   // We are done with the IID_IOPCDataCallback, so release
   // (remove us from its reference count):
   pCP->Release ();
  }
  
  catch (...)
  {
   // If a problem, make sure hr = E_FAIL so error gets
   // processed correctly below:
   ASSERT (FALSE);
   hr = E_FAIL;
  }
 }

}
这样就注册了数据变更回调了!!!!!!

CWYDataSink20 的声明如下,参考了kepware客户端源码

class CWYDataSink20 : public IOPCDataCallback
{
public:
 CWYDataSink20();
 CWYDataSink20(CString *pstr);//自己加的一个函数,用来测试
 virtual ~CWYDataSink20();
 
 // IUnknown Methods
 STDMETHODIMP         QueryInterface (REFIID iid, LPVOID *ppInterface);
 STDMETHODIMP_(ULONG) AddRef ();
 STDMETHODIMP_(ULONG) Release ();
 
 // IOPCDataCallback Methods
 STDMETHODIMP OnDataChange (  // OnDataChange notifications
  DWORD dwTransID,   // 0 for normal OnDataChange events, non-zero for Refreshes
  OPCHANDLE hGroup,   // client group handle
  HRESULT hrMasterQuality, // S_OK if all qualities are GOOD, otherwise S_FALSE
  HRESULT hrMasterError,  // S_OK if all errors are S_OK, otherwise S_FALSE
  DWORD dwCount,    // number of items in the lists that follow
  OPCHANDLE *phClientItems, // item client handles
  VARIANT *pvValues,   // item data
  WORD *pwQualities,   // item qualities
  FILETIME *pftTimeStamps, // item timestamps
  HRESULT *pErrors);   // item errors 
 
 STDMETHODIMP OnReadComplete ( // OnReadComplete notifications
  DWORD dwTransID,   // Transaction ID returned by the server when the read was initiated
  OPCHANDLE hGroup,   // client group handle
  HRESULT hrMasterQuality, // S_OK if all qualities are GOOD, otherwise S_FALSE
  HRESULT hrMasterError,  // S_OK if all errors are S_OK, otherwise S_FALSE
  DWORD dwCount,    // number of items in the lists that follow
  OPCHANDLE *phClientItems, // item client handles
  VARIANT *pvValues,   // item data
  WORD *pwQualities,   // item qualities
  FILETIME *pftTimeStamps, // item timestamps
  HRESULT *pErrors);   // item errors 
 
 STDMETHODIMP OnWriteComplete ( // OnWriteComplete notifications
  DWORD dwTransID,   // Transaction ID returned by the server when the write was initiated
  OPCHANDLE hGroup,   // client group handle
  HRESULT hrMasterError,  // S_OK if all errors are S_OK, otherwise S_FALSE
  DWORD dwCount,    // number of items in the lists that follow
  OPCHANDLE *phClientItems, // item client handles
  HRESULT *pErrors);   // item errors 
 
 STDMETHODIMP OnCancelComplete ( // OnCancelComplete notifications
  DWORD dwTransID,   // Transaction ID provided by the client when the read/write/refresh was initiated
  OPCHANDLE hGroup);
 
private:
 DWORD m_cnRef;
 //测试数据
 CString *m_pstrItem;
 
};

这里对OnDataChange ()进行了测试,

这个就是响应OPC服务器指定数据变化时的事件处理函数,内容仅测试使用

STDMETHODIMP CWYDataSink20::OnDataChange (DWORD dwTransID,
            OPCHANDLE hGroup,
            HRESULT hrMasterQuality,
            HRESULT hrMasterError,
            DWORD dwCount,
            OPCHANDLE *phClientItems,
            VARIANT *pvValues,
            WORD *pwQualities,
            FILETIME *pftTimeStamps,
            HRESULT *pErrors)
{
 pvValues->vt;
 char buf[255] = { 0 };

//变量类型转换
 switch(pvValues->vt)
 {
  case VT_I1:
  case VT_UI1: // BYTE
   sprintf(buf, "%d", pvValues->bVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_I2:  // SHORT
   sprintf(buf, "%d", pvValues->iVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_UI2: // UNSIGNED SHORT
   sprintf(buf, "%d", pvValues->uiVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_I4:  // LONG
   sprintf(buf, "%ld", pvValues->lVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_UI4: // UNSIGNED LONG
   sprintf(buf, "%ld", pvValues->ulVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_INT: // INTEGER
   sprintf(buf, "%ld", pvValues->intVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_UINT: // UNSIGNED INTEGER
   sprintf(buf, "%u", pvValues->uintVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_R4:  // FLOAT
   sprintf(buf, "%5.2f", pvValues->fltVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_R8:  // DOUBLE
   sprintf(buf, "%9.4f", pvValues->dblVal);
   m_pstrItem->Format("%s",buf);
   break;
  case VT_BSTR: // BSTR
   *m_pstrItem = pvValues->bstrVal;
   break;
  case VT_BOOL: // BOOL
   if (pvValues->boolVal)
    strcpy(buf, "TRUE");
   else
    strcpy(buf, "FALSE");
   m_pstrItem->Format("%s",buf);
   break;
  default:
   sprintf(buf, "unknown type:%d", pvValues->vt);
   m_pstrItem->Format("%s",buf);
   break;
 }
// MessageBox(NULL,"OPC数据改变","OPC客户端测试",MB_OK);

     return (S_OK);

 }

好了,测试可以开始了:

对话框里使用:

//*************************************************************************
//函    数:OnBtnConnect
//所属类名:CWYOPCClientDlg
//参    数:
//返 回 值:void
//功能描述:响应点击“连接”按钮事件
//全局变量:
//调用模块:CWYOPCClientDlg
//作    者:吴毅
//日    期:2011年11月16日
//修 改 人:吴毅
//日    期:
//版    本:
//*************************************************************************
void CWYOPCClientDlg::OnBtnConnect()
{
 // TODO: Add your control notification handler code here
 //--连接到OPC服务器:
 int iFlag = -1;
 iFlag = m_OPCComm.Connect();
 if(iFlag == 0)
 {
  iFlag = m_OPCComm.InitCommunication();
 }

 if(iFlag == 0)
 {
    SetTimer(0001,10,NULL);//这个是定时刷新CEdit的显示
 }
 
 if(iFlag)
 {
  MessageBox("与OPC服务器通信失败!",
          "OPC客户端测试");
 }
 
}

 

五.写数据

简单写数据就是这样了

//*************************************************************************
//函    数:WriteToServer
//所属类名:COPCComm
//输    入:1.CString strSend
//输    出:int
//功能描述:向服务器写入数据
//全局变量:
//调用模块:CWYOPCClientDlg
//作    者:吴毅
//日    期:2011年11月17日
//修 改 人:吴毅
//日    期:
//版    本:
//*************************************************************************
int COPCComm::WriteToServer(CString strSend)
{
 //--写一个值
 // 使用(COleVariant : public tagVARIANT)来包装数据
 COleVariant ovWriteValue;
 HRESULT hr;
 OPCHANDLE hServerAO;
 ovWriteValue = strSend;// strSend为待写的数据
 ovWriteValue.ChangeType(VT_BOOL); //这个随情况而变

 //  使用VARIANT来包装数据
 VARIANT vtWriteValue;

 // 同步写数据
 hr = m_pOPCSync->Write(
  1,
  &m_hServer,
  ovWriteValue,
  &m_pErrors);
 
 return 0;

}

 

抱歉!评论已关闭.