OPC Technology Forum http://www.opc-china.com OPC server, customer program technology discussion OPC Client Program (C language articles, OPC1.0, 2.0 specification)
When this program is formerly personal learning, downloaded on foreign websites, I originally wanted to rewrite an article, because of all the reasons, did not write, so now put the downloaded procedure for everyone to learn. This program feels worth seeing, although it looks a bit long.
This program includes three files: opc.idl opccomn.idl opctest.cpp
Opc.idl opccomn.idl is the IDL definition of the OPC specification, opctest.cpp is the main program.
The opctest.cpp file is as follows:
// 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 this
// 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 askYNC 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 size ignore transaction ID's and
// Use the client handle returned as value.
//
// 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 solilaly to assist in understanding
// The OPC Data Access Specification In Relation to a SST OPC Server.
// this code is provided as-is and welcome warranty or support of any sort.//
// this code May be Freely Re-use 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 ing 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 valuees or askNC 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 specification
IopcgroupStatemgt * g_piopcgroupstatemgt = null;
Iopcasyncio * g_piopcasyncio = NULL; //opc1.0 specification
IOPCSYNCIO * g_piopcsyncio = null; IOPCITEMMGT * g_piopcitemmgt = NULL;
Iopcasyncio2 * g_piopcasyncio2 = NULL;
IOPCCMMON * g_piopccommon = NULL;
IUNKNOWN * G_PIGROUNKNOWN = NULL;
IopcbrowServeraddresSpace * g_piopcbrowse = null;
// critical section stuff
CCOMAUTOCRITICATION G_READCS;
Ccomautocriticalsection g_writecs;
Class Clock
{
PUBLIC:
CCOMAUTOCRITICATION * 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_write);
Class ATL_NO_VTABLE CTESTADVISESINK;
Class ATL_NO_VTABLE COPCCALLBACK;
TypedEf CComObject
TypedEf CComObject
Uint g_nopcformatdata = :: registerclipboardFormat ("opcstmformatdata);
Uint g_nopcformatdata = :: registerclipboardformat ("opcstmformatdataatime);
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 ShrowError (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 Hserve [MAX_ITEMS];
Variant Val [MAX_ITEMS];
Variant vcount;
For (DWORD DW = 0; 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 ()) { // r r t 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 { 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 { IF (failed (PERRORS [DW])) { Char SZ [100]; Sprintf (SZ, "Syncio-> Read (% ls) returned", TestItem [DW] .wszname); ShowError (PerroS [dw], sz); } } } Else { ShowError (HR, "Sync Read"); } IF (g_bwriteenable) // Quick Write enable Hack { // pump out data sync to items For (dw = 0; 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 ((& 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 { IF (Failed (PerrorD (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 { :: 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) OONDATACHANGE (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 IF (pBuffer == null) return; Const opcgroupheaderwrite * pheader = reinterpret_cast IF (Failed (Phet-> HRSTATUS)) { ShowError (Phet-> Hrstatus, "General Async Write"); } IF (g_dwwritetransid! = pheter-> dwtransaction) { ShowError (S_ok, "Async Write Callback, TransactionId's Do Not Match"); } DWORD DWSIZE = SizeOf (OpcGroupHeaderwrite); For (DWORD DW = 0; DW { Const opcitemheaderwrite * pitemheader = reinterpret_cast IF (Failed (Pitemheader-> dwerror)) { ShowError (Pitemheader-> Dwerror, "async write request"); } } g_bwritecomplete = true; :: GlobalUnlock (PSTM-> Hglobal); Return; } Else IF (pfe-> cfformat! = g_nopcformatdataure) return; Const lpbyte pBuffer = reinterpret_cast IF (pBuffer == null) return; Const opcgroupheader * phet = reinterpret_cast IF (Faader-> HRSTATUS) { ShowError (Phet-> Hrstatus, "General Async Read"); } IF (g_bpoll) { // if we are polling, ignore async updates IF (Phet-> DWTRANSACTIONID == 0) { Return; } Read_lock; IF (Pheter-> 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 { Const opcitemheader1 * pitemhead = reinterpret_cast IF (PitemHeader-> wquality == opc_quality_good) { Variant * pvalue = reinterpret_cast 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 (succeededed (:: VariantchanGType (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_nopcformatdata; // NEED TO FILL THE REST OF THE STRUCT or THE Proxy Make Puke Formatetc.pt = NULL; Formatetc.dwaspect = dvaspect_content; Formatetc.lindex = -1; Formatetc.Tymed = TYMED_HGLOBAL; CComcTestAdvisesink * psink = null; Atltry (psink = new ccomcteestadvises); 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, & DWRITECONNECTION); 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 Hserve [MAX_ITEMS]; Variant Val [MAX_ITEMS]; Variant vcount; DWORD DW = 0; IF (g_bwriteenable) { For (dw = 0; 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 { V_vt (& val [dw]) = vt_i2; :: VariantCopy (& Val [dw], & vcount); :: VariantChangeType (& Val [DW], & Val [DW], 0, V_VT (& TestItem [DW])); } V_i2 (& vcount) ; IF ((& 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 (DWRITECONNECTION, G_DWNUMITEMS, HSERVER, VAL, & G_DWWWRITETRANSID, & PERRORSWRITE); g_writecs.unlock (); IF (Failed (HR)) { ShowError (HR, "asyncio-> write ()"); } Else IF (hr == s_false) { For (dw = 0; DW { IF (Failed (PerrorD (PERRORSWRITE [DW]))) { ShowError (PerrorSwrite [dw], "asyncio-> write () item returned"); } } :: cotaskmemfree (perrorswrite); } ELSE // S_OK { :: cotaskmemfree (perrorswrite); } } } IF (g_bwriteenable) { For (dw = 0; 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 dwriteconnection = 0; g_bpoll = true; // We are polling for value Formatetc.cfformat = g_nopcformatdata; // NEED TO FILL THE REST OF THE STRUCT or THE Proxy Make Puke Formatetc.pt = NULL; Formatetc.dwaspect = dvaspect_content; Formatetc.lindex = -1; Formatetc.Tymed = TYMED_HGLOBAL; CTestAdVisesink * psink = null; Atltry (psink = new ccomcteestadvises); 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, & DWRITECONNECTION); 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 Hserve [MAX_ITEMS]; Variant Val [MAX_ITEMS]; Variant vcount; DWORD DW = 0; IF (g_bwriteenable) { For (dw = 0; 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 { V_vt (& val [dw]) = vt_i2; :: VariantCopy (& Val [dw], & vcount); :: VariantchanGType (& Val [DW], & Val [DW], 0, V_VT (& TestItem [DW]);} V_i2 (& vcount) ; IF ((& 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 (DWRITECONNECTION, G_DWNUMITEMS, HSERVER, VAL, & G_DWWWRITETRANSID, & PERRORSWRITE); g_writecs.unlock (); IF (Failed (HR)) { ShowError (HR, "asyncio-> write ()"); } Else IF (hr == s_false) { For (dw = 0; DW { IF (Failed (PerrorD (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 { :: 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 * pvrough, / * [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 { 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 (succeededed (:: VariantchanGType (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 * pvrough, / * [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 { 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 (::4))) Printf ("% ld / t", v_i4); 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 { IF (failed (PERRORS [DW])) { ShowError (PerroS [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 ccomcopcallback); 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 Hserve [MAX_ITEMS]; Variant Val [MAX_ITEMS]; Variant vcount; DWORD DW = 0; IF (g_bwriteenable) { For (dw = 0; 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 { V_vt (& val [dw]) = vt_i2; :: VariantCopy (& Val [dw], & vcount); :: VariantchanGType (& Val [DW], & Val [DW], 0, V_VT (& TestItem [DW]);} V_i2 (& vcount) ; IF ((& 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 { IF (Failed (PerrorD (PERRORSWRITE [DW]))) { ShowError (PERRORSWRITE [DW], "asyncio2-> write () item returned"); } } :: cotaskmemfree (perrorswrite); } ELSE // S_OK { :: cotaskmemfree (perrorswrite); } } } IF (g_bwriteenable) { For (dw = 0; DW { :: VariantClear (& Val [dw]); } } :: VariantClear (& vcount); // Release Interfaces HR = PCP-> Unadvise (dwcookie); IF (Failed (HR)) { ShowError (HR, "Unadvise ()"); } PCP-> Release (); PCPC-> Release (); While (psink-> relation ()); 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 ccomcopcallback); 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"); 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 Hserve [MAX_ITEMS]; Variant Val [MAX_ITEMS]; Variant vcount; For (DWORD DW = 0; 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 { V_vt (& val [dw]) = vt_i2; :: VariantCopy (& Val [dw], & vcount); :: VariantChangeType (& Val [DW], & Val [DW], 0, V_VT (& TestItem [DW])); } V_i2 (& vcount) ; IF ((& 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 { IF (Failed (PerrorD (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, "); IF (Failed (HR)) { ShowError (HR, "asyncio2-> read ()"); } Else IF (hr == s_false) { For (dw = 0; DW { IF (Failed (PERRORSREAD [DW])) { ShowError (PerroSread [DW], "asyncio2-> read () item returned"); } } :: CotaskMemfree (PerroSread); } ELSE // S_OK { :: CotaskMemfree (PerroSread); } } :: Sleep (g_dwupdaterate); } For (dw = 0; DW { :: VariantClear (& Val [dw]); } :: VariantClear (& vcount); // Release Interfaces HR = PCP-> Unadvise (dwcookie); IF (Failed (HR)) { ShowError (HR, "Unadvise ()"); } PCP-> Release (); PCPC-> Release (); While (psink-> relation ()); 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 (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, & Cls); 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 ALOW 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_IOPCBROWSESERVERVERADDRESSSSSPACE, (Void **) & g_piopcbrowse; if (Failed (HR)) { ShowError (HR, "Queryinterface (IID_IOPCBROWSESERVERVERADDRESSSSPACE"); } 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"); } HR = g_pigroupunknown-> queryinterface (IID_IOPCGroupStatemgt, (void **) & g_piopcgroupstatemgt); IF (Failed (HR)) { Showerror (HR, "Queryinterface (IID_IOPCGroupStatemgt"); HR = g_pigroupunknown-> queryinterface (IID_ID_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"); } 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"); } 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_piopcser -> 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 Twhere 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")))) } 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 (Stricmp (SZ2, "VT_I2")) TestItem [NTESTITEM] .vt = vt_i2; Else if (! Stricmp (Stricmp (SZ2, "VT_I4")) TestItem [NTESTITEM] .vt = vt_i4; Else if (! Stricmp (Stricmp (SZ2, "VT_EMPTY)) TestItem [NTESTITEM] .vt = vt_empty; Else if (! Stricsp (Stricmp (SZ2, "VT_R4")) TestItem [NTESTITEM] .vt = vt_r4; Else if (! Stricmp (Stricmp (SZ2, "VT_R8")) TestItem [NTESTITEM] .vt = vt_r8; Else if (! Stricmp (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, "); if (Failed (HR)) { ShowError (HR, "AddItem ()"); CONTINUE; } HR = S_OK; IF (Failed (PerroS [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_IENUMOPCITEMATRIBUTES, ReinterPret_cast En (ac)) { Printf ("IOPCITEMMGT :: CreateEnumerator () / N"); Penumopcitems-> reset (); // NOTE: 3rd Param Must Not Be a null or the proxy will puke HR = penumopcitems-> next (static_cast En (ac)) { IF ((dwfetched! = static_cast { Printf ("Error: Penumopcitems-> next () - fetched! = Requested / N"); } For (ulong i = 0; 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 (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 value 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; } The IDL file is as follows: Opc.idl file, which is OPCDA.IDL file for OPC2.0 specification. OPCComn.idl file, is also an IDL file of the OPC specification. The content of the two files can be copied on my blog.