9

VxDCOM Applications

COM Support and Optional Component VxDCOM



9.1    Introduction

VxDCOM is the technology that implements COM and distributed COM (DCOM) on VxWorks. The name VxDCOM refers both to this technology and to the optional product. The VxDCOM optional product adds DCOM capabilities to the basic COM support that is included in the standard VxWorks facilities.

COM stands for the Component Object Model, which is a binary specification for component-based object communication. The Wind River VxDCOM technology was designed to significantly facilitate the creation of both COM and DCOM components, by automating much of the boiler-plate code. Thus, VxDCOM enables users to easily write distributed object applications for use in real-time embedded systems software.

The VxDCOM documentation assumes working knowledge of the COM technology, and focuses on the VxDCOM facilities that are used to create server applications for execution on VxWorks. The documentation comprises both this chapter and the Tornado User's Guide: Building COM and DCOM Applications. The latter includes a step-by-step overview of how to create and build a VxDCOM application. It covers procedural information for running the VxDCOM wizard, descriptions of the generated output, and the process of building and deploying VxDCOM applications.

This chapter covers the following topics, which are primarily reference material and programming issues:

  • a brief overview of the VxDCOM technology

  • the Wind Object Template Library (WOTL) classes

  • the auto-generated WOTL skeleton code

  • the Wind IDL compiler and command-line options

  • the structure and meaning of IDL definitions in the auto-generated files

  • the configuration parameters for DCOM support in VxWorks

  • real-time extensions and OPC interfaces

  • tips and examples for writing implementation code

  • an implementation comparison of VxDCOM and ATL

The demo example used in this chapter is a DCOM server application that includes both a Visual Basic and a C++ client. Source for this demo is located in the installDir/host/src/vxdcom/demo/MathDemo directory.



9.2    An Overview of COM Technology

COM is a specification for a communication protocol between objects, which are called COM components. COM components are the basic building blocks of COM client/server applications. The COM component is the server that provides services to a client application by means of the functionality it advertises as its COM interfaces. COM interfaces are sets of method prototypes that, viewed as a whole, describe a coherent, well-defined functionality or service that is offered to COM clients.

9.2.1   COM Components and Software Reusability

COM components are instantiated from classes called CoClasses. The CoClass definitions include (single or multiple) inheritance from COM interfaces. The CoClass is then required to implement the methods of the interfaces from which it is derived. While the interface itself strictly defines the service it provides, the implementation details are completely hidden from the client in the CoClass implementation code. Clients are essentially unaware of COM components and interact only with COM interfaces. This is one of the strengths of the COM technology design; and it enables software developers to both update and reuse components without impacting the client applications.

The following sections describe these fundamental elements of COM in more detail.

COM Interfaces

An interface is a named set of pure method prototypes. The interface name typically reflects the functionality of its methods and, by convention, begins with the capital letter I. Thus, IMalloc might represent an interface that allocates, frees, and manages memory. Similarly, ISem might be an interface that encapsulates the functionality of a semaphore.

As part of the COM technology, basic interface services are defined in the COM library. The COM and DCOM libraries that ship with VxDCOM are implementations of the basic interfaces that are required for the aspects of COM technology that VxDCOM supports.


*      
NOTE: These COM and DCOM libraries are used by the C++ template class library (WOTL) and are shipped with VxDCOM. For the API definitions, see comLib.h and dcomLib.h.

As a developer, you typically define your own custom interfaces. Interface definitions must conform to the COM specification, including descriptive attributes and specifications for the interface, its methods, the method parameters, and the return type (see The Interface Definition). By strictly adhering to this specification, the COM interface becomes a contractual agreement between client and server, and enables the communication protocol to function properly.


*      
NOTE: Interface definitions are pure prototypes, similar to abstract C++ classes that contain only pure virtual methods. In fact, the Wind Object Template Library (WOTL), which you use to write VxDCOM applications, implements interfaces in exactly this manner.

The contract between client and server is to provide a service; but it is not (and need not be) a guarantee how that service is implemented. The implementation details are hidden in the CoClass.

CoClasses

CoClasses are the classes that declare the interfaces and implement the interface methods. The CoClass definition includes a declaration of all the interfaces that it agrees to support. The CoClass implementation code must implement all of the methods of all of the interfaces declared by that CoClass. When the CoClass is instantiated, it becomes a COM component, or server. Client applications query for interfaces services they need, and if your server supports those interfaces, then it communicates with the client application through those interfaces.

Interface Pointers

In the COM model, the communication protocol between COM clients and servers in handled using pointers to the COM interfaces. The interface pointers are used to pass data between COM clients and COM servers. The client application uses COM interface pointers to query COM servers for specific interfaces, to obtain access to those interfaces, and to invoke the methods of the interfaces. Because the COM specification and communication protocol is based on these pointers, it is considered to be a binary standard.

Because COM technology uses a binary standard, it is theoretically possible for COM components to be written in any language, and to run on any operating system, while still being able to communicate. In this way, COM technology offers great flexibility and component reusability to software developers, especially those wanting to port applications to different operating systems.


*      
NOTE: Currently, the only languages supported for COM servers under VxDCOM are C and C++; for DCOM servers, only C++ is currently supported.

VxDCOM Tools

Correctly defining interfaces and CoClasses, according to the COM specification, can be detailed and tedious. However, because the specification follows standard rules, the VxDCOM tools are able to facilitate this process by automatically generating much of the code for you. For example, using the VxDCOM wizard, you simply name your interface, and then select method parameter types and attributes from predefined lists. The wizard automatically generates the correct method return type for you, as well as your interface and CoClass definitions.

When building the application, the Wind IDL (Interface Definition Language) compiler, widl, generates additional code used for server registration and marshaling. Thus, you only need to use the tools, write your client and server implementation code, and build your project, in order to develop COM applications.

9.2.2   VxDCOM and Real-time Distributed Technology

COM provides a common framework for describing the behavior and accessibility of software components. Extending the basic COM technology across process and machine boundaries into the realm of distributed objects requires a network RPC protocol. For this, DCOM uses Object RPC (ORPC), a Microsoft extension of the DCE-RPC specification. ORPC uses the marshaled interface pointer as the protocol for communication between COM components, specifying the way references to the component's interfaces are represented, communicated, and maintained. COM provides this functionality in its primary interfaces as defined in the standard COM libraries.

The VxDCOM technology supports the basic COM interfaces as part of the standard VxWorks facilities, and the DCOM network protocol as part of the VxDCOM optional product. These implementations are documented in the VxDCOM COM and DCOM libraries, which contain sets of COM and DCOM-related interfaces targeted for embedded systems development, as well as support for ORPC.


*      
NOTE: For details on these interfaces, see the appropriate .idl files, such as vxidl.idl. For the API documentation, see comLib.h and dcomLib.h.

VxDCOM supports both in-process and remote server models. You can write a server on a VxWorks target that provides services to client applications running on other VxWorks targets or on PCs running Windows NT. Using the DCOM protocol for embedded intelligent systems (such as telecommunications devices, industrial controllers, and office peripherals) allows developers to extend the COM programming environment out into the local area network, or even into the Internet, without concern for networking issues.


*      
NOTE: VxDCOM does not support VxWorks to Windows NT when starting the connection up on a VxWorks target. A callback from NT to VxWorks can be used instead. VxDCOM does support VxWorks to NT connections, if all NT security is disabled. For details, see the Tornado User's Guide: Authenticate the Server and the Tornado User's Guide: Registering, Deploying, and Running Your Application.



9.3    Using the Wind Object Template Library

The Wind Object Template Library (WOTL) is a C++ template class library designed for writing VxDCOM clients and servers. It is source-compatible with a subset of ATL (the Microsoft COM template class library), on which it was modeled. WOTL is the framework you use to write your client and server code.

You run the VxDCOM wizard to create your COM components. This wizard generates output files. Among these output files are headers and implementation source files that contain skeleton code in WOTL. Using this skeleton code, you complete the implementation details for your server, and for any optional client applications.

The Tornado User's Guide: Building COM and DCOM Applications describes which files to use to write implementation code appropriate to your type of application. Further details are discussed in 9.10 Writing VxDCOM Servers and Client Applications and 9.10.3 Writing Client Code. Code snippets in this chapter are from the CoMathDemo demo, located in the installDir/host/src/VxDCOM/demo/MathDemo directory.

9.3.1   WOTL Template Class Categories

There are three VxDCOM template classes that you can define using WOTL. These types are distinguished by whether or not the class uses a CLSID, and whether or not there can be only one instantiation of the class at any one time. A CLSID is a unique identification value for a class, and allows the class to be externally instantiated via a class factory.


*      
NOTE: Class factories are objects that instantiate CoClasses based on CLSIDs.

The three WOTL template classes are categorized as follows:

These are true COM classes, meaning that they have registered CLSIDs and are instantiated through class factories. The CComCoClass template class is used to declare these classes. This is the template class that the VxDCOM wizard uses to generates skeleton code for the CoClass definitions for your COM components.

These are also true CoClasses, but for which there is only one instance. Thus, every invocation of IClassFactory::CreateInstance( ) returns the same instance. To declare such a class, use the macro DECLARE_CLASSFACTORY_SINGLETON in your class definition.

These are simple classes that are not technically true COM classes. The template class has no associated CLSID (class identification value) and thus, instantiates objects by using a default class-factory creation mechanism. These can never be DCOM classes. To declare such a class, use the CComObject class. You typically use these classes to create internal objects that enhance the object-oriented functionality of your code.

The WOTL classes and class templates are described below, with details on how to use them. WOTL classes are declared in the header installDir/target/h/comObjLib.h. Some additional helper classes, such as VxComBSTR, are defined in installDir/target/h/comObjLibExt.h. For more information, see 9.11 Comparing VxDCOM and ATL Implementations..

9.3.2   True CoClass Template Classes

The VxDCOM wizard automatically generates the template class definitions for your CoClass, including proper derivation from base classes and from interfaces you want to implement in your CoClass. The following sections explain this code.

CComObjectRoot - IUnknown Implementation Support Class

CComObjectRoot is the base class for all VxDCOM classes and provides task-safe IUnknown implementation, plus support for aggregatable object. To declare a class that provides concrete implementations for one or more COM interfaces, the class must inherit from both CComObjectRoot and the interfaces it implements. The following example declares a class, CExample, that supports the interface IExample and will implement its methods:

class CExample: public CComObjectRoot, public IExample 
    { 
        // Private instance data 
    public: 
        // Implementation, including IExample methods... 
    };

As the implementation class for IUnknown, all WOTL implementation classes that you declare should include CComObjectRoot as an intimate base class.

CComCoClass - CoClass Class Template

CComCoClass is the template class that provides the CoClass implementation; that is, it includes a publicly-known CLSID for the class, and one or more publicly known interfaces. Objects created from this class are considered to be true COM objects because they can be 'externally' instantiated using the CLSID and calling the COM or DCOM API creation functions CoCreateInstance( ) and CoCreateInstanceEx( ).

CComCoClass wraps the functionality required by the class factory class and the registration mechanism, so that classes derived from it inherit that functionality. CComClassFactory is the class factory class template that implements the standard IClassFactory COM interface. This interface allows objects to be created at run-time using CLSIDs. The declaration for this class follows the standard WOTL format, inheriting from CComObjectRoot and the interface being implemented.

CoClass Definitions

The definition for CoClasses is automatically generated by the wizard. These CoClasses are derived from all of the following:

  • the ultimate WOTL base class, CComObjectRoot
  • the WOTL base class template for all CoClasses, CComCoClass
  • the primary interface, which is implemented by the CoClass
  • all additional interfaces implemented by the CoClass
Example Definition

The example below shows this inheritance in the definition of the server CoClass in the CoMathDemoImpl.h header file:

class CoMathDemoImpl  
    : public CComObjectRoot, 
      public CComCoClass<CoMathDemoImpl, &CLSID_CoMathDemo> 
      ,public IMathDemo 
      ,public IEvalDemo 
    { 
        // body of definition 
    };

The generated server implementation header for your application will include a declaration for your CoClass that is similarly derived from CComObjectRoot, from CComCoClass, your primary interface, and from any additional interfaces. Note that WOTL supports multiple inheritance, which includes inheritance from multiple interfaces; so your CoClass definition derives from each of the interfaces it implements.

Using CLSID Instantiation

The CComCoClass class template creates its class instantiation based upon the name of the CoClass and the CLSID. The &CLSID_CoMathDemo parameter, in the CoMathDemo example code above, represents the GUID (globally unique identifier) that identifies the CoClass. The CLSID for your CoClass is CLSID_basename, and can be found in the basename_i.c file, generated by the widl compilation. The parameter you would use is thus &CLSID_basename.


*      
NOTE: The CLSID_basename (class ID) is declared as a const, and represents the GUID generated for the CoClass from the compilation of the .idl file. CLSID_basename is used in the server header and implementation files, and also in the client implementation file. Use this const when referencing the CLSID (class ID) for your CoClass.

9.3.3   Lightweight Object Class Template

Lightweight classes create COM objects without a CLSID; and because of this, they are not considered to be true CoClasses. Lightweight classes are typically used internally to optimize the object-oriented design of an application through the use of interfaces.

CComObject is the lightweight object class template for WOTL. CComObject is a wrapper template class used to create classes for lightweight objects. The actual implementation class used as an argument to the templatized class CComObject derives from CComObjectRoot in order to inherit IUnknown interface support.

In the following CComObject template class instantiation, CExample is the class defined in the example above. It derives from CComObjectRoot and implements the IExample interface:

CComObject<CExample>

Lightweight classes do not have a CLSID and, thus, cannot be externally instantiated using the normal class-factory method. For this reason, CComObject provides default class-factory implementation. These classes are instantiated by calling the function CComObject<CExample>::CreateInstance( ), rather than by using CoCreateInstance( ). This function takes the same arguments as IClassFactory::CreateInstance( ).

The VxDCOM wizard does not generate definitions for this template class. To create a lightweight class object, simply add the definition and implementation code to your header and source files.

9.3.4   Single Instance Class Macro

To define a class as a singleton, that is, a class for which there is only one instance, you include a DECLARE_CLASSFACTORY_SINGLETON statement in your class definition.

class CExample  
    : public CComObjectRoot, 
      public CComCoClass<CExample, &CLSID_Example>, 
      public IExample 
    { 
        // Private instance data 
    public: 
        DECLARE_CLASSFACTORY_SINGLETON(); 
        // Implementation, including IExample methods... 
    };

When you declare a class using this macro, every call to IClassFactory::CreateInstance( ) returns the same instance. The VxDCOM wizard does not generate definitions for this template class. To create a single instance class object, simply add the definition and implementation code to your header and source files.



9.4    Reading WOTL-Generated Code

The purpose of this section is to familiarize you with the code appearing in the wizard-generated output files. For the most part, you will not need to modify this code to implement your server or client. However, WOTL includes many helper macros, which have been defined for convenience and readability. This section discusses these macro definitions to enable users to more easily read the generated WOTL code and, over time, better understand how VxDCOM implements the COM technology. This section also summarizes the generated interface and CoClass definitions and the files they appear in.

9.4.1   WOTL CoClass Definitions

The definition for your server CoClass is generated by the wizard in the implementation header, basenameImpl.h. This header includes two basic header files:

  • the WOTL header file, installDir/target/h/comObjLib.h

  • the widl-generated interface method prototype header, basename.h.

You can include other header files as necessary, depending upon your implementation code. For example, the CoMathDemoImpl.h header file looks like this:

/* CoMathDemoImpl.h -- auto-generated COM class header */ 
 
#include "comObjLib.h"    // COM-object template lib 
#include "CoMathDemo.h"   // IDL-output interface defs 
#include <string>

The widl-generated interface prototype header, basename.h (mentioned above), declares your interfaces as abstract C++ classes with only pure virtual functions. Those methods are then re-defined, in the CoClass that implements them, as virtual STDCALL functions that return an HRESULT. Essentially, the CoClass is defined as implementing the virtual methods of its purely abstract base class, which is the interface.

9.4.2   Macro Definitions Used in Generated Files

There are three automatically generated WOTL files that use macros in their definitions. These files are:

  • the interface prototype header, basename.h, generated by widl

  • the server CoClass header, basenameImpl.h, generated by the wizard

  • the server implementation file, basenameImpl.cpp, generated by the wizard

The macros used in these files are defined in the header installDir\target\h\comLib.h file. The five definitions are as follows:

#define STDMETHODCALLTYPE     STDCALL 
#define STDMETHODIMP          HRESULT STDMETHODCALLTYPE 
#define STDMETHODIMP_(type)    type STDMETHODCALLTYPE 
#define STDMETHOD(method)      virtual HRESULT STDMETHODCALLTYPE method  
#define STDMETHOD_(type,method)   virtual type STDMETHODCALLTYPE method 

These macros are used when mapping interface method definitions across .idl files, header files, and implementation files. The details of this process are described in the sections that follow. Table 9-1 provides a quick reference summary of these mappings when reading the files.

Table 9-1:   Interface Method Definition Mappings Across Files   


File
Meaning
Tool Used for Code Generation
Method Prototype Syntax

basename.idl
IDL interface definitions
wizard
HRESULT routine_name (parameters);
basename.h
C++ interface method prototypes
widl
virtual HRESULT STDMETHODCALLTYPE 
routine_name(parameters) = 0;
basenameImpl.h
C++ CoClass
method prototypes
wizard
STDMETHOD(method) (parameters);
basenameImpl.cpp
C++ CoClass
method definitions
wizard
STDMETHODIMP coclass :: routine_name 
(params) { // implementation code }

Mapping IDL Definitions to Interface Header Prototypes

Using these macros, the interface method prototypes are mapped so that each interface method definition, found in the .idl file, is defined as a virtual STDCALL method that returns an HRESULT in the generated interface header file, basename.h. Thus, the following definition syntax in the .idl file:

HRESULT routine_name (parameters);

becomes:

virtual HRESULT STDMETHODCALLTYPE routine_name(parameters) = 0;

in the basename.h header file.

Mapping Interface Prototypes to CoClass Method Definitions

Further, the macro STDMETHODCALLTYPE is defined in comBase.h as STDMETHOD:

#define STDMETHOD(method)    virtual HRESULT STDMETHODCALLTYPE method

So, for each interface method you define in the wizard, the final prototype in your basenameImpl.h file is:

STDMETHOD(method) (parameters);


*      
NOTE: If you edit the C++ header, basenameImpl.h, by hand, use the CoMathDemo files or other wizard generated files as examples.

Defining CoClass Methods in Implementation Files

The server implementation file uses the macro STDMETHODIMP to represent a STDCALL method that returns an HRESULT. Therefore, the method definitions in your generated implementation files, as in the CoMathDemoImpl.cpp file, are of the following form:

STDMETHODIMP coclass :: routine_name (params) { // implementation code }

9.4.3   Interface Maps

WOTL uses the COM_MAP style of interface mapping within the class definition. These macros are similar to those used in ATL and are part of the implementation of the IUnknown method, QueryInterface( ). The WOTL library definitions of COM_MAP macros are found in the WOTL header file, comObjLib.h.

The COM_MAP macros define a function called _qi_impl( ), which does run-time casts to obtain the requested interface pointer. Other than that, the layout of the COM_MAP in your CoClass is identical to that of an ATL map; however, only the entries for COM_INTERFACE_ENTRY and COM_INTERFACE_ENTRY_IID are supported.

The CoMathDemoImpl.h header includes an interface map at the end of the public definition of methods. Both interfaces implemented by the CoClass are defined in the interface map:

// COM Interface map 
BEGIN_COM_MAP(CoMathDemoImpl) 
    COM_INTERFACE_ENTRY(IMathDemo) 
    COM_INTERFACE_ENTRY(IEvalDemo) 
END_COM_MAP()

The header files generated for your CoClass will have similar interface map definitions for your CoClass interfaces.1



9.5    Configuring DCOM Properties' Parameters

If you add DCOM support, you can also configure the DCOM properties' parameters. The meaning and purpose of these parameters are described below, along with the range of possible values and the default setting for each.

For a description of how to change these parameters, see the Tornado User's Guide: Building COM and DCOM Client and Server Applications.

Specifies the level of authentication required by the application.

Values
0 = default, no authentication required

1 = no authentication required

2 = RPC_CN_AUTHN_LEVEL_CONNECT, which means that the application must create a userId and password combination (using the vxdcomUserAdd( ) API) so that incoming connections can be authenticated.

Default
0

Sets configuration for marshaling of BSTRs.

Values
TRUE = BSTRs are marshaled as counted strings (byte-arrays), in which the first byte specifies the length of the string and the remaining bytes are represented as ASCII characters.

FALSE = BSTRs are marshaled as BSTRs, that is as wide-character strings, which are two-byte unicode character strings.

Default
FALSE

Adds priority schemes and real-time extension priority configuration to basic DCOM functionality.

Values
TRUE = propagates the client priority, enabling the server to run at the same priority as the client.

FALSE = server runs at its own priority, independent of the client priority.

Default
TRUE

For more information, see 9.8.2 Configuring Client Priority Propagation on Windows .

Specifies the number of additional threads that can be allocated at peak times.

Values
Range is 0..32

Default
30

Sets the configuration for validating the ObjectExporter port number after a system reboot. If this parameter is set to zero, the port number is assigned dynamically.

Values
Range is 1025..65535 (short int values)

Default
65000

Specifies the stack size, at build time, of the Service Control Manager (SCM) task.This parameter is mainly used to configure PPC60x target architectures, which can create a larger stack frame than other standard architectures.

On most architectures, the default value can be used.

Default
30K

If a stack exception or data exception is seen in the tSCM task, use the browser to check whether the stack has been exhausted. If it has, then increase this value.

Specifies the stack size of threads in the server threadpool.

Values
Recommended range is 16K...128K

Default
16K

If data exceptions or stack exceptions are seen in tCOM tasks, use the browser to check whether stack exhaustion is the cause. If it is, increase this value.

Specifies the number of threads to preallocate in the server threadpool.

Values
Range is 1..32

Default
5

VxDCOM starts up a number of initialized threads to speed up CoClass startup times. Set this value to the average number of CoClasses running within the system in order to speed up CoClass initialization.

Specifies the default priority of threads in the server threadpool. For more information, see 9.8.3 Using Threadpools.

Values
Same range as that of the VxWorks task priority.

Default
150



9.6    Using the Wind IDL Compiler

The Wind IDL Compiler, widl, is a command-line tool that is integrated into the build process in the Tornado IDE. Thus, when you build a D/COM project, widl is run automatically.

9.6.1   Command-Line Syntax

You can run widl from the command line; however, doing so becomes redundant once the .idl file is part of the project. The command-line syntax for running widl is:

widl [-IincDir] [-noh] [-nops] [-dep] [-o outPutDir] idlFile[.idl] 

The idlFile must be specified, with or without the .idl extension, which is assumed. The other command-line switches are optional and are described below:

-I incDir
Add specified include directory to the header search path.

-noh
Do not generate header file.

-nops
Do not generate proxy/stub file.

-dep
Generate GNU-style dependency file.

-o outputDir
Write output from widl to the specified output directory.

9.6.2   Generated Code

When widl compiles the wizard-generated .idl file, additional code is generated that is added to these three files which were created by the wizard with empty contents: basename_i.c, basename.h, and basename_ps.cpp.

basename.tlb (DCOM only)

basename_ps.cpp (DCOM only)

basename.h

basename_i.c


*      
WARNING: The widl tool only generates proxy/stub code for the interfaces defined in the IDL file being compiled, and not for interfaces defined in imported IDL files. To generate the proxy/stub code for those interfaces, you must separately compile each IDL file that defines each of the required interfaces. The exception to this rule is the IDL file, installDir\target\h\vxidl.idl, whose proxy/stub code routines are supplied in DCOM support components.

9.6.3   Data Types

The widl tool compiles both automation data types and non-automation data types. The specific individual types from these two groups that widl compiles with this version of VxDCOM are listed below.

Automation Data Types

Automation data types are simple types, interface pointers, VARIANTs, and BSTRs as described below.

long, long *, int, int *, short, short *, char, char *, float, float *, double, double *

IUnknown *, IUnknown **

BSTR, BSTR * 


comWideToAscii( ) 
Convert a wide-string to ASCII.

comAsciiToWide( ) 
Convert an ASCII string to a wide-string.


*      
NOTE: VxDCOM also supports the ATL macros, OLE2T and T2OLE. However, these macros call alloca( ), which uses memory from the current stack, meaning the macros could consume an entire stack space if used inside a loop. It is recommended that you use the VxComBSTR class, defined in comObjLibExt.h, as an alternative. For details, see 9.11.7 VxComBSTR.

VARIANT, VARIANT * 

Non-Automation Data Types

The non-automation types that widl can correctly compile are:

simple array 

fixed-size structure 

conformant array 

conformant structure 

string 


*      
CAUTION: widl does not support the type union.

If you use a non-automated type, then you will need to build a proxy DLL. You can use midl to generate the DLL for you, and then you need to build it. For details on registering proxy DLLs, see the Tornado User's Guide: Register Proxy DLLs on Windows.

If you are using non-automation data types because you are using OPC interfaces, then you need to also add the DCOM_OPC support component to your system, and install the OPC Proxy DLL on windows. For more information, see the Tornado User's Guide: OPC Program Support.

SAFEARRAY with VARIANTS

VxDCOM support for SAFEARRAYs is only available when marshaling within a VARIANT, it cannot be marshaled otherwise. VxDCOM implementation of SAFEARRAY supports both the use of COM based SAFEARRAYs and also marshalling SAFEARRAYs from DCOM server to DCOM client. When using SAFEARRAYs in your application, note the following documented sections on specific support and restrictions for both COM and DCOM application.

COM Support

When using SAFEARRAYs under COM, the following is a list of supported features.

  • Support for Multi Dimension SAFEARRAYs of the following types:

VT_UI1
VT_I2
VT_I4
VT_R4
VT_R8
VT_ERROR
VT_CY
VT_DATE
VT_BOOL
VT_UNKNOWN
VT_VARIANT
VT_BSTR

  • Support for SAFEARRAYs of VARIANTs in which the VARIANTs can contain SAFEARRAYs.

  • Support for a minimal SAFEARRAY API.


*      
CAUTION: The memory structure returned by SafeArrayAccessData( ) is not guaranteed to be the same layout as that returned by the Microsoft API.

DCOM Support

  • Support for Single Dimension SAFEARRAYs of the following types:

VT_UI1
VT_I2
VT_I4
VT_R4
VT_R8
VT_ERROR
VT_CY
VT_DATE
VT_BOOL
VT_BSTR

  • VxDCOM only supports SAFEARRAYs less than 16Kb in size.

  • Because Microsoft DCOM fragments packets that are over 4 KB in length, you should only send SAFEARRAYs of 4 KB or less in size when sending a SAFEARRAY from a Microsoft platform.

HRESULT Return Values

All interface methods require an HRESULT return type. HRESULTs are the 32-bit return value from all ORPC methods and are used to handle RPC exceptions. By using ORPC and HRESULT return types the COM technology can provide a virtually transparent process of object communication from the developer's perspective. This is because, as long as client code returns an HRESULT, the client application can access all objects, whether in-process or remote, in a uniform transparent fashion. In fact, in-process servers communicate with client applications by using C++ virtual method calls; whereas remote servers communicate with client applications by using proxy/stub code and by invoking local RPC services. However, this process is all transparent to the programmer, because it happens automatically within the COM mechanism. The only difference is that making a remote procedure call requires more overhead.2

The VxDCOM wizard automatically generates an HRESULT return type for all methods you define. When writing your implementation code, you can refer to the header file installDir\target\h\comErr.h for a comprehensive list of HRESULT error constants. Table 9-2 lists the most commonly used HRESULT values.

Table 9-2:   Common HRESULT Values   


Value
Meaning

S_OK
Success. (0x00000000)
E_OUTOFMEMORY
Insufficient memory to complete the call. (0x80000002)
E_INVALIDARG
One or more arguments are invalid. (0x80000003)
E_NOINTERFACE
No such interface supported. (0x80000004)
E_ACCESSDENIED
A secured operation has failed due to inadequate security privileges. (0x80070005)
E_UNEXPECTED
Unknown, but relatively catastrophic error. (0x8000FFFF)
S_FALSE
False. (0x00000001)

   



9.7    Reading IDL Files

IDL (Interface Definition Language) is a specification used by COM for defining its elements (interfaces, methods, type libraries, CoClasses, and so on). The file containing these definitions is written in IDL and, by convention, is identified by an .idl extension. VxDCOM assumes this extension for the tools to work properly. Therefore, the wizard generated interface definition file has this extension.

9.7.1   IDL File Structure

The .idl file structure includes the interface, CoClass, and type-library definitions, each of which contains a header and a body. The CoClass definition is itself part of the library body. This structure is fairly standard, although you may encounter some .idl files that diverge slightly from it.

Below is an example of the typical syntax for a .idl file with multiple interface definitions, where the library definition contains the CoClass definition in its body; and the CoClass definition contains the interfaces it implements in its body.

//import directives, typedefs, constant declarations, other definitions 
 
[attributes] interface interfacenamebase-interface {definitions}; 
[attributes] interface interfacenamebase-interface {definitions}; 
 
//additional interface definitions as required... 
 
[attributes] library libname {definitions};

The sample .idl file below is from the CoMathDemo demo program found in the installDir/host/src/VxDCOM/MathDemo directory. It demonstrates the structure of an .idl file that defines two interfaces, IMathDemo and IEvalDemo, a type library, CoMathDemoLib, and a CoClass, CoMathDemo, that implements both interfaces.

#ifdef _WIN32 
import "unknwn.idl"; 
#else 
import "vxidl.idl"; 
#endif 
 
[ 
    object, 
    oleautomation, 
    uuid(A972BFBE-B4A9-11D3-80B6-00C04FA12C4A), 
    pointer_default(unique) 
] 
interface IMathDemo:IUnknown 
    { 
    HRESULT pi([out,retval]double* value); 
    HRESULT acos ([in]double x, [out,retval]double* value); 
    HRESULT asin ([in]double x, [out,retval]double* value); 
    HRESULT atan([in]double x, [out,retval]double* value); 
    HRESULT cos([in]double x, [out,retval]double* value); 
    HRESULT cosh([in]double x, [out,retval]double* value); 
    HRESULT exp([in]double x, [out,retval]double* value); 
    HRESULT fabs([in]double x, [out,retval]double* value); 
    HRESULT floor ([in]double x, [out,retval]double* value); 
    HRESULT fmod([in]double x,[in]double y,[out,retval]double* value); 
    HRESULT log ([in]double x, [out,retval]double* value); 
    HRESULT log10 ([in]double x, [out,retval]double* value); 
    HRESULT pow ([in]double x,[in]double y,[out,retval]double* value); 
    HRESULT sin ([in]double x, [out,retval]double* value); 
    HRESULT sincos ([in]double x,  
                    [out]double* sinValue,  
                    [out]double* cosValue); 
    HRESULT sinh ([in]double x, [out,retval]double* value); 
    HRESULT sqrt ([in]double x, [out,retval]double* value); 
    HRESULT tan ([in]double x, [out,retval]double* value); 
    HRESULT tanh ([in]double x, [out,retval]double* value); 
    };
[ 
    object, 
    oleautomation, 
    uuid(4866C2E0-B6E0-11D3-80B7-00C04FA12C4A), 
    pointer_default(unique) 
] 
interface IEvalDemo:IUnknown 
    { 
    HRESULT eval ([in]BSTR str, [out,retval]double* value); 
    HRESULT evalSubst ([in]BSTR str,  
                       [in]double x,  
                       [out,retval] double* value); 
    }; 
 
[ 
    uuid(A972BFC0-B4A9-11D3-80B6-00C04FA12C4A), 
    version(1.0), 
    helpstring("CoMathDemo Type Library") 
] 
 
library CoMathDemoLib 
    { 
    importlib("stdole32.tlb"); 
    importlib("stdole2.tlb"); 
 
    [ 
        uuid(A972BFBF-B4A9-11D3-80B6-00C04FA12C4A), 
        helpstring("CoMathDemo Class") 
    ] 
    coclass CoMathDemo 
        { 
        [default] interface IEvalDemo; 
        interface IMathDemo; 
        }; 
};

The following sections describe the syntax for the parts of an .idl file.

The import Directive

The import directive precedes the interface definition and specifies another .idl or header file. These imported files contain definitions - such as typedefs, constant declarations, and interface definitions - that can be referenced in the importing IDL file. All interfaces inherit from the IUnknown interface; therefore all interface definitions will conditionally include either the WIN32 header, unknwn.idl, or vxidl.idl, which implements IUnknown under VxDCOM when running on a target.

The Interface Definition

The interface definition in the .idl file specifies the actual contract between the client application and server object. It describes the characteristics of each interface in an interface header and an interface body. The syntax for an interface definition is:

[attributes] interface interfacenamebase-interface {definitions};
Interface Header

The interface header is the section at the beginning of the interface definition. The interface header comprises both the information that is enclosed in square brackets, along with the keyword interface, followed by the interface name. The information within the brackets is an attribute list describing characteristics that apply to the interface as a whole. This information is global to the entire interface (in contrast to attributes applied to interface methods.) The attributes describing the interface - [object], [oleautomation], [uuid], and [pointer-default] - are the standard generated attributes for VxDCOM interface definitions and are discussed in 9.7.2 Definition Attributes.

Interface Body

The interface body is the section of the interface definition that is enclosed in C-style braces ({ }). This section contains remote data types and method prototypes for the interface. It can optionally include zero or more import lists, pragmas, constant declarations, general declarations, and function declarators.

Library and CoClass Definitions

The type library and CoClass definitions follow the same syntax pattern as that of an interface definition. The library definition syntax is:

[attributes] library libname {definitions};

The library name is preceded by descriptive attributes and followed by a body of definitions that are enclosed in C-style braces ({ }). The library keyword indicates that the compiler should generate a type library. The type library includes definitions for every element inside of the library block, plus definitions for any elements that are defined outside, and referenced from within, the library block. The CoClass definition lies within the {definitions} or body section of the library block, and has its own header and body sections.

The [version] attribute identifies a particular version among multiple versions of the interface. The [version] attribute ensures that only compatible versions of client and server software will be connected.

The [helpstring] attribute specifies a zero-terminated string of characters containing help text that is used to describe the element to which it applies, in this example, the type library. The [helpstring] attribute can be used with--a type library, an import library, an interface, a module--or with a CoClass statement, typedefs, properties, and methods.

The CoClass statement is used to define the CoClass and the interfaces that it supports. The CoClass definition is similar to the interface definition. It is comprised of a set of attributes, which requires the [uuid] attribute (representing the CLSID), the CoClass keyword, the CoClass name, and a body of definitions. All of the interfaces the CoClass implements are listed in the CoClass body; thus, you must add any additional interfaces that your CoClass implements to that list.3 You will also need to add those interfaces to the CoClass definition in the server implementation header file, as described in 9.10 Writing VxDCOM Servers and Client Applications.

9.7.2   Definition Attributes

Interface attributes are annotations that specify certain qualities of your interface. Interface attributes are grouped together in a comma-delimited list, surrounded by brackets. The attribute list always precedes whatever object the attributes in the list are describing. Attributes can also be applied to the libraries and CoClasses. However, some attributes, or combinations of attributes, are valid for one definition type and not another (see Attribute Restrictions for VxDCOM and Library and CoClass Definitions). For comprehensive information on attributes, see the Microsoft COM specification.

IDL File Attributes

When you run the D/COM Application Wizard, the generated interface definition contains the following attributes as defaults. If you intend to modify any of them, you must follow the language restrictions. (see Attribute Restrictions for VxDCOM.)

[object]

The [object] attribute is an IDL extension that specifies the interface as a COM interface (rather than an RPC interface). This attribute tells the IDL compiler to generate all of the proxy/stub code specifically for a COM interface, and to generate a type library for each library block defined within the .idl file (see Library and CoClass Definitions). All VxDCOM interface definitions should specify this attribute in the definition.

[oleautomation]

The [oleautomation] attribute indicates that the interface is compatible with Automation. VxDCOM interfaces definitions generated by the wizard are declared with this attribute, which specifies that the parameters and return types are automation types.

[pointer_default]

The [pointer_default] attribute specifies the default pointer attribute for all pointers except pointers that appear as top-level parameters, such as individual pointers used as function parameters; these automatically default to ref pointers. The syntax for the [pointer_default] attribute is:

pointer_default (ptr | ref | unique)

where ptr, ref, or unique can be specified:

ptr

ref

unique

The [pointer_default] attribute can apply to pointers returned by functions. For pointers that appear as top-level parameters, such as individual pointers used as function parameters, you must supply the appropriate pointer attribute. For example:

HRESULT InterfaceMethod( [unique] VXTYPEptrVXTYPE );

In this case, the pointer attribute will override the default [pointer_default] attribute that appears in the interface header. The [pointer_default] attribute is optional in the .idl file, and is required only when a function returns an undefined pointer type or when a function contains a parameter with more than one asterisk (*).

[uuid]

The [uuid] interface attribute designates a universally unique identifier (UUID) that is assigned to the interface and that distinguishes it from other interfaces. COM technology relies upon these unique values as a means of identifying components and interfaces, as well as sub-objects within the COM system such as interface pointers and type libraries. As part of the .idl file, the wizard generates a UUID value for each interface, for the type library, and for the CoClass definition. For a COM interface--that is, for an interface identified by the [object] interface attribute--the [uuid] attribute is required to determine whether the client can bind to the server. It is used to differentiate versions of public interfaces, so that different vendors can introduce distinct new features without risking compatibility conflicts.


*      
NOTE: A UUID designates a 128-bit value that is guaranteed to be unique. The actual value may represent a GUID, a CLSID, or an IID.

Attribute Restrictions for VxDCOM

The only interface attributes auto-generated for VxDCOM interface definitions are [object], [oleautomation], [pointer-default], and [uuid]. Restrictions on using interface attributes with VxDCOM are summarized as follows:

  • VxDCOM does not support all interface types; thus, any attributes that are defined for non-supported interface types would not be applicable. For example, because VxDCOM does not support IDispatch, the [dual] attribute cannot be used.

  • Some combinations of attributes are inherently prohibited; for example, the [version] attribute cannot be used for a COM interface. The [version] attribute, identifies a particular version among multiple versions of an RPC interface. The MIDL compiler does not support multiple versions of a COM interface, so the [object] attribute (which specifies the interface as a COM interface) cannot also include a [version] attribute.

For a complete list of interface attributes, their meanings, and valid combinations, see the Microsoft documentation.


*      
NOTE: To create a new version of an existing COM interface, use interface inheritance. A derived COM interface has a different UUID but inherits the interface member functions, status codes, and interface attributes of the base interface.

Directional Attributes for Interface Method Parameters

Directional attributes on interface method parameters indicate the direction that data values are passed between methods. The two primary directional attributes are [in] and [out]. These are the four combinations available for a parameter attribute:

[in]

[out]

[in,out]

[out,retval]



9.8    Adding Real-Time Extensions

For DCOM server applications, VxDCOM offers real-time extensions that can improve the performance of your application. Real-time extensions are priority schemes and threadpooling, and they are described in the following sections.

9.8.1   Using Priority Schemes on VxWorks

Priority schemes are used to control the scheduling of server objects running on a VxWorks target. You can specify the priority scheme to use at class registration time in the parameters to the AUTOREGISTER_COCLASS macro. The second and third arguments to this macro specify priority scheme details. An example of the AUTOREGISTER_COCLASS macro, that is auto-generated by the wizard for the MathDemo is:

AUTOREGISTER_COCLASS (CoMathDemoImpl, PS_DEFAULT, 0);

The generated definition in your server implementation file would look similar with the first argument reflecting your server name.

Second Parameter Priority Scheme

The second argument to AUTOREGISTER_COCLASS specifies the priority scheme to be used. The three possible values for this argument are:

PS_SVR_ASSIGNED

PS_CLNT_PROPAGATED

PS_DEFAULT

Third Parameter Priority Level

The third argument specifies the actual priority level to assign to either the server, in the case of a PS_SVR_ASSIGNED scheme, or to the client, in the case of a PS_CLNT_PROPAGATED scheme. In addition, it specifies when the VXDCOM_ORPC_EXTENT (which contains the priority) is not present in the request.

If the client is a VxWorks target, the client's priority is conveyed with the RPC invocation to the server, using an ORPC extension mechanism specified in the DCOM protocol definition. This causes the server to execute the method at the same priority as the client task that invoked it.

9.8.2   Configuring Client Priority Propagation on Windows

When configuring VxDCOM projects, the client priority is automatically and transparently transmitted. The client priority propagation can be turned off (see 9.5 Configuring DCOM Properties' Parameters), thereby saving a few bytes per request.

Using Windows, you must create a channel hook IChannelHook interface to propagate the priority, that is, to add a priority (in the form of an ORPC_EVENT) to an outgoing request. It is still the user's responsibility to convert the priority value from the other operating system to an appropriate VxWorks priority. Only the ClientGetSize( ) and ClientFillBuffer( ) functions must be implemented. The other IChannelHook interface methods, ClientNotify( ), ServerNotify( ), ServerGetSize( ), and ServerFillBuffer( ) can be empty functions. The following example code implements the ClientGetSize( ) and ClientFillBuffer( ) methods:

void CChannelHook::ClientGetSize 
    (REFGUID uExtent, REFIID riid, ULONG* pDataSize) 
    { 
    if(uExtent == GUID_VXDCOM_EXTENT) 
        *pDataSize = sizeof(VXDCOMEXTENT); 
    } 
 
void CChannelHook::ClientFillBuffer 
    (REFGUID uExtent, REFIID riid, ULONG* pDataSize, void* pDataBuffer) 
    { 
    if(uExtent == GUID_VXDCOM_EXTENT) 
        { 
        VXDCOMEXTENT *data = (VXDCOMEXTENT*)pDataBuffer; 
        getLocalPriority ( (int *) &(data->priority)); 
        *pDataSize = sizeof(VXDCOMEXTENT); 
        } 
    }

GUID_VXDCOM_EXTENT and VXDCOMEXTENT are defined in installDir\target\h\dcomLib.h. Windows programmers wishing to do priority propagation should #include this file. Once a channel hook is implemented, it must be registered with the Windows COM run-time using the CoRegisterChannelHook( ) function.

9.8.3   Using Threadpools

A threadpool is a group of tasks owned by the VxWorks run-time libraries, dedicated to servicing DCOM requests for object method execution. Since a small number of tasks can handle a large number of requests, threadpools optimize performance and increase the scalability of the system.4

If all of the tasks in the pool are busy, new tasks can be dynamically added to the pool to handle the increased activity. These tasks are reclaimed by the system when the load drops again.

If all of the static tasks in the thread pool are busy and the maximum number of dynamic tasks has been allocated, then a queue can store the unserviced requests. As tasks become free, they will service the requests in this queue. If the queue becomes full, a warning message is returned to the calling task.

Since threadpools are part of the kernel, threadpool parameters are configurable.



9.9    Using OPC Interfaces

VxDCOM supports the OPC interfaces defined in the files listed below and identified by their corresponding categorical group name:

installDir\target\h\opccomn.idl

Common Interfaces

nstallDir\target\h\opcda.idl

Data Access

installDir\target\h\opc_ae.idl

Alarms and Events

Some of the OPC interface services use array and anonymous structure data types. In order to support these types as per the OPC specification, these files are shipped with some slight modifications in the form of conditional tags.

If you use OPC interfaces in your application, you may need to use non-automation data types that require additional editing in the .idl file. For details, see the Tornado User's Guide: Building COM and DCOM Applications.

The VxDCOM version of the OPC files import the installDir\target\h\vxidl.idl file. This file defines interfaces required by an OPC application. For each of the interfaces that are required from the OPC protocol, a proxy/stub interface is required for VxDCOM to be able to remotely access an interface. In order to use this proxy/stub code, you must add the DCOM_OPC support component, as described in the Tornado User's Guide: Building COM and DCOM Applications.



9.10    Writing VxDCOM Servers and Client Applications

This section discusses programming issues and provides example code for writing VxDCOM servers and client applications.

9.10.1   Programming Issues

When writing VxDCOM applications, there are several programming issues of which you need to be aware. These are described below.

Using the VX_FP_TASK Option

The main program for all COM threads should be created using the VX_FP_TASK option.


*      
NOTE: Because COM is single-threaded, any COM objects that are spawned by the main program will run within the main program thread.

Avoiding Virtual Base Classes

Each implementation of an interface must have its own copy of the vtable, including sections for the IUnknown method pointers. Using virtual inheritance alters this layout and interferes with the COM interface mapping.

Therefore, when defining your COM and DCOM classes, do not use virtual inheritance. For example, do not use this type of definition:

class CoServer : public virtual IFoo,  
                 public virtual IBar 
    { 
    // class impl... 
    };
Distinguishing the COM and DCOM APIs

VxDCOM supports both in-process and remote servers. When you write the servers, note that the main distinction between COM and DCOM components is the version of COM API routines used. The DCOM version of a COM routine is often appended with an Ex. In particular, you must use the CoCreateInstanceEx( ) version of the COM CoCreateInstance( ) routine for DCOM applications. For details, see the COM and DCOM libraries as part of the Microsoft COM documentation.

9.10.2   Writing a Server Program

This remainder of this section describes the server code for the CoMathDemo, focusing the methods that provide services to the client. These methods are the interface between client and server. The code used in this section is taken from the following files:

  • the CoMath.idl interface definition file
  • the CoMathDemoImpl.h header file
  • the CoMathDemoImpl.cpp implementation file

For the implementation details, see the CoMathDemo source files, which are well commented.

Server Interfaces

The CoMathDemo server component implements two interfaces, IMathDemo and IEvalDemo, both of which are derived directly from IUknown.

The IMathDemo interface defines 19 common math methods listed below in the interface definition.

interface IMathDemo : IUnknown 
    { 
    HRESULT pi ([out,retval]double* value); 
    HRESULT acos ([in]double x, [out,retval]double* value); 
    HRESULT asin ([in]double x, [out,retval]double* value); 
    HRESULT atan ([in]double x, [out,retval]double* value); 
    HRESULT cos ([in]double x, [out,retval]double* value); 
    HRESULT cosh ([in]double x, [out,retval]double* value); 
    HRESULT exp ([in]double x, [out,retval]double* value); 
    HRESULT fabs ([in]double x, [out,retval]double* value); 
    HRESULT floor ([in]double x, [out,retval]double* value); 
    HRESULT fmod ([in]double x, [in]double y, [out,retval]double* value); 
    HRESULT log ([in]double x, [out,retval]double* value); 
    HRESULT log10 ([in]double x, [out,retval]double* value); 
    HRESULT pow ([in]double x, [in]double y, [out,retval]double* value); 
    HRESULT sin ([in]double x, [out,retval]double* value); 
    HRESULT sincos ([in]double x, [out]double* sinValue,  
                    [out]double* cosValue); 
    HRESULT sinh ([in]double x, [out,retval]double* value); 
    HRESULT sqrt ([in]double x, [out,retval]double* value); 
    HRESULT tan ([in]double x, [out,retval]double* value); 
    HRESULT tanh ([in]double x, [out,retval]double* value); 
    };

The IEvalDemo defines two methods, eval( ) and evalSubst( ).

interface IEvalDemo : IUnknown 
    { 
    HRESULT eval ([in]BSTR str, [out,retval]double* value); 
    HRESULT evalSubst ([in]BSTR str,  
                       [in]double x,  
                       [out,retval]double* value); 
    };

The eval( ) method takes a BSTR, which contains a algebraic expression, str, and returns the value as a double, value. Note that the variable that the eval( ) method receives for evaluation is defined by the [in] parameter attribute and the value that is returned is defined by both the [out] and [retval] parameter attributes. The [retval] attribute allows this method to be used by languages that require a return value. For more information on parameter attributes, see the Tornado User's Reference: COM Tools.

This evalSubst( ) method similarly takes a BSTR containing an algebraic expression and returns the value as a double. In addition, evalSubst( ) will also substitute the variable x with a supplied value.

The implementation code indicates that both eval( ) and evalSubst( ) return an HRESULT of S_OK when successful or E_FAIL when an error occurs in decoding the expression.

Client Interaction

The CoMathDemoClient is created with arguments that include an expression to evaluate. The client first queries the server for the IEvalDemo interface and then for the IMathDemo interface by invoking the QueryInterface( ) method of IUknown. The client code then calls the eval( ) method of IEvalDemo, passing it the expression to evaluate, str, and a reference for the return value, &result. Then the client code is written specifically to call the pi( ), sin( ), and cos( ) methods of the IMathDemo interface. (see 9.10.4 Querying the Server).

9.10.3   Writing Client Code

Because DCOM is transparent across architectures, it minimizes the need for special code to be written in the client application that differentiates between in-process and remote procedure calls to an object. You can write code that uses the services of a COM interface, without concern for the network location of the object that implements that interface. The MathDemo program, described below, uses the same user client code. Only the ifdef statements generated by the VxDCOM wizard determine the whether the client is a COM or DCOM client and, for DCOM clients, whether it is for use on VxWorks or Windows NT.

This program demonstrates how to write a simple COM or DCOM client. The MathDemo program creates a COM object and uses that object to perform simple arithmetic calculations.

Determining the Client Type

The first section of the client code contain #define and #include directives that are mostly auto-generated by the VxDCOM wizard. The #include directives for the alt*.* files for _WIN32 and the comOjbLib.h file were added manually because this program uses CComBSTR. Similarly, you would add any necessary header files for code that your program requires.

/* includes */ 
 
#ifdef _WIN32 
 
#define _WIN32_WINNT 0x0400 
#define WIN32_LEAN_AND_MEAN 
#include <windows.h> 
#include "CoMathDemo.w32.h" 
#include <atlbase.h> 
#include <atlimpl.cpp> 
 
#else 
 
#include "comLib.h" 
#include "CoMathDemo.h" 
#include "comObjLib.h" 
#define mbstowcs comAsciiToWide 
 
#endif 
 
#include <stdio.h> 
#include <iostream.h> 
#include <math.h> 
 
#ifdef _DCOM_CLIENT 
#ifndef _WIN32 
#include "dcomLib.h" 
#endif 
#endif

Creating and Initializing the Client

This section of the code creates the COM or DCOM object (component) by calling the appropriate COM or DCOM routines, CoCreateInstance( ) or CoCreateInstanceEx( ). The DCOM client requires additional initialization code for security purposes. This creation and initialization code is auto-generated by the wizard.

#define MAX_X 79 
#define MAX_Y 25 
 
int CoMathDemoClient (const char* serverName, const char* expression) 
    { 
    HRESULT     hr = S_OK; 
    double      result; 
    int         x; 
    int         y; 
    IUnknown *  pItf; 
    IEvalDemo * pEvalDemo; 
    IMathDemo *  pMathDemo;

If the client is a COM client, this code is used.

#ifndef _DCOM_CLIENT 
    // This section creates the COM object. 
    hr = CoCreateInstance (CLSID_CoMathDemo, 
                           0, 
                           CLSCTX_INPROC_SERVER, 
                           IID_IEvalDemo, 
                           (void **)&pItf); 
#else

If the client is DCOM client, this code is used. This section of code initializes DCOM for this thread and creates the DCOM object on the target.

    OLECHAR wszServerName [128]; 
 
    // Convert the server name to a wide string. 
    mbstowcs (wszServerName, serverName, strlen (serverName) + 1); 
 
    // Initialize DCOM for this thread. 
    hr = CoInitializeEx (0, COINIT_MULTITHREADED); 
    if (FAILED (hr)) 
        { 
        cout << "Failed to initialize DCOM\n"; 
        return hr; 
        } 
 
    // This initizlizes security to none. 
    hr = CoInitializeSecurity (0, -1, 0, 0, 
                            RPC_C_AUTHN_LEVEL_NONE, 
                            RPC_C_IMP_LEVEL_IDENTIFY, 
                            0, EOAC_NONE, 0); 
    if (FAILED (hr))  
        { 
        cout << "Failed to initialize security\n"; 
        return hr; 
        }

The code following creates an MQI structure, which is used to query the COM server object for the IID_IMathDemo interface. This is one of the two interfaces defined by the CoMathDemo CoClass that is instantiated as the DCOM server.

When writing a typical DCOM client program with multiple interfaces you include all your interface requests into the MQI and query them as one operation (thus saving bandwidth). However, for the purposes of this demo we want to keep the main body of the code the same so we only want the IUnknown for the DCOM object at this point so we can treat it the same as a COM object lower down.

    MULTI_QI mqi [] = { {&IID_IEvalDemo, 0, S_OK} }; 
    COSERVERINFO serverInfo = { 0, wszServerName, 0, 0 }; 
 
    hr = CoCreateInstanceEx (CLSID_CoMathDemo, 
                            0, 
                            CLSCTX_REMOTE_SERVER, 
                            &serverInfo, 
                            1, 
                               mqi); 
 
    if (SUCCEEDED (hr) && SUCCEEDED (mqi [0].hr)) 
        { 
        cout << "Created CoMathDemo OK\n"; 
        pItf = mqi [0].pItf; 
        } 
 
    else 
 
        { 
        cout << "Failed to create CoMathDemo, HRESULT=" <<  
            hex << cout.width (8) << mqi [0].hr << "\n"; 
        return E_FAIL; 
        } 
 
#endif

9.10.4   Querying the Server

Query the IUnknown interface of the COM object to get an interface pointer to the IEvalDemo interface.

    if (FAILED (hr = pItf->QueryInterface (IID_IEvalDemo, 
                (void**)&pEvalDemo))) 
        { 
        cout << "Failed to create IEvalDemo interface pointer, 
                HRESULT=" << hex << cout.width (8) << hr << "\n"; 
        pItf->Release (); 
        return hr; 
        }

Query the IUnknown interface of the COM object to get an interface pointer to the IMathDemo interface.

    if (FAILED (pItf->QueryInterface (IID_IMathDemo, void**)&pMathDemo))) 
        { 
        cout << "Failed to create IMathDemo interface pointer, HRESULT=" 
             << hex << cout.width (8) << hr << "\n"; 
        pEvalDemo->Release(); 
        pItf->Release (); 
        return hr; 
        } 
 
    pItf->Release ();

This code calls the eval( ) method of the IEvalDemo interface, querying for it to evaluate the given expression, which is passed to the client program from the command line.5

    cout << "Querying IEvalDemo interface\n"; 
    CComBSTR str; 
 
    str = expression; 
 
    hr = pEvalDemo->eval(str, &result); 
    if (SUCCEEDED (hr)) 
        { 
        cout << expression << "=" << result; 
        } 
        else 
        { 
        cout << "eval failed (" << hr << "," << result << ")\n"; 
        } 
    pEvalDemo->Release ();

This code queries the IMathDemo interface to draw the sine and cosine graphs. Note that it calls the pi( ), sin( ), and cos( ) methods of that interface.

    printf("Querying IMathDemo interface\n"); 
 
    double sinResult; 
    double cosResult; 
    double pi; 
 
    hr = pMathDemo->pi(&pi); 
    if (FAILED (hr)) 
        return hr; 
 
    double step_x =  (pi * 2.0) / ((double)MAX_X); 
    double scale_y = ((double)MAX_Y) / 2.0; 
 
    for (y = MAX_Y; y >= 0; y--) 
        { 
        for (x = 0; x < MAX_X; x++) 
            { 
            hr = pMathDemo->sin((double)x * step_x , &sinResult); 
            if (FAILED (hr)) 
                return hr; 
            hr = pMathDemo->cos((double)x * step_x , &cosResult); 
            if (FAILED (hr)) 
                return hr; 
            if ((int)((double)((sinResult + 1.0) * scale_y)) == y) 
                { 
                putchar('*'); 
                } 
            else if ((int)((double)((cosResult + 1.0) * scale_y)) == y) 
                { 
                putchar('+'); 
                } 
            else 
                { 
                putchar(' '); 
                } 
            } 
        putchar('\n'); 
        } 
    pMathDemo->Release ();
#ifdef _DCOM_CLIENT 
    CoUninitialize (); 
#endif 
    return hr; 
    }

9.10.5   Executing the Client Code

This section of code is for a DCOM C++ client running on a Windows NT operating system. This code was generated by the wizard except for the addition of one argument, exp, which bumps the number of arguments to check for up to 3.

#ifdef _WIN32 
int main (int argc, char* argv []) 
    { 
    if (argc != 3) 
    { 
    puts ("usage: CoMathDemoClient <server> <exp>"); 
    exit (1); 
    } 
 
    return CoMathDemoClient (argv [1], argv[2]); 
    }

This section of the code was added by the programmer for situations in which you do not want C++ name mangling.

#else 
extern "C" 
    { 
    int ComTest (char * server, char * exp) 
        { 
        return CoMathDemoClient (server, exp); 
        } 
    } 
#endif


9.11    Comparing VxDCOM and ATL Implementations.

This section is a reference summary of the noteworthy differences between the VxDCOM and Microsoft ATL implementations of COM interfaces. It also includes the VxDCOM implementation synopsis for VARIANTs.

9.11.1   CComObjectRoot

VxDCOM implements CComObjectRoot directly, whereas ATL implements it as a typedef of CComObjectRootEx.

CComObjectRoot always implements the aggregation pointer even if it is not used.

Constructor

Constructor for the class. Initializes the ref count to 0 and provides a storage for the aggregating outer interface, if required.

CComObjectRoot  
    ( 
    IUnknown *  punk = 0        // aggregating outer 
    )
InternalAddRef

Increments the ref count by one and returns the resultant ref count.

ULONG InternalAddRef ()
InternalRelease

Decrements the ref count by one and returns the resultant ref count.

ULONG InternalRelease ()

9.11.2   CComClassFactory

Derived from IClassFactory and CComObjectRoot.

Constructor

Constructor for class.

CComClassFactory ()
AddRef

Increments the ref count by one and returns the resultant ref count. This is handled by a call to InternalAddRef( ) in CComObjectRoot.

ULONG STDMETHODCALLTYPE AddRef ()
Release

Decrements the ref count by one and returns the resultant ref count. This is handled by a call to InternalRelease( ) in CComObjectRoot.

ULONG STDMETHODCALLTYPE Release ()
QueryInterface

Provides the QueryInterface mechanism to provide the IID_IUnknown and IID_IClassFactory interface to IClassFactory.

HRESULT STDMETHODCALLTYPE QueryInterface  
    ( 
    REFIID riid, // The GUID of the interface being requested 
    void ** ppv // A pointer to the interface riid 
    )
CreateInstance

Creates a new instance of the requested object; calls CComObjectRoot::CreateInstance. Therefore, unlike ATL, the object created for WOTL is initialized.

HRESULT STDMETHODCALLTYPE CreateInstance 
    ( 
    IUnknown * pUnkOuter, // aggregated outer 
    REFIID riid, // The GUID of the interface being created 
    void ** ppv // A pointer to the interface riid 
    )
LockServer

This is a stub function, which always returns S_OK and is provided for compatibility only.

HRESULT STDMETHODCALLTYPE LockServer  
    ( 
    BOOL Lock 
    )

9.11.3   CComCoClass

VxDCOM does not implement the macros DECLARE_CLASSFACTORY or DECLARE_AGGREGATABLE. The functionality is implicitly built into the class.

GetObjectCLSID

Returns the CLSID of the object.

static const CLSID& GetObjectCLSID( )

9.11.4   CComObject

Derived from the given interface class.

CreateInstance

There are two versions of CreateInstance. For details about when each is used and how it is invoked, see the Microsoft COM documentation.

CreateInstance as defined below creates a single instance with no aggregation and no specific COM interface.

static HRESULT CreateInstance  
    ( 
    CComObject** pp // object that is created 
    )

The version of CreateInstance below creates an instance of the class and searches for the requested interface on it.

static HRESULT CreateInstance 
    ( 
    IUnknown* punkOuter, // aggretable interface 
    REFIID riid, // GUID of the interface 
    void** ppv // resultant object 
    )
AddRef

Does an AddRef on either the punkOuter or the CComObjectRoot depending on the type of the object.

ULONG STDMETHODCALLTYPE AddRef ()
Release

Does a Release on either the punkOuter or the CComObjectRoot::AddRef depending on the type of object.

ULONG STDMETHODCALLTYPE Release ()
QueryInterface

Queries either the punkOuter or the object for an interface.

HRESULT STDMETHODCALLTYPE QueryInterface 
    ( 
    REFIID riid, // GUID to query for 
    void ** ppv // resultant interface 
    )

9.11.5   CComPtr

Template class that takes a COM interface specifying the type of pointer to be stored.

Constructors

Supported constructors.

CComPtr () 
CComPtr (Itf* p) 
CComPtr (const CComPtr& sp)
Release

Release an instance of the object.

void Release ()
Operators

Supported operators.

operator Itf* () const 
Itf** operator& () 
Itf* operator-> () 
const Itf* operator-> () const 
Itf* operator= (Itf* p) 
Itf* operator= (const CComPtr& sp) 
bool operator! () const
Attach

Attach an object to the pointer without incrementing its ref count.

void Attach  
    ( 
    Itf * p2 // object to attach to pointer 
    )
Detach

Detach an object from the pointer without decrementing its ref count.

Itf *Detach()
CopyTo

Copy an object to the pointer and increment its ref count.

HRESULT CopyTo  
    ( 
    Itf ** ppT // object to copy to pointer 
    )

9.11.6   CComBSTR

Class wrapper for the BSTR type. CComBSTR provides methods for the safe creation, assignment, conversion and destruction of a BSTR.

Constructors

Supported constructors.

CComBSTR () 
explicit CComBSTR (int nSize, LPCOLESTR sz = 0) 
explicit CComBSTR (LPCOLESTR psz) 
explicit CComBSTR (const CComBSTR& src)
Operators

Supported operators.

CComBSTR& operator= (const CComBSTR& cbs) 
CComBSTR& operator= (LPCOLESTR pSrc) 
operator BSTR () const 
BSTR * operator& () 
bool operator! () const 
CComBSTR& operator+= (const CComBSTR& cbs)
Length

Get the length of the BSTR.

unsigned int Length () const
Copy

Make a copy of the BSTR within the wrapper class and return it.

BSTR Copy() const
Append

Append to the BSTR.

void Append (const CComBSTR& cbs) 
void Append  (LPCOLESTR lpsz) 
void AppendBSTR (BSTR bs) 
void Append (LPCOLESTR lpsz, int nLen)
Empty

Delete any existing BSTR from the wrapper class.

void Empty ()
Attach

Attach a BSTR to the wrapper class.

void Attach (BSTR src)
Detach

Detach the BSTR from the wrapper class and return it.

BSTR Detach ()

9.11.7   VxComBSTR

The comObjLibExt file provides VxWorks specific extensions to the existing ATL-like classes defined in comObjLib.h. VxComBSTR is derived from CComBSTR and extends it.

Constructors

Constructors for this class are derived from the CComBSTR constructors.

VxComBSTR () 
explicit VxComBSTR (int nSize, LPCOLESTR sz = 0) 
explicit VxComBSTR (const char * pstr) 
explicit VxComBSTR (LPCOLESTR psz) 
explicit VxComBSTR (const CComBSTR& src) 
explicit VxComBSTR (DWORD src) 
explicit VxComBSTR (DOUBLE src)
Operators

Supported operators are described below, followed by the declaration.

Convert the BSTR into an array of char and return a pointer to a temporary copy. This copy is guaranteed to be valid until another call is made on the VxComBSTR object. It can be used in place of the OLE2T macro:

operator char * ()

Return the decimal numeric value stored in the BSTR as a DWORD value. This method follows the same string rules as the standard library function atoi:

operator DWORD ()

Convert a DWORD value into its decimal string representation and store it as a BSTR:

VxComBSTR& operator = (const DWORD& src)

Convert a DOUBLE value into it's decimal string representation and stores it as a BSTR:

VxComBSTR& operator = (const DOUBLE& src)

Convert an array of char into a BSTR format. It can be used instead of T2OLE:

VxComBSTR& operator = (const char * src)

Return TRUE if the given VxComBSTR value is equal to the stored BSTR, FALSE otherwise:

bool const operator == (const VxComBSTR& src)

Return TRUE if the given VxComBSTR value is not equal to the stored BSTR, FALSE otherwise:

bool const operator != (const VxComBSTR& src) 
SetHex

Convert a DWORD value into its hexadecimal string representation and stored it as a BSTR.

void SetHex (const DWORD src)

9.11.8   CComVariant

Derived from tagVARIANT.

Constructors

Supported Constructors.

CComVariant() 
CComVariant(const VARIANT& varSrc) 
CComVariant(const CComVariant& varSrc) 
CComVariant(BSTR bstrSrc) 
CComVariant(LPCOLESTR lpszSrc) 
CComVariant(bool bSrc) 
CComVariant(int nSrc) 
CComVariant(BYTE nSrc) 
CComVariant(short nSrc) 
CComVariant(long nSrc, VARTYPE vtSrc = VT_I4) 
CComVariant(float fltSrc) 
CComVariant(double dblSrc) 
CComVariant(CY cySrc) 
CComVariant(IUnknown* pSrc)
Operators

Supported operators.

CComVariant& operator=(const CComVariant& varSrc) 
CComVariant& operator=(const VARIANT& varSrc) 
CComVariant& operator=(BSTR bstrSrc) 
CComVariant& operator=(LPCOLESTR lpszSrc) 
CComVariant& operator=(bool bSrc) 
CComVariant& operator=(int nSrc) 
CComVariant& operator=(BYTE nSrc) 
CComVariant& operator=(short nSrc) 
CComVariant& operator=(long nSrc) 
CComVariant& operator=(float fltSrc) 
CComVariant& operator=(double dblSrc) 
CComVariant& operator=(CY cySrc) 
CComVariant& operator=(IUnknown* pSrc) 
bool operator==(const VARIANT& varSrc) 
bool operator!=(const VARIANT& varSrc)
Clear

Clear the VARIANT within the wrapper class.

HRESULT Clear()
Copy

Copy the given VARIANT into the wrapper class.

HRESULT Copy(const VARIANT* pSrc)
Attach

Attach the given VARIANT to the class without copying it.

HRESULT Attach(VARIANT* pSrc)
Detach

Detach the VARIANT from the class and return it.

HRESULT Detach(VARIANT* pDest)
ChangeType

Change the type of the VARIANT to vtNew. The types supported by this wrapper function are the same as those of the comLib function VariantChangeType.

HRESULT ChangeType 
    ( 
    VARTYPE vtNew, 
    const VARIANT* pSrc = NULL 
    )


1:  The default IUnknown is always the first COM_INTERFACE_ENTRY in the table. Therefore, by definition, this entry must be derived from IUnknown or the static_cast( ) will fail.

2:  As there is no need to support cross-process (and local RPC) server models, VxDCOM supports only the in-process and remote server models.

3:  This list specifies the full set of interfaces that the CoClass implements, both incoming and outgoing.

4:  This is similar to a call center where a fixed number of operators service incoming calls. In fact, there are Erlang calculators that can optimize the ideal number of operators given the average call frequency and length.

5:  The expression is passed in as an array of char, but it is converted to a BSTR for marshaling across to the COM server.