Using QuickOPC from Visual C++ 6.0

From OPC Labs Knowledge Base
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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

Instead of specifying the library ID (TLB) using the 'libid:' keyword, you need to specify it simply using its file name. The QuickOPC.h file listed in the example in this article already does that. The file needs to be on a path that the compiler searches when processing the #import directive; for it, see the next point.

There are no project-scope directory settings

As opposed to newer versions of Visual Studio, you cannot specify search paths as part of a project; they need to be set globally for the whole Visual Studio (Tools -> Options; then, the Directories tab). You will need following paths:

  • Include files: to the .NET Framework directory, typically C:\WINDOWS\MICROSOFT.NET\FRAMEWORK\V4.0.30319 .
  • Include files: to the SDK\Lib directory of the QuickOPC installation (for QuickOPC type libraries).
  • Include files: to the SDK\Include directory of the QuickOPC installation (if you are going to use the QuickOPC.h file that ships with the product).

There is no CComSafeArray class

If you are using functions that work with COM safe arrays, you will find that the CComSafeArray (template) class is not available in Visual C++ 6.0. It can be replaced e.g. by direct calls to Win32 API, but there is also a different class with similar purpose, called COleSafeArray. The ReadMultipleItems.cpp file listed in this article (further below) shows how this class can be used to pass arguments to a QuickOPC method, and retrieve the results.

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 it 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.


// $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[])

    // Initialize the COM library. Note: If you choose STA, you will be responsible for pumping messages.

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

    COleSafeArray ResultArray(ClientPtr->ReadMultipleItems(&ArgumentsArray.parray), VT_VARIANT);

    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));

        _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()
    ClientPtr = NULL;


    TCHAR line[80];
    _putts(_T("Press Enter to continue..."));
    _fgetts(line, sizeof(line), stdin);
    return 0;


// 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
#include <afxcmn.h>			// MFC support for Windows Common Controls

#include <iostream>

#include "QuickOpc.h"
// TODO: reference additional headers your program requires here

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__FEF9DF0F_01E8_4734_B0FC_90EB0159E0BF__INCLUDED_)


// $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"
#define IMPORT_MSCORLIB "mscorlib.tlb"
#pragma warning(pop)
using namespace mscorlib;

// System.Drawing
#if _MSC_VER >= 1300
#define IMPORT_SYSTEM_DRAWING "libid:D37E2A3E-8545-3A39-9F4F-31827C9124AB"
#define IMPORT_SYSTEM_DRAWING "System.Drawing.tlb"
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"
#define IMPORT_SYSTEM_WINDOWS_FORMS "System.Windows.Forms.tlb"
#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"
#define IMPORT_OPCLABS_BASELIB	"OpcLabs.BaseLib.tlb"
	rename("value", "Value")
using namespace OpcLabs_BaseLib;

// OpcLabs.BaseLibForms
#if _MSC_VER >= 1300
#define IMPORT_OPCLABS_BASELIBFORMS	"libid:A0D7CA1E-7D8C-4D31-8ECB-84929E77E331"
#define IMPORT_OPCLABS_BASELIBFORMS	"OpcLabs.BaseLibForms.tlb"
using namespace OpcLabs_BaseLibForms;

// OpcLabs.EasyOpcClassic
#if _MSC_VER >= 1300
#define IMPORT_OPCLABS_EASYOPCCLASSIC	"libid:1F165598-2F77-41C8-A9F9-EAF00C943F9F"
#define IMPORT_OPCLABS_EASYOPCCLASSIC	"OpcLabs.EasyOpcClassic.tlb"
	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"
#define IMPORT_OPCLABS_EASYOPCUA	"OpcLabs.EasyOpcUA.tlb"
	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"
#define IMPORT_OPCLABS_EASYOPCFORMS	"OpcLabs.EasyOpcForms.tlb"
using namespace OpcLabs_EasyOpcForms;