我机器上安装了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;
}