C# · 12月 19, 2021

OPC CLIENT程序(C语言篇,OPC1.0,2.0规范)

转自:https://www.xuebuyuan.com/2034211.html

本程序为以前个人学习时,在国外网站上下载,本来想自己重新写一篇,因为各方面的原因,没有写,所以现在把下载的这个程序帖出来供大家学习。此程序个人觉得值得一看,虽然看起来程序有些长。

本程序一共包括三个文件: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

#include

#include

#include

#include

//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

#include

// 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 CComCTestAdviseSink;

typedef CComObject 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],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(::GlobalLock(pSTM->hGlobal));

if(pBuffer == NULL) return;

const OPCGROUPHEADERWRITE *pHeader = reinterpret_cast(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