OPC技术论坛 http://www.opc-china.com OPC服务器,客户程序技术讨论
OPC CLIENT程序(C语言篇,OPC1.0,2.0规范)
本程序为以前个人学习时,在国外网站上下载,本来想自己重新写一篇,因为各方面的原因,没有写,所以现在把下载的这个程序帖出来供大家学习。此程序个人觉得值得一看,虽然看起来程序有些长。
本程序一共包括三个文件:opc.idl opccomn.idl opctest.cpp
opc.idl opccomn.idl为OPC规范的IDL定义,opctest.cpp为主程序文件。
opctest.cpp文件如下:
// SST Win32 console OPC client Example
// Copyright ?1998-1999 SST, a division of Woodhead Canada Limited
// www.sstech.on.ca
//
// Created by Richard Illes
// May 21, 1998
// Async updates added June 10, 1998
// Simple write added June 24, 1998
// Async reads added July 20, 1998
// Logging added July 27, 1998
// Cache/Device added August 8, 1998
// Version 2.0 support added August 18, 1998
//
// This is a sample console Win32 client that
// does sync/async reads at 100ms intervals
// with up to 10 items
//
// Critical sections are used for async calls to keep track of the
// transaction ID. This slows the response rate down, but ensures all
// calls are completed. An alternative, the client can place transaction ID's
// into a que from OnDataChange() and after a async call is completed. Then a
// watchdog thread after a set timeout period can check both ques to see if the
// transaction completed. Or the client can simply ignore transaction ID's and
// use the client handle returned as validation.
//
// OPC version 2.0 negates the need for critical sections, since the client
// generates the transaction ID BEFORE the read/write is called.
//
// DISCLAIMER:
// This sample code is provided by SST solely to assist in understanding
// the OPC Data Access Specification in relation to a SST OPC server.
// This code is provided as-is and without warranty or support of any sort.
//
// This code may be freely re-used long as credit is openly given
// to SST.
//
//
#define STRICT
#define VC_EXTRALEAN
#ifndef _WIN32_DCOM
#define _WIN32_DCOM // WinNT 4.0 or Win95 w/DCOM
#endif
#define _ATL_FREE_THREADED
#include <stdio.h>
#include <time.h>
#include <windows.h>
#include <conio.h>
#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
CComModule _Module;
#include <atlcom.h>
#include <atlctl.h>
// check for Visual C++ 5 w/SP3
#if _ATL_VER < 0x0202
#error minimum requirements: Visual C++ 5 w/SP3
#endif
#include "opc_i.c"
#include "opc.h"
#include "opccomn_i.c"
#include "opccomn.h"
#define MAX_KEYLEN 256
#define MAX_ITEMS 10
DWORD g_dwUpdateRate = 100;
DWORD g_dwClientHandle = 1;
DWORD g_dwNumItems = 0;
bool g_bWriteEnable = false;
bool g_bWriteComplete = true;
bool g_bReadComplete = true;
bool g_bPoll = false; // poll for values or async updates
bool g_bVer2 = false; // version 2.0 flag
OPCHANDLE g_hClientGroup = 0;
IOPCServer *g_pIOPCServer = NULL;
DWORD g_dwUpdateTransID = 1;
DWORD g_dwCancelID = 1;
DWORD g_dwReadTransID = 1;
DWORD g_dwWriteTransID = 2;
FILE *g_stream = NULL; // file log handle
// group interfaces
IDataObject *g_pIDataObject = NULL; //OPC1.0规范
IOPCGroupStateMgt *g_pIOPCGroupStateMgt = NULL;
IOPCAsyncIO *g_pIOPCAsyncIO = NULL; //OPC1.0规范
IOPCSyncIO *g_pIOPCSyncIO = NULL;
IOPCItemMgt *g_pIOPCItemMgt = NULL;
IOPCAsyncIO2 *g_pIOPCAsyncIO2 = NULL;
IOPCCommon *g_pIOPCCommon = NULL;
IUnknown *g_pIGroupUnknown = NULL;
IOPCBrowseServerAddressSpace *g_pIOPCBrowse = NULL;
// critical section stuff
CComAutoCriticalSection g_Readcs;
CComAutoCriticalSection g_Writecs;
class CLock
{
public:
CComAutoCriticalSection* m_pcs;
CLock(CComAutoCriticalSection* pcs) {m_pcs = pcs; pcs->Lock();}
~CLock() {m_pcs->Unlock();}
};
#define READ_LOCK CLock gl(&g_Readcs);
#define WRITE_LOCK CLock gl(&g_Writecs);
class ATL_NO_VTABLE CTestAdviseSink;
class ATL_NO_VTABLE COPCCallback;
typedef CComObject<CTestAdviseSink> CComCTestAdviseSink;
typedef CComObject<COPCCallback> CComCOPCCallback;
UINT g_nOpcFormatData = ::RegisterClipboardFormat("OPCSTMFORMATDATA");
UINT g_nOpcFormatDatatime = ::RegisterClipboardFormat("OPCSTMFORMATDATATIME");
UINT g_nOpcFormatWrite = ::RegisterClipboardFormat("OPCSTMFORMATWRITECOMPLETE");
// PROTOTYPES
int OpcStart();
int OpcStop();
int GetStatus(WORD *pwMav, WORD *pwMiv, WORD *pwB, LPWSTR *pswzV);
int AddItems();
void SyncRead(bool bFlag);
int AsyncRead(bool bFlag);
int AsyncUpdate();
void ShowError(HRESULT hr, LPCSTR pszError);
void StartErrorLog();
void EndErrorLog();
LPCSTR GetDateTime();
bool Version2();
int Async2Read(bool bFlag);
int Async2Update();
// struct's and classes
struct structItem
{
WCHAR wszName[100];
VARTYPE vt;
DWORD hClient;
DWORD hServer;
} TestItem[10];
void main() //main
{
printf("SST Win32 console OPC client example./nVersion: 1999.04.06/n/n");
StartErrorLog();
int nRet = OpcStart(); // connect to a server
if(nRet) exit(nRet);
nRet = AddItems(); // add some items
if(nRet) exit(nRet);
char szBuffer[50];
if(!g_bVer2)
{
printf("/nPerform Sync reads, Async reads or Async Updates (S/A/U)? ");
}
else // version 2.0 has more options
{
printf("/n1)Sync reads/n2)Async reads/n3)Async Updates/n4)Async2 reads/n5)ConnectionPoint Updates/n");
printf("Select(1 - 5)? ");
}
_flushall();
gets(szBuffer);
if((*szBuffer == 'a') || (*szBuffer == 'A') || (*szBuffer == '2'))
{
printf("Read from Cache or Device (C/D)? ");
gets(szBuffer);
if((*szBuffer == 'c') || (*szBuffer == 'C'))
AsyncRead(true);
else
AsyncRead(false);
}
else if((*szBuffer == 's') || (*szBuffer == 'S') || (*szBuffer == '1'))
{
printf("Read from Cache or Device (C/D)? ");
gets(szBuffer);
if((*szBuffer == 'c') || (*szBuffer == 'C'))
SyncRead(true);
else
SyncRead(false);
}
else if(*szBuffer == '4')
{
printf("Read from Cache or Device (C/D)? ");
gets(szBuffer);
if((*szBuffer == 'c') || (*szBuffer == 'C'))
Async2Read(true);
else
Async2Read(false);
}
else if(*szBuffer == '5')
{
Async2Update();
}
else
{
AsyncUpdate();
}
nRet = OpcStop(); // done with server
EndErrorLog();
exit(nRet);
// heap error on exit?
}
void SyncRead(bool bFlag)
{
OPCITEMSTATE *pItemState = NULL;
HRESULT *pErrors = NULL;
HRESULT hr = 0;
// check for dupes
int dupbool = 0;
int dupi2 = 0;
long dupi4 = 0;
float dupr4 = 0.0f;
double dupr8 = 0.0;
if(g_bWriteEnable)
printf("Performing Sync reads/write...press a key to exit./n");
else
printf("Performing Sync reads...press a key to exit./n");
OPCHANDLE hServer[MAX_ITEMS];
VARIANT Val[MAX_ITEMS];
VARIANT vCount;
for(DWORD dw = 0; dw < g_dwNumItems; dw++)
{
hServer[dw] = TestItem[dw].hServer;
::VariantInit(&Val[dw]);
}
::VariantInit(&vCount);
V_VT(&vCount) = VT_I2;
V_I2(&vCount) = 0;
HRESULT *pErrorsWrite = NULL;
// loop around doing sync reads until user hits a key
while(!_kbhit())
{
// read from the server
hr = g_pIOPCSyncIO->Read(bFlag ? OPC_DS_CACHE : OPC_DS_DEVICE,
g_dwNumItems,
&hServer[0],
&pItemState,
&pErrors);
if(hr == S_OK)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
switch(V_VT(&pItemState[dw].vDataValue))
{
case VT_BOOL:
if(V_BOOL(&pItemState[dw].vDataValue) != dupbool)
printf("%d/t", V_BOOL(&pItemState[dw].vDataValue));
break;
case VT_I2:
default:
if(V_I2(&pItemState[dw].vDataValue) != dupi2)
printf("%d/t", V_I2(&pItemState[dw].vDataValue));
break;
case VT_I4:
if(V_I4(&pItemState[dw].vDataValue) != dupi4)
printf("%ld/t", V_I4(&pItemState[dw].vDataValue));
break;
case VT_R4:
if(V_R4(&pItemState[dw].vDataValue) != dupr4)
printf("%f/t", V_R4(&pItemState[dw].vDataValue));
break;
case VT_R8:
if(V_R8(&pItemState[dw].vDataValue) != dupr8)
printf("%lf/t", V_R8(&pItemState[dw].vDataValue));
break;
case VT_BSTR:
printf("%ls/t", V_BSTR(&pItemState[dw].vDataValue));
break;
}
}
printf("/r");
::CoTaskMemFree(pItemState);
::CoTaskMemFree(pErrors);
}
else if(hr == S_FALSE)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
if(FAILED(pErrors[dw]))
{
char sz[100];
sprintf(sz,"SyncIO->Read(%ls) returned", TestItem[dw].wszName);
ShowError(pErrors[dw], sz);
}
}
}
else
{
ShowError(hr,"Sync Read");
}
if(g_bWriteEnable) // quick write enable hack
{
// pump out data sync to items
for(dw = 0; dw < g_dwNumItems; dw++)
{
V_VT(&Val[dw]) = VT_I2;
::VariantCopy(&Val[dw], &vCount);
::VariantChangeType(&Val[dw], &Val[dw], 0, V_VT(&TestItem[dw]));
}
V_I2(&vCount)++;
if((V_VT(&TestItem[0]) == VT_BOOL) && (V_I2(&vCount) > 1))
{
V_I2(&vCount) = 0; // allow bool to toggle on/off
}
hr = g_pIOPCSyncIO->Write(g_dwNumItems, hServer, Val, &pErrorsWrite);
if(FAILED(hr))
{
ShowError(hr,"SyncIO->Write()");
}
else if(hr == S_FALSE)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
if(FAILED(pErrorsWrite[dw]))
{
ShowError(pErrorsWrite[dw],"SyncIO->Write() item returned");
}
}
::CoTaskMemFree(pErrorsWrite);
}
else // S_OK
{
::CoTaskMemFree(pErrorsWrite);
}
}
::Sleep(g_dwUpdateRate); // sleep between updates
}
for(dw = 0; dw < g_dwNumItems; dw++)
{
::VariantClear(&Val[dw]);
}
}
// AdviseSink class derived from IAdviseSink
// used with async updates
class ATL_NO_VTABLE CTestAdviseSink :
public CComObjectRoot,
public IAdviseSink
{
public:
BEGIN_COM_MAP(CTestAdviseSink)
COM_INTERFACE_ENTRY(IAdviseSink)
END_COM_MAP()
STDMETHODIMP_(void) OnViewChange(DWORD, LONG) {};
STDMETHODIMP_(void) OnRename(LPMONIKER) {};
STDMETHODIMP_(void) OnSave(void) {};
STDMETHODIMP_(void) OnClose(void) {};
STDMETHODIMP_(void) OnDataChange(LPFORMATETC pFE, LPSTGMEDIUM pSTM)
{
// Verify the format follows the OPC spec
if(TYMED_HGLOBAL != pFE->tymed) return;
if(pSTM->hGlobal == 0) return;
if(pFE->cfFormat == g_nOpcFormatWrite)
{
WRITE_LOCK;
const LPBYTE pBuffer = reinterpret_cast<const LPBYTE>(::GlobalLock(pSTM->hGlobal));
if(pBuffer == NULL) return;
const OPCGROUPHEADERWRITE *pHeader = reinterpret_cast<const OPCGROUPHEADERWRITE*>(pBuffer);
if(FAILED(pHeader->hrStatus))
{
ShowError(pHeader->hrStatus,"General Async Write");
}
if(g_dwWriteTransID != pHeader->dwTransactionID)
{
ShowError(S_OK,"Async Write callback, TransactionID's do not match");
}
DWORD dwSize = sizeof(OPCGROUPHEADERWRITE);
for(DWORD dw=0; dw < pHeader->dwItemCount; dw++, dwSize += sizeof(OPCITEMHEADERWRITE))
{
const OPCITEMHEADERWRITE* pItemHeader = reinterpret_cast<const OPCITEMHEADERWRITE*>(&pBuffer[dwSize]);
if(FAILED(pItemHeader->dwError))
{
ShowError(pItemHeader->dwError,"Async Write request");
}
}
g_bWriteComplete = true;
::GlobalUnlock(pSTM->hGlobal);
return;
}
else if(pFE->cfFormat != g_nOpcFormatDatatime) return;
const LPBYTE pBuffer = reinterpret_cast<const LPBYTE>(::GlobalLock(pSTM->hGlobal));
if(pBuffer == NULL) return;
const OPCGROUPHEADER *pHeader = reinterpret_cast<const OPCGROUPHEADER*>(pBuffer);
if(FAILED(pHeader->hrStatus))
{
ShowError(pHeader->hrStatus,"General Async Read");
}
if(g_bPoll)
{
// if we are polling, ignore async updates
if(pHeader->dwTransactionID == 0)
{
return;
}
READ_LOCK;
if(pHeader->dwTransactionID != g_dwReadTransID)
{
ShowError(S_OK,"Async Read callback, TransactionID's do not match");
return;
}
if(!g_bReadComplete) g_bReadComplete = true;
}
DWORD dwSize = sizeof(OPCGROUPHEADER);
for(DWORD dw=0; dw < pHeader->dwItemCount; dw++, dwSize += sizeof(OPCITEMHEADER1))
{
const OPCITEMHEADER1* pItemHeader = reinterpret_cast<const OPCITEMHEADER1*>(&pBuffer[dwSize]);
if(pItemHeader->wQuality == OPC_QUALITY_GOOD)
{
VARIANT *pValue = reinterpret_cast<VARIANT*>(&pBuffer[pItemHeader->dwValueOffset]);
switch(V_VT(pValue))
{
case VT_BOOL:
printf("%d/t", V_BOOL(pValue));
break;
case VT_I2:
printf("%d/t", V_I2(pValue));
break;
case VT_I4:
printf("%ld/t", V_I4(pValue));
break;
case VT_R4:
printf("%f/t", V_R4(pValue));
break;
case VT_R8:
printf("%lf/t", V_R8(pValue));
break;
case VT_BSTR:
printf("%ls/t", V_BSTR(pValue));
break;
default:
if(SUCCEEDED(::VariantChangeType(pValue,pValue,0,VT_I4)))
printf("%ld/t", V_I4(pValue));
else
printf("***/t");
break;
}
}
else
{
switch(pItemHeader->wQuality)
{
case OPC_QUALITY_BAD:
default:
ShowError(S_OK, "Quality Bad");
break;
case OPC_QUALITY_UNCERTAIN:
ShowError(S_OK, "Quality UNCERTAIN");
break;
case OPC_QUALITY_CONFIG_ERROR:
ShowError(S_OK, "CONFIG ERROR");
break;
case OPC_QUALITY_NOT_CONNECTED:
ShowError(S_OK, "NOT CONNECTED");
break;
case OPC_QUALITY_DEVICE_FAILURE:
ShowError(S_OK, "DEVICE FAILURE");
break;
case OPC_QUALITY_OUT_OF_SERVICE:
ShowError(S_OK, "OUT OF SERVICE");
break;
}
}
}
printf("/r");
::GlobalUnlock(pSTM->hGlobal);
}
};
int AsyncUpdate()
{
HRESULT hr = 0;
FORMATETC formatetc;
DWORD dwUpdateConnection = 0;
DWORD dwWriteConnection = 0;
formatetc.cfFormat = g_nOpcFormatDatatime;
// need to fill the rest of the struct or the proxy make puke
formatetc.ptd = NULL;
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex = -1;
formatetc.tymed = TYMED_HGLOBAL;
CComCTestAdviseSink *pSink = NULL;
ATLTRY(pSink = new CComCTestAdviseSink);
if(pSink == NULL)
{
ShowError(E_OUTOFMEMORY,"new CTestAdviseSink");
return 1;
}
hr = g_pIDataObject->DAdvise(&formatetc, 0, pSink, &dwUpdateConnection);
if(FAILED(hr))
{
ShowError(hr,"DAdvise(Datatime)");
return 1;
}
if(g_bWriteEnable)
{
formatetc.cfFormat = g_nOpcFormatWrite;
hr = g_pIDataObject->DAdvise(&formatetc, 0, pSink, &dwWriteConnection);
if(FAILED(hr))
{
ShowError(hr,"DAdvise(Write)");
return 1;
}
printf("Performing Async updates/write...press a key to exit./n");
}
else
printf("Performing Async updates...press a key to exit./n");
OPCHANDLE hServer[MAX_ITEMS];
VARIANT Val[MAX_ITEMS];
VARIANT vCount;
DWORD dw = 0;
if(g_bWriteEnable)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
hServer[dw] = TestItem[dw].hServer;
::VariantInit(&Val[dw]);
}
}
::VariantInit(&vCount);
V_VT(&vCount) = VT_I2;
V_I2(&vCount) = 0;
HRESULT *pErrorsWrite = NULL;
// nap while server does its callback
while(!_kbhit())
{
::Sleep(0);
if(g_bWriteEnable && g_bWriteComplete)
{
// pump out data async to items
for(dw = 0; dw < g_dwNumItems; dw++)
{
V_VT(&Val[dw]) = VT_I2;
::VariantCopy(&Val[dw], &vCount);
::VariantChangeType(&Val[dw], &Val[dw], 0, V_VT(&TestItem[dw]));
}
V_I2(&vCount)++;
if((V_VT(&TestItem[0]) == VT_BOOL) && (V_I2(&vCount) > 1))
{
V_I2(&vCount) = 0; // allow bool to toggle on/off
}
g_bWriteComplete = false;
g_Writecs.Lock(); // lock callbacks until we get transid
hr = g_pIOPCAsyncIO->Write(dwWriteConnection, g_dwNumItems, hServer, Val, &g_dwWriteTransID, &pErrorsWrite);
g_Writecs.Unlock();
if(FAILED(hr))
{
ShowError(hr,"AsyncIO->Write()");
}
else if(hr == S_FALSE)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
if(FAILED(pErrorsWrite[dw]))
{
ShowError(pErrorsWrite[dw],"AsyncIO->Write() item returned");
}
}
::CoTaskMemFree(pErrorsWrite);
}
else // S_OK
{
::CoTaskMemFree(pErrorsWrite);
}
}
}
if(g_bWriteEnable)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
::VariantClear(&Val[dw]);
}
}
::VariantClear(&vCount);
hr = g_pIDataObject->DUnadvise(dwUpdateConnection);
if(FAILED(hr))
{
ShowError(hr,"DUnadvise(Datatime)");
}
if(g_bWriteEnable)
{
hr = g_pIDataObject->DUnadvise(dwWriteConnection);
if(FAILED(hr))
{
ShowError(hr,"DUnadvise(Write)");
}
}
return 0;
}
int AsyncRead(bool bFlag)
{
HRESULT hr = 0;
FORMATETC formatetc;
DWORD dwReadConnection = 0;
DWORD dwWriteConnection = 0;
g_bPoll = true; // we are polling for values
formatetc.cfFormat = g_nOpcFormatDatatime;
// need to fill the rest of the struct or the proxy make puke
formatetc.ptd = NULL;
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex = -1;
formatetc.tymed = TYMED_HGLOBAL;
CTestAdviseSink *pSink = NULL;
ATLTRY(pSink = new CComCTestAdviseSink);
if(pSink == NULL)
{
ShowError(E_OUTOFMEMORY,"new CTestAdviseSink");
return 1;
}
hr = g_pIDataObject->DAdvise(&formatetc, 0, pSink, &dwReadConnection);
if(FAILED(hr))
{
ShowError(hr,"DAdvise(Datatime)");
return 1;
}
if(g_bWriteEnable)
{
formatetc.cfFormat = g_nOpcFormatWrite;
hr = g_pIDataObject->DAdvise(&formatetc, 0, pSink, &dwWriteConnection);
if(FAILED(hr))
{
ShowError(hr,"DAdvise(Write)");
return 1;
}
printf("Performing Async reads/write...press a key to exit./n");
}
else
printf("Performing Async reads...press a key to exit./n");
OPCHANDLE hServer[MAX_ITEMS];
VARIANT Val[MAX_ITEMS];
VARIANT vCount;
DWORD dw = 0;
if(g_bWriteEnable)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
hServer[dw] = TestItem[dw].hServer;
::VariantInit(&Val[dw]);
}
}
::VariantInit(&vCount);
V_VT(&vCount) = VT_I2;
V_I2(&vCount) = 0;
HRESULT *pErrorsWrite = NULL;
// nap while server does its callback
while(!_kbhit())
{
if(g_bWriteEnable && g_bWriteComplete)
{
// pump out data async to items
for(dw = 0; dw < g_dwNumItems; dw++)
{
V_VT(&Val[dw]) = VT_I2;
::VariantCopy(&Val[dw], &vCount);
::VariantChangeType(&Val[dw], &Val[dw], 0, V_VT(&TestItem[dw]));
}
V_I2(&vCount)++;
if((V_VT(&TestItem[0]) == VT_BOOL) && (V_I2(&vCount) > 1))
{
V_I2(&vCount) = 0; // allow bool to toggle on/off
}
g_bWriteComplete = false;
g_Writecs.Lock(); // lock callbacks until we get transid
// write to one item
hr = g_pIOPCAsyncIO->Write(dwWriteConnection, g_dwNumItems, hServer, Val, &g_dwWriteTransID, &pErrorsWrite);
g_Writecs.Unlock();
if(FAILED(hr))
{
ShowError(hr,"AsyncIO->Write()");
}
else if(hr == S_FALSE)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
if(FAILED(pErrorsWrite[dw]))
{
ShowError(pErrorsWrite[dw],"AsyncIO->Write() item returned");
}
}
::CoTaskMemFree(pErrorsWrite);
}
else // S_OK
{
::CoTaskMemFree(pErrorsWrite);
}
}
if(g_bReadComplete)
{
g_bReadComplete = false;
g_Readcs.Lock(); // lock callbacks until we get transid
// read all items in group
hr = g_pIOPCAsyncIO->Refresh(dwReadConnection,
bFlag ? OPC_DS_CACHE : OPC_DS_DEVICE,
&g_dwReadTransID);
g_Readcs.Unlock();
if(FAILED(hr))
{
ShowError(hr,"AsyncIO->Refresh()");
}
}
::Sleep(g_dwUpdateRate);
}
if(g_bWriteEnable)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
::VariantClear(&Val[dw]);
}
}
::VariantClear(&vCount);
hr = g_pIDataObject->DUnadvise(dwReadConnection);
if(FAILED(hr))
{
ShowError(hr,"DUnadvise(Datatime)");
}
if(g_bWriteEnable)
{
hr = g_pIDataObject->DUnadvise(dwWriteConnection);
if(FAILED(hr))
{
ShowError(hr,"DUnadvise(Write)");
}
}
return 0;
}
// AdviseSink class derived from IOPCDataCallback
// used with async updates
class ATL_NO_VTABLE COPCCallback :
public CComObjectRoot,
public IOPCDataCallback
{
public:
BEGIN_COM_MAP(COPCCallback)
COM_INTERFACE_ENTRY(IOPCDataCallback)
END_COM_MAP()
STDMETHODIMP OnDataChange(
/* [in] */ DWORD dwTransid,
/* [in] */ OPCHANDLE hGroup,
/* [in] */ HRESULT hrMasterquality,
/* [in] */ HRESULT hrMastererror,
/* [in] */ DWORD dwCount,
/* [size_is][in] */ OPCHANDLE __RPC_FAR *phClientItems,
/* [size_is][in] */ VARIANT __RPC_FAR *pvValues,
/* [size_is][in] */ WORD __RPC_FAR *pwQualities,
/* [size_is][in] */ FILETIME __RPC_FAR *pftTimeStamps,
/* [size_is][in] */ HRESULT __RPC_FAR *pErrors)
{
if(FAILED(hrMastererror))
{
ShowError(hrMastererror,"General ConnectionPoint Update");
}
for(DWORD dw=0; dw < dwCount; dw++)
{
if((pwQualities[dw] == OPC_QUALITY_GOOD) && SUCCEEDED(pErrors[dw]))
{
VARIANT *pValue = &pvValues[dw];
switch(V_VT(pValue))
{
case VT_BOOL:
printf("%d/t", V_BOOL(pValue));
break;
case VT_I2:
printf("%d/t", V_I2(pValue));
break;
case VT_I4:
printf("%ld/t", V_I4(pValue));
break;
case VT_R4:
printf("%f/t", V_R4(pValue));
break;
case VT_R8:
printf("%lf/t", V_R8(pValue));
break;
case VT_BSTR:
printf("%ls/t", V_BSTR(pValue));
break;
default:
if(SUCCEEDED(::VariantChangeType(pValue,pValue,0,VT_I4)))
printf("%ld/t", V_I4(pValue));
else
printf("***/t");
break;
}
}
else // else if
{
switch(pwQualities[dw])
{
case OPC_QUALITY_GOOD:
ShowError(S_OK, "Quality Good");
break;
case OPC_QUALITY_BAD:
default:
ShowError(S_OK, "Quality Bad");
break;
case OPC_QUALITY_UNCERTAIN:
ShowError(S_OK, "Quality UNCERTAIN");
break;
case OPC_QUALITY_CONFIG_ERROR:
ShowError(S_OK, "CONFIG ERROR");
break;
case OPC_QUALITY_NOT_CONNECTED:
ShowError(S_OK, "NOT CONNECTED");
break;
case OPC_QUALITY_DEVICE_FAILURE:
ShowError(S_OK, "DEVICE FAILURE");
break;
case OPC_QUALITY_OUT_OF_SERVICE:
ShowError(S_OK, "OUT OF SERVICE");
break;
}
} // endif
} // end for
printf("/r");
return S_OK;
}
STDMETHODIMP OnReadComplete(
/* [in] */ DWORD dwTransid,
/* [in] */ OPCHANDLE hGroup,
/* [in] */ HRESULT hrMasterquality,
/* [in] */ HRESULT hrMastererror,
/* [in] */ DWORD dwCount,
/* [size_is][in] */ OPCHANDLE __RPC_FAR *phClientItems,
/* [size_is][in] */ VARIANT __RPC_FAR *pvValues,
/* [size_is][in] */ WORD __RPC_FAR *pwQualities,
/* [size_is][in] */ FILETIME __RPC_FAR *pftTimeStamps,
/* [size_is][in] */ HRESULT __RPC_FAR *pErrors)
{
if(FAILED(hrMastererror))
{
ShowError(hrMastererror,"General Async2 Read");
}
if(dwTransid != g_dwReadTransID)
{
ShowError(S_OK,"Async2 Read callback, TransactionID's do not match");
return S_FALSE;
}
for(DWORD dw=0; dw < dwCount; dw++)
{
if((pwQualities[dw] == OPC_QUALITY_GOOD) && SUCCEEDED(pErrors[dw]))
{
VARIANT *pValue = &pvValues[dw];
switch(V_VT(pValue))
{
case VT_BOOL:
printf("%d/t", V_BOOL(pValue));
break;
case VT_I2:
printf("%d/t", V_I2(pValue));
break;
case VT_I4:
printf("%ld/t", V_I4(pValue));
break;
case VT_R4:
printf("%f/t", V_R4(pValue));
break;
case VT_R8:
printf("%lf/t", V_R8(pValue));
break;
case VT_BSTR:
printf("%ls/t", V_BSTR(pValue));
break;
default:
if(SUCCEEDED(::VariantChangeType(pValue,pValue,0,VT_I4)))
printf("%ld/t", V_I4(pValue));
else
printf("***/t");
break;
}
}
else // else if
{
switch(pwQualities[dw])
{
case OPC_QUALITY_GOOD:
ShowError(S_OK, "Quality Good");
break;
case OPC_QUALITY_BAD:
default:
ShowError(S_OK, "Quality Bad");
break;
case OPC_QUALITY_UNCERTAIN:
ShowError(S_OK, "Quality UNCERTAIN");
break;
case OPC_QUALITY_CONFIG_ERROR:
ShowError(S_OK, "CONFIG ERROR");
break;
case OPC_QUALITY_NOT_CONNECTED:
ShowError(S_OK, "NOT CONNECTED");
break;
case OPC_QUALITY_DEVICE_FAILURE:
ShowError(S_OK, "DEVICE FAILURE");
break;
case OPC_QUALITY_OUT_OF_SERVICE:
ShowError(S_OK, "OUT OF SERVICE");
break;
}
} // endif
} // end for
g_bReadComplete = true;
printf("/r");
return S_OK;
}
STDMETHODIMP OnWriteComplete(
/* [in] */ DWORD dwTransid,
/* [in] */ OPCHANDLE hGroup,
/* [in] */ HRESULT hrMastererr,
/* [in] */ DWORD dwCount,
/* [size_is][in] */ OPCHANDLE __RPC_FAR *pClienthandles,
/* [size_is][in] */ HRESULT __RPC_FAR *pErrors)
{
if(FAILED(hrMastererr))
{
ShowError(hrMastererr,"General Async2 Write");
}
if(g_dwWriteTransID != dwTransid)
{
ShowError(S_OK,"Async2 Write callback, TransactionID's do not match");
}
for(DWORD dw=0; dw < dwCount; dw++)
{
if(FAILED(pErrors[dw]))
{
ShowError(pErrors[dw], "Async2 Write request");
}
}
g_bWriteComplete = true;
return S_OK;
}
STDMETHODIMP OnCancelComplete(
/* [in] */ DWORD dwTransid,
/* [in] */ OPCHANDLE hGroup)
{
return S_OK;
}
};
int Async2Update()
{
if(g_pIOPCAsyncIO2 == NULL) return 1; // not supported
IConnectionPointContainer *pCPC = NULL;
IConnectionPoint *pCP = NULL;
DWORD dwCookie = 0;
HRESULT hr = S_OK;
// create the sink
CComCOPCCallback *pSink = NULL;
ATLTRY(pSink = new CComCOPCCallback);
if(pSink == NULL)
{
ShowError(E_OUTOFMEMORY,"new COPCCallback");
return 1;
}
// obtain connection points
hr = g_pIGroupUnknown->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPC);
if(FAILED(hr))
{
ShowError(hr, "QueryInterface(IID_IConnectionPointContainer)");
return 1;
}
hr = pCPC->FindConnectionPoint(IID_IOPCDataCallback, &pCP);
if(FAILED(hr))
{
ShowError(hr, "FindConnectionPoint(IID_IOPCDataCallback)");
return 1;
}
hr = pCP->Advise(pSink, &dwCookie);
if(FAILED(hr))
{
ShowError(hr, "Advise()");
return 1;
}
if(g_bWriteEnable)
printf("Performing C.P. Updates/Async2 write...press a key to exit./n");
else
printf("Performing ConnectionPoint Updates...press a key to exit./n");
OPCHANDLE hServer[MAX_ITEMS];
VARIANT Val[MAX_ITEMS];
VARIANT vCount;
DWORD dw = 0;
if(g_bWriteEnable)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
hServer[dw] = TestItem[dw].hServer;
::VariantInit(&Val[dw]);
}
}
::VariantInit(&vCount);
V_VT(&vCount) = VT_I2;
V_I2(&vCount) = 0;
HRESULT *pErrorsWrite = NULL;
// nap while server does its callback
while(!_kbhit())
{
::Sleep(0);
if(g_bWriteEnable && g_bWriteComplete)
{
// pump out data async to items
for(dw = 0; dw < g_dwNumItems; dw++)
{
V_VT(&Val[dw]) = VT_I2;
::VariantCopy(&Val[dw], &vCount);
::VariantChangeType(&Val[dw], &Val[dw], 0, V_VT(&TestItem[dw]));
}
V_I2(&vCount)++;
if((V_VT(&TestItem[0]) == VT_BOOL) && (V_I2(&vCount) > 1))
{
V_I2(&vCount) = 0; // allow bool to toggle on/off
}
g_bWriteComplete = false;
hr = g_pIOPCAsyncIO2->Write(g_dwNumItems, hServer, Val, ++g_dwWriteTransID, &g_dwCancelID, &pErrorsWrite);
if(FAILED(hr))
{
ShowError(hr,"AsyncIO2->Write()");
}
else if(hr == S_FALSE)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
if(FAILED(pErrorsWrite[dw]))
{
ShowError(pErrorsWrite[dw],"AsyncIO2->Write() item returned");
}
}
::CoTaskMemFree(pErrorsWrite);
}
else // S_OK
{
::CoTaskMemFree(pErrorsWrite);
}
}
}
if(g_bWriteEnable)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
::VariantClear(&Val[dw]);
}
}
::VariantClear(&vCount);
// release interfaces
hr = pCP->Unadvise(dwCookie);
if(FAILED(hr))
{
ShowError(hr, "Unadvise()");
}
pCP->Release();
pCPC->Release();
while(pSink->Release()) ;
return 0;
}
int Async2Read(bool bFlag)
{
if(g_pIOPCAsyncIO2 == NULL) return 1; // not supported
IConnectionPointContainer *pCPC = NULL;
IConnectionPoint *pCP = NULL;
DWORD dwCookie = 0; // advise cookie
HRESULT hr = S_OK;
// create the sink
CComCOPCCallback *pSink = NULL;
ATLTRY(pSink = new CComCOPCCallback);
if(pSink == NULL)
{
ShowError(E_OUTOFMEMORY,"new COPCCallback");
return 1;
}
// obtain connection points
hr = g_pIGroupUnknown->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPC);
if(FAILED(hr))
{
ShowError(hr, "QueryInterface(IID_IConnectionPointContainer)");
return 1;
}
hr = pCPC->FindConnectionPoint(IID_IOPCDataCallback, &pCP);
if(FAILED(hr))
{
ShowError(hr, "FindConnectionPoint(IID_IOPCDataCallback)");
return 1;
}
hr = pCP->Advise(pSink, &dwCookie);
if(FAILED(hr))
{
ShowError(hr, "Advise()");
return 1;
}
g_pIOPCAsyncIO2->SetEnable(FALSE); // turn off update callbacks
if(g_bWriteEnable)
printf("Performing Async2 reads/writes...press a key to exit./n");
else
printf("Performing Async2 reads...press a key to exit./n");
OPCHANDLE hServer[MAX_ITEMS];
VARIANT Val[MAX_ITEMS];
VARIANT vCount;
for(DWORD dw = 0; dw < g_dwNumItems; dw++)
{
hServer[dw] = TestItem[dw].hServer;
::VariantInit(&Val[dw]);
}
::VariantInit(&vCount);
V_VT(&vCount) = VT_I2;
V_I2(&vCount) = 0;
HRESULT *pErrorsWrite = NULL;
HRESULT *pErrorsRead = NULL;
// nap while server does its callback
while(!_kbhit())
{
if(g_bWriteEnable && g_bWriteComplete)
{
// pump out data async to items
for(dw = 0; dw < g_dwNumItems; dw++)
{
V_VT(&Val[dw]) = VT_I2;
::VariantCopy(&Val[dw], &vCount);
::VariantChangeType(&Val[dw], &Val[dw], 0, V_VT(&TestItem[dw]));
}
V_I2(&vCount)++;
if((V_VT(&TestItem[0]) == VT_BOOL) && (V_I2(&vCount) > 1))
{
V_I2(&vCount) = 0; // allow bool to toggle on/off
}
g_bWriteComplete = false;
// write items
hr = g_pIOPCAsyncIO2->Write(g_dwNumItems, hServer, Val, ++g_dwWriteTransID, &g_dwCancelID, &pErrorsWrite);
if(FAILED(hr))
{
ShowError(hr,"AsyncIO2->Write()");
}
else if(hr == S_FALSE)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
if(FAILED(pErrorsWrite[dw]))
{
ShowError(pErrorsWrite[dw],"AsyncIO2->Write() item returned");
}
}
::CoTaskMemFree(pErrorsWrite);
}
else // S_OK
{
::CoTaskMemFree(pErrorsWrite);
}
}
if(g_bReadComplete)
{
g_bReadComplete = false;
// read all items in group
hr = g_pIOPCAsyncIO2->Read(g_dwNumItems, hServer, ++g_dwReadTransID, &g_dwCancelID, &pErrorsRead);
if(FAILED(hr))
{
ShowError(hr,"AsyncIO2->Read()");
}
else if(hr == S_FALSE)
{
for(dw = 0; dw < g_dwNumItems; dw++)
{
if(FAILED(pErrorsRead[dw]))
{
ShowError(pErrorsRead[dw],"AsyncIO2->Read() item returned");
}
}
::CoTaskMemFree(pErrorsRead);
}
else // S_OK
{
::CoTaskMemFree(pErrorsRead);
}
}
::Sleep(g_dwUpdateRate);
}
for(dw = 0; dw < g_dwNumItems; dw++)
{
::VariantClear(&Val[dw]);
}
::VariantClear(&vCount);
// release interfaces
hr = pCP->Unadvise(dwCookie);
if(FAILED(hr))
{
ShowError(hr, "Unadvise()");
}
pCP->Release();
pCPC->Release();
while(pSink->Release()) ;
return 0;
}
int OpcStart()
{
// browse registry for OPC 1.0A Servers
HKEY hk = HKEY_CLASSES_ROOT;
TCHAR szKey[MAX_KEYLEN];
for(int nIndex = 0; ::RegEnumKey(hk, nIndex, szKey, MAX_KEYLEN) == ERROR_SUCCESS; nIndex++)
{
HKEY hProgID;
TCHAR szDummy[MAX_KEYLEN];
if(::RegOpenKey(hk, szKey, &hProgID) == ERROR_SUCCESS)
{
LONG lSize = MAX_KEYLEN;
if(::RegQueryValue(hProgID, "OPC", szDummy, &lSize) == ERROR_SUCCESS)
{
printf("%s/n",szKey);
}
::RegCloseKey(hProgID);
}
}
WCHAR wszServerName[100];
TCHAR szBuffer[100];
USES_CONVERSION;
printf("/nEnter server name:");
_flushall();
gets(szBuffer);
wcscpy(wszServerName, T2W(szBuffer));
// enter '.' to default to SST test server
if((wcslen(wszServerName) == 0) || (*wszServerName == L'.')) wcscpy(wszServerName, L"SST.SimulatorOpcSvr.1");
// enter '=' to default to SST DHP server
if(*wszServerName == L'=') wcscpy(wszServerName, L"SST.DataHighwayPlusOpcSvr.1");
CLSID clsid;
HRESULT hr = ::CLSIDFromProgID(wszServerName, &clsid );
if(FAILED(hr))
{
ShowError(hr,"CLSIDFromProgID()");
return 1;
}
printf("Server ID found./n");
hr = ::CoInitializeEx(NULL,COINIT_MULTITHREADED); // setup COM lib
if(FAILED(hr))
{
ShowError(hr,"CoInitializeEx()");
return 1;
}
// Create a running object from that class ID
// (CLSCTX_ALL will allow in-proc, local and remote)
hr = ::CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IOPCServer, (void**)&g_pIOPCServer);
if(FAILED(hr) || (g_pIOPCServer == NULL))
{
if(FAILED(hr)) ShowError(hr,"CoCreateInstance()");
printf("You may not have registered the OPC Proxy dll!/n");
return 1;
}
printf("Connected to server./n");
WORD wMajor, wMinor, wBuild;
LPWSTR pwsz = NULL;
if(!GetStatus(&wMajor, &wMinor, &wBuild, &pwsz))
{
printf("Version: %d.%d.%d/n", wMajor, wMinor, wBuild);
printf("%ls/n/n",pwsz);
::CoTaskMemFree(pwsz);
}
g_bVer2 = Version2();
if(g_bVer2)
{
printf("Server supports OPC 2.0 interfaces/n/n");
}
hr = g_pIOPCServer->QueryInterface(IID_IOPCBrowseServerAddressSpace, (void**)&g_pIOPCBrowse);
if(FAILED(hr))
{
ShowError(hr,"QueryInterface(IID_IOPCBrowseServerAddressSpace");
}
float fTemp = 0.0f;
long lTimeBias = 0;
DWORD dwRevisedUpdateRate = 0;
// create an in-active group
// NOTE: 1st param must not be a NULL or the proxy will puke
hr = g_pIOPCServer->AddGroup(L"", // [in] Server name, if NULL OPC Server will generate a unique name
TRUE , // [in] State of group to add
g_dwUpdateRate, // [in] Requested update rate for group (ms)
1234, // [in] Client handle to OPC Group
&lTimeBias, // [in] Time
&fTemp, // [in] Percent Deadband
0, // [in] Localization ID
&g_hClientGroup, // [out] Server Handle to group
&dwRevisedUpdateRate, // [out] Revised update rate
IID_IUnknown, // [in] Type of interface desired
&g_pIGroupUnknown); // [out] where to store the interface pointer
if(FAILED(hr))
{
ShowError(hr,"AddGroup()");
g_pIOPCServer->Release();
return 1;
}
printf("Group added, update rate = %ld./n", dwRevisedUpdateRate);
// Get pointer to OPC Server interfaces required for this program.
hr = g_pIGroupUnknown->QueryInterface(IID_IDataObject, (void**)&g_pIDataObject);
if(FAILED(hr))
{
ShowError(hr,"QueryInterface(IID_IDataObject)");
}
hr = g_pIGroupUnknown->QueryInterface(IID_IOPCGroupStateMgt, (void**)&g_pIOPCGroupStateMgt);
if(FAILED(hr))
{
ShowError(hr,"QueryInterface(IID_IOPCGroupStateMgt)");
}
hr = g_pIGroupUnknown->QueryInterface(IID_IOPCAsyncIO, (void**)&g_pIOPCAsyncIO);
if(FAILED(hr))
{
ShowError(hr,"QueryInterface(IID_IOPCAsyncIO)");
}
hr = g_pIGroupUnknown->QueryInterface(IID_IOPCItemMgt, (void**)&g_pIOPCItemMgt);
if(FAILED(hr))
{
ShowError(hr,"QueryInterface(IID_IOPCItemMgt)");
}
hr = g_pIGroupUnknown->QueryInterface(IID_IOPCSyncIO, (void**)&g_pIOPCSyncIO);
if(FAILED(hr))
{
ShowError(hr,"QueryInterface(IID_IOPCSyncIO)");
}
if(g_bVer2)
{
hr = g_pIGroupUnknown->QueryInterface(IID_IOPCAsyncIO2, (void**)&g_pIOPCAsyncIO2);
if(FAILED(hr))
{
ShowError(hr,"QueryInterface(IID_IOPCAsyncIO2)");
}
hr = g_pIOPCServer->QueryInterface(IID_IOPCCommon, (void**)&g_pIOPCCommon);
if(FAILED(hr))
{
ShowError(hr,"QueryInterface(IID_IOPCCommon)");
}
else
{
g_pIOPCCommon->SetClientName(L"SST Win32 Simple Client");
}
}
//
if(FAILED(hr))
{
g_pIOPCServer->Release();
printf("ERROR: secondary QI failed/n");
return 1;
}
if(dwRevisedUpdateRate != g_dwUpdateRate)
{
g_dwUpdateRate = dwRevisedUpdateRate;
}
printf("Active Group interface added./n");
return 0;
}
int OpcStop()
{
// terminate server and it will clean up itself
if(g_pIOPCServer) while(g_pIOPCServer->Release()) ;
::CoUninitialize();
printf("Server and all group interfaces terminated./n");
return 1;
}
int GetStatus(WORD *pwMav, WORD *pwMiv, WORD *pwB, LPWSTR *pszV)
{
*pwMav = 0;
*pwMiv = 0;
*pwB = 0;
*pszV = NULL;
OPCSERVERSTATUS *pStatus = NULL;
if(g_pIOPCServer == NULL) return E_POINTER;
HRESULT hr = g_pIOPCServer->GetStatus(&pStatus);
if(FAILED(hr) || (pStatus == NULL) || (pStatus->dwServerState != OPC_STATUS_RUNNING))
{
if(FAILED(hr)) ShowError(hr,"GetStatus()");
if(pStatus != NULL) ::CoTaskMemFree(pStatus);
return E_FAIL;
}
*pwMav = pStatus->wMajorVersion;
*pwMiv = pStatus->wMinorVersion;
*pwB = pStatus->wBuildNumber;
*pszV = pStatus->szVendorInfo;
::CoTaskMemFree(pStatus);
return 0;
}
// simple check for version OPC 2.0 type connection point containers
bool Version2()
{
if(g_pIOPCServer == NULL) return false;
IConnectionPointContainer *pCPC = NULL;
if(FAILED(g_pIOPCServer->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPC)))
{
return false;
}
pCPC->Release();
return true;
}
int AddItems()
{
// loop until all items are added
char sz2[200];
TCHAR szBuffer[256];
HRESULT hr = 0;
int nTestItem = 0; // how many items there are
IEnumString* pEnumString = NULL;
int nCount = 0;
USES_CONVERSION;
_flushall();
hr = g_pIOPCBrowse->BrowseOPCItemIDs(OPC_FLAT, L""/*NULL*/, VT_EMPTY, 0, &pEnumString);
if(FAILED(hr))
{
ShowError(hr, _T("BrowseOPCItemIDs()"));
}
if(hr == S_OK)
{
LPOLESTR pszName = NULL;
ULONG count = 0;
while((hr = pEnumString->Next(1, &pszName, &count)) == S_OK)
{
printf(_T("%s/n"), OLE2T(pszName));
::CoTaskMemFree(pszName);
if(nCount++ > 22)
{
printf("** press any key to continue **/n");
gets(szBuffer);
nCount = 0;
}
}
pEnumString->Release();
}
while(true)
{
_flushall();
if(nTestItem)
{
printf("Add an item (y/n)? ");
gets(szBuffer);
if(_tcsicmp(szBuffer,_T("y"))) break;
}
else
{
printf("Add items, ");
}
printf("Enter item name:");
gets(szBuffer);
wcscpy(TestItem[nTestItem].wszName, T2W(szBuffer));
// enter '.' to select a sample item from sst test server
if(!wcscmp(TestItem[nTestItem].wszName,L".")) wcscpy(TestItem[nTestItem].wszName, L"Simulated Card.Simulated Node.Random.I4");
printf("Enter item type (ie: VT_I4): ");
gets(sz2);
// you can enter '.' for VT_EMPTY and the server will select the right type
if(!strcmp(sz2,".")) strcpy(sz2,"VT_EMPTY");
if(!stricmp(sz2,"VT_I2")) TestItem[nTestItem].vt = VT_I2;
else if(!stricmp(sz2,"VT_I4")) TestItem[nTestItem].vt = VT_I4;
else if(!stricmp(sz2,"VT_EMPTY")) TestItem[nTestItem].vt = VT_EMPTY;
else if(!stricmp(sz2,"VT_R4")) TestItem[nTestItem].vt = VT_R4;
else if(!stricmp(sz2,"VT_R8")) TestItem[nTestItem].vt = VT_R8;
else if(!stricmp(sz2,"VT_BOOL")) TestItem[nTestItem].vt = VT_BOOL;
else
{
printf("Error: valid types: VT_EMPTY, VT_I2, VT_I4, VT_R4, VT_R8, VT_BOOL/n");
continue;
}
OPCITEMRESULT *pItemResult = NULL;
HRESULT *pErrors = NULL;
OPCITEMDEF ItemDef;
ItemDef.szAccessPath = L"";
ItemDef.szItemID = TestItem[nTestItem].wszName;
ItemDef.bActive = TRUE;
ItemDef.hClient = g_dwClientHandle++;
ItemDef.dwBlobSize = 0;
ItemDef.pBlob = NULL;
ItemDef.vtRequestedDataType = TestItem[nTestItem].vt;
TestItem[nTestItem].hClient = ItemDef.hClient;
hr = g_pIOPCItemMgt->AddItems(1, &ItemDef, &pItemResult, &pErrors);
if(FAILED(hr))
{
ShowError(hr,"AddItem()");
continue;
}
hr = S_OK;
if(FAILED(pErrors[0]))
{
ShowError(pErrors[0],"AddItem() item");
continue;
}
// record unique handle for this item
TestItem[nTestItem].hServer = pItemResult->hServer;
TestItem[nTestItem].vt = pItemResult->vtCanonicalDataType;
nTestItem++;
::CoTaskMemFree(pItemResult);
::CoTaskMemFree(pErrors);
if(nTestItem >= MAX_ITEMS) break;
}
g_dwNumItems = nTestItem;
// Enumerate items and display
OPCITEMATTRIBUTES *pItemAttr = NULL;
ULONG dwFetched = 0;
IEnumOPCItemAttributes *pEnumOPCItems = NULL;
hr = g_pIOPCItemMgt->CreateEnumerator(IID_IEnumOPCItemAttributes, reinterpret_cast<LPUNKNOWN*>(&pEnumOPCItems));
if(SUCCEEDED(hr))
{
printf("IOPCItemMgt::CreateEnumerator()/n");
pEnumOPCItems->Reset();
// NOTE: 3rd param must not be a NULL or the proxy will puke
hr = pEnumOPCItems->Next(static_cast<ULONG>(nTestItem), &pItemAttr, &dwFetched);
if(SUCCEEDED(hr))
{
if((dwFetched != static_cast<ULONG>(nTestItem)) || (hr == S_FALSE))
{
printf("Error: pEnumOPCItems->Next() - fetched != requested/n");
}
for(ULONG i = 0; i < dwFetched; i++)
{
printf("Item: %ls = VT_", pItemAttr[i].szItemID);
switch(pItemAttr[i].vtCanonicalDataType)
{
case VT_I2:
printf("I2 (short)");
break;
case VT_I4:
default:
printf("I4 (long)");
break;
case VT_R4:
printf("R4 (float)");
break;
case VT_R8:
printf("R8 (double)");
break;
case VT_BOOL:
printf("BOOL (boolean)");
break;
case VT_EMPTY:
printf("EMPTY (Server Defined)");
break;
}
printf("/n");
if(pItemAttr[i].szItemID)
::CoTaskMemFree(pItemAttr[i].szItemID);
if(pItemAttr[i].szAccessPath)
::CoTaskMemFree(pItemAttr[i].szAccessPath);
if(pItemAttr[i].dwBlobSize)
::CoTaskMemFree(pItemAttr[i].pBlob);
}
// must release the memory after we are done with it
::CoTaskMemFree(pItemAttr);
}
else
{
ShowError(hr,"pEnumOPCItems->Next()");
}
}
else
{
ShowError(hr,"IOPCItemMgt::CreateEnumerator()");
}
pEnumOPCItems->Release();
printf("Do you wish to write values to each item (Y/N)?");
gets(szBuffer);
if((*szBuffer == _T('y')) || (*szBuffer == _T('Y')))
{
g_bWriteEnable = true;
}
return 0;
}
void ShowError(HRESULT hr, LPCSTR pszError)
{
LPWSTR pwszError = NULL;
if((g_pIOPCServer != NULL) && SUCCEEDED(g_pIOPCServer->GetErrorString(hr, 0, &pwszError)))
{
printf("Error: %s failed,/n->%ls/n", pszError, pwszError);
// dump to log file
if(g_stream)
{
fprintf(g_stream, "%s Error: %s failed,/n->%ls/n", GetDateTime(), pszError, pwszError);
fflush(g_stream); // make sure buffers are flushed
}
::CoTaskMemFree(pwszError);
}
else
{
printf("Error: %s failed,/n->%lX/n", pszError, hr);
// dump to log file
if(g_stream)
{
fprintf(g_stream, "%s Error: %s failed,/n->%lX/n", GetDateTime(), pszError, hr);
fflush(g_stream); // make sure buffers are flushed
}
}
}
void StartErrorLog()
{
g_stream = fopen(_T("SST_client.log"), _T("w"));
if(g_stream)
{
fprintf(g_stream, "%sSST Client Start./n", GetDateTime());
}
}
void EndErrorLog()
{
if(g_stream)
{
fprintf(g_stream, "%sSST Client End./n", GetDateTime());
fclose(g_stream);
}
}
LPCSTR GetDateTime()
{
static char sz[128];
char sz2[128];
_strdate(sz);
strcat(sz, " ");
_strtime(sz2);
strcat(sz, sz2);
strcat(sz, "|");
return sz;
}
IDL文件如下:
OPC.idl文件,即为OPC2.0规范的OPCDA.IDL文件
OPCCOMN.idl文件,也是OPC规范的IDL文件。
两个文件的内容都可以在我的BLOG上复制。