OPC客户端的建立

原文:https://blog.csdn.net/wuyijc/article/details/6987510 

一.导入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
//日    期:
//版    本:
//*************************************************************************
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
//日    期:
//版    本:
//*************************************************************************
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
//日    期:
//版    本:
//*************************************************************************
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
//版    本:
//*************************************************************************
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
//日    期:
//版    本:
//*************************************************************************
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
//日    期:
//版    本:
//*************************************************************************
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;

}


 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值