Using QuickOPC from Visual C++ 6.0
Visual C++ 6.0 (also contained in Visual Studio 6.0) is still in use in some programming shops. Although it is not a development environment that we officially target, it is perfectly possible to use QuickOPC from Visual C++ 6.0. QuickOPC exposes its API via COM, and that can be consumed from Visual C++ 6.0 well.
There are, however, some differences from the current Visual C++, and the examples we ship with QuickOPC do not work "out of the box" with Visual C++ 6.0.
This article explains the main differences, and how to overcome them.
The #import directive does not support the 'libid:' keyword
There are no project-scope directory settings
There is no CComSafeArray class
Default character width may be 8 bits
Some (or all?) projects created from within Visual C++ 6.0 default to non-Unicode (8-bit) character set (ANSI). Since COM uses Unicode (16-bit) characters in strings, you either need to switch the project to Unicode, or properly convert the strings - most likely, with the use of macros like OLE2T, T2OLE, and USES_CONVERSION.
Example code
Below is a Visual C++ 6.0 example (ReadMultipleItems) modified from the one that we ship for Visual Studio 2012 or later. We have tested with a beta version of QuickOPC 2017.1, but it should work without changes with QuickOPC 2016.2 and many earlier versions as well.
The main file below (ReadMultipleItems.cpp) #include-s (through stdafx.h) a QuickOPC.h file, which contains common QuickOPC imports and definitions. Both these files are also listed, further below.
We will include this example (as full Visual C++ 6.0 project/solution) with QuickOPC 2017.1, but we do not plan to provide a "discoverability" of it, or actively maintain it or regularly update it with further releases.
ReadMultipleItems.cpp
// $Header: $
// Copyright (c) CODE Consulting and Development, s.r.o., Plzen. All rights reserved.
// ReadMultipleItems.cpp : Defines the entry point for the console application.
//
#include "stdafx.h" // Includes "QuickOpc.h", and other commonly used files
#include <atlconv.h>
//
int _tmain(int argc, _TCHAR* argv[])
{
USES_CONVERSION;
// Initialize the COM library. Note: If you choose STA, you will be responsible for pumping messages.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Instatiate the EasyOPC-DA client object
_EasyDAClientPtr ClientPtr(__uuidof(EasyDAClient));
_DAReadItemArgumentsPtr ReadItemArguments1Ptr(_uuidof(DAReadItemArguments));
ReadItemArguments1Ptr->ServerDescriptor->ServerClass = L"OPCLabs.KitServer.2";
ReadItemArguments1Ptr->ItemDescriptor->ItemId = L"Simulation.Random";
_DAReadItemArgumentsPtr ReadItemArguments2Ptr(_uuidof(DAReadItemArguments));
ReadItemArguments2Ptr->ServerDescriptor->ServerClass = L"OPCLabs.KitServer.2";
ReadItemArguments2Ptr->ItemDescriptor->ItemId = L"Trends.Ramp (1 min)";
_DAReadItemArgumentsPtr ReadItemArguments3Ptr(_uuidof(DAReadItemArguments));
ReadItemArguments3Ptr->ServerDescriptor->ServerClass = L"OPCLabs.KitServer.2";
ReadItemArguments3Ptr->ItemDescriptor->ItemId = L"Trends.Sine (1 min)";
_DAReadItemArgumentsPtr ReadItemArguments4Ptr(_uuidof(DAReadItemArguments));
ReadItemArguments4Ptr->ServerDescriptor->ServerClass = L"OPCLabs.KitServer.2";
ReadItemArguments4Ptr->ItemDescriptor->ItemId = L"Simulation.Register_I4";
COleSafeArray ArgumentsArray;
ArgumentsArray.CreateOneDim(VT_VARIANT, 4);
long i;
i = 0; ArgumentsArray.PutElement(&i, &_variant_t((IDispatch*)ReadItemArguments1Ptr));
i = 1; ArgumentsArray.PutElement(&i, &_variant_t((IDispatch*)ReadItemArguments2Ptr));
i = 2; ArgumentsArray.PutElement(&i, &_variant_t((IDispatch*)ReadItemArguments3Ptr));
i = 3; ArgumentsArray.PutElement(&i, &_variant_t((IDispatch*)ReadItemArguments4Ptr));
//LPSAFEARRAY pArgumentsArray = ArgumentsArray.Detach();
COleSafeArray ResultArray(ClientPtr->ReadMultipleItems(&ArgumentsArray.parray), VT_VARIANT);
//ArgumentsArray.Attach(pArgumentsArray);
for (i = 0; i < ResultArray.GetOneDimSize(); i++)
{
_variant_t vDAVtqResult;
ResultArray.GetElement(&i, &vDAVtqResult);
_DAVtqResultPtr DAVtqResultPtr = vDAVtqResult;
_ExceptionPtr ExceptionPtr(DAVtqResultPtr->Exception);
if (ExceptionPtr != NULL)
{
_variant_t exceptionAsString(ExceptionPtr->ToString);
_tprintf(_T("results(%d).Exception.ToString(): %s\n"), i, OLE2T(exceptionAsString.bstrVal));
continue;
}
_DAVtqPtr DAVtqPtr(DAVtqResultPtr->Vtq);
_variant_t vtqAsString(DAVtqPtr->ToString);
_tprintf(_T("results(%d).Vtq.ToString(): %s\n"), i, OLE2T(vtqAsString.bstrVal));
}
// Release all interface pointers BEFORE calling CoUninitialize()
ArgumentsArray.Clear();
ResultArray.Clear();
ClientPtr = NULL;
CoUninitialize();
TCHAR line[80];
_putts(_T("Press Enter to continue..."));
_fgetts(line, sizeof(line), stdin);
return 0;
}
stdafx.h
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#if !defined(AFX_STDAFX_H__FEF9DF0F_01E8_4734_B0FC_90EB0159E0BF__INCLUDED_)
#define AFX_STDAFX_H__FEF9DF0F_01E8_4734_B0FC_90EB0159E0BF__INCLUDED_
#define _WIN32_DCOM
#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers
#include <afx.h>
#include <afxwin.h> // MFC core and standard components
#include <afxext.h> // MFC extensions
#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT
#include <iostream>
#include "QuickOpc.h"
// TODO: reference additional headers your program requires here
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__FEF9DF0F_01E8_4734_B0FC_90EB0159E0BF__INCLUDED_)
QuickOPC.h
// $Header: $
// Copyright (c) CODE Consulting and Development, s.r.o., Plzen. All rights reserved.
// This files imports all QuickOPC libraries (and system libraries they require), creating Compiler COM Support wrappers.
// It also contains additional definitions for creating event sinks.
#pragma once
// #import system libraries
// mscorlib
#pragma warning(push)
#pragma warning(disable:4278) // 'ReportEvent': identifier in type library 'BED7F4EA-1A96-11D2-8F08-00A0C9A6186D' is already a macro; use the 'rename' qualifier
#if _MSC_VER >= 1300
#define IMPORT_MSCORLIB "libid:BED7F4EA-1A96-11D2-8F08-00A0C9A6186D"
#else
#define IMPORT_MSCORLIB "mscorlib.tlb"
#endif
#import IMPORT_MSCORLIB
#pragma warning(pop)
using namespace mscorlib;
// System.Drawing
#if _MSC_VER >= 1300
#define IMPORT_SYSTEM_DRAWING "libid:D37E2A3E-8545-3A39-9F4F-31827C9124AB"
#else
#define IMPORT_SYSTEM_DRAWING "System.Drawing.tlb"
#endif
#import IMPORT_SYSTEM_DRAWING
using namespace System_Drawing;
// System.Windows.Forms
#pragma warning(push)
#pragma warning(disable:4192) // automatically excluding 'IDataObject' while importing type library 'System.Windows.Forms.tlb'
#if _MSC_VER >= 1300
#define IMPORT_SYSTEM_WINDOWS_FORMS "libid:215D64D2-031C-33C7-96E3-61794CD1EE61"
#else
#define IMPORT_SYSTEM_WINDOWS_FORMS "System.Windows.Forms.tlb"
#endif
#import IMPORT_SYSTEM_WINDOWS_FORMS
#pragma warning(pop)
using namespace System_Windows_Forms;
// #import QuickOPC libraries
// OpcLabs.BaseLib
#if _MSC_VER >= 1300
#define IMPORT_OPCLABS_BASELIB "libid:ecf2e77d-3a90-4fb8-b0e2-f529f0cae9c9"
#else
#define IMPORT_OPCLABS_BASELIB "OpcLabs.BaseLib.tlb"
#endif
#import IMPORT_OPCLABS_BASELIB \
rename("value", "Value")
using namespace OpcLabs_BaseLib;
// OpcLabs.BaseLibForms
#if _MSC_VER >= 1300
#define IMPORT_OPCLABS_BASELIBFORMS "libid:A0D7CA1E-7D8C-4D31-8ECB-84929E77E331"
#else
#define IMPORT_OPCLABS_BASELIBFORMS "OpcLabs.BaseLibForms.tlb"
#endif
#import IMPORT_OPCLABS_BASELIBFORMS
using namespace OpcLabs_BaseLibForms;
// OpcLabs.EasyOpcClassic
#if _MSC_VER >= 1300
#define IMPORT_OPCLABS_EASYOPCCLASSIC "libid:1F165598-2F77-41C8-A9F9-EAF00C943F9F"
#else
#define IMPORT_OPCLABS_EASYOPCCLASSIC "OpcLabs.EasyOpcClassic.tlb"
#endif
#import IMPORT_OPCLABS_EASYOPCCLASSIC \
rename("itemId", "ItemId") \
rename("machineName", "MachineName") \
rename("requestedUpdateRate", "RequestedUpdateRate") \
rename("serverClass", "ServerClass")
using namespace OpcLabs_EasyOpcClassic;
// OpcLabs.EasyOpcUA
#if _MSC_VER >= 1300
#define IMPORT_OPCLABS_EASYOPCUA "libid:E15CAAE0-617E-49C6-BB42-B521F9DF3983"
#else
#define IMPORT_OPCLABS_EASYOPCUA "OpcLabs.EasyOpcUA.tlb"
#endif
#import IMPORT_OPCLABS_EASYOPCUA \
rename("attributeData", "AttributeData") \
rename("browsePath", "BrowsePath") \
rename("endpointDescriptor", "EndpointDescriptor") \
rename("expandedText", "ExpandedText") \
rename("inputArguments", "InputArguments") \
rename("inputTypeCodes", "InputTypeCodes") \
rename("nodeDescriptor", "NodeDescriptor") \
rename("nodeId", "NodeId") \
rename("value", "Value")
using namespace OpcLabs_EasyOpcUA;
// OpcLabs.EasyOpcForms
#if _MSC_VER >= 1300
#define IMPORT_OPCLABS_EASYOPCFORMS "libid:2C654FA0-6CD6-496D-A64E-CE2D2925F388"
#else
#define IMPORT_OPCLABS_EASYOPCFORMS "OpcLabs.EasyOpcForms.tlb"
#endif
#import IMPORT_OPCLABS_EASYOPCFORMS
using namespace OpcLabs_EasyOpcForms;
// DISPID-s
#define DISPID_EASYAECLIENTCONFIGURATIONEVENTS_LOGENTRY 1
#define DISPID_EASYAECLIENTEVENTS_NOTIFICATION 2001
#define DISPID_EASYDACLIENTCONFIGURATIONEVENTS_LOGENTRY 1
#define DISPID_EASYDACLIENTEVENTS_ITEMCHANGED 1002
#define DISPID_EASYUACLIENTCONFIGURATIONEVENTS_LOGENTRY 1
#define DISPID_EASYUACLIENTEVENTS_DATACHANGENOTIFICATION 1002
#define DISPID_EASYUACLIENTEVENTS_EVENTNOTIFICATION 1003
#define DISPID_EASYUACLIENTEVENTS_SERVERCONDITIONCHANGED 1101