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:
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.
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.
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.
|
|
|||||||||||||||||||
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.
|
|
|||||||||||||||||||
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.
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.
|
|
|||||||||||||||||||
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.
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.
|
|
|||||||||||||||||||
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.
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.
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.
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..
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 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.
The definition for CoClasses is automatically generated by the wizard. These CoClasses are derived from all of the following:
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.
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.
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.
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.
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.
The definition for your server CoClass is generated by the wizard in the implementation header, basenameImpl.h. This header includes two basic header files:
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.
There are three automatically generated WOTL files that use macros in their definitions. These files are:
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.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
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);
virtual HRESULT STDMETHODCALLTYPE routine_name(parameters) = 0;
|
|
|||||||||||||||||||
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 }
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
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.
Adds priority schemes and real-time extension priority configuration to basic DCOM functionality.
For more information, see 9.8.2 Configuring Client Priority Propagation on Windows .
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.
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.
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.
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.
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.
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:
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.
|
|
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.
|
||||||||||||||||||
|
|
|||||||||||||||||||
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.
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.
|
|
|||||||||||||||||||
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.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
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.
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 interfacename: base-interface {definitions};
[attributes] interface interfacename: base-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 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 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 interfacename: base-interface {definitions};
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.
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.
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.
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.
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.)
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.
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.
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)
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] VXTYPE* ptrVXTYPE );
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 (*).
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.
|
|
|||||||||||||||||||
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:
|
|
|||||||||||||||||||
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:
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.
The second argument to AUTOREGISTER_COCLASS specifies the priority scheme to be used. The three possible values for this argument are:
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.
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.
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.
VxDCOM supports the OPC interfaces defined in the files listed below and identified by their corresponding categorical group name:
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.
|
|
|||||||||||||||||||
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...
};
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.
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 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.
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).
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.
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
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
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; }
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
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 )
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 ()
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 ()
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 )
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 )
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 )
Does an AddRef on either the punkOuter or the CComObjectRoot depending on the type of the object.
ULONG STDMETHODCALLTYPE AddRef ()
Does a Release on either the punkOuter or the CComObjectRoot::AddRef depending on the type of object.
ULONG STDMETHODCALLTYPE Release ()
operator Itf* () const Itf** operator& () Itf* operator-> () const Itf* operator-> () const Itf* operator= (Itf* p) Itf* operator= (const CComPtr& sp) bool operator! () const
Attach an object to the pointer without incrementing its ref count.
void Attach ( Itf * p2 // object to attach to pointer )
CComBSTR () explicit CComBSTR (int nSize, LPCOLESTR sz = 0) explicit CComBSTR (LPCOLESTR psz) explicit CComBSTR (const CComBSTR& src)
CComBSTR& operator= (const CComBSTR& cbs) CComBSTR& operator= (LPCOLESTR pSrc) operator BSTR () const BSTR * operator& () bool operator! () const CComBSTR& operator+= (const CComBSTR& cbs)
void Append (const CComBSTR& cbs) void Append (LPCOLESTR lpsz) void AppendBSTR (BSTR bs) void Append (LPCOLESTR lpsz, int nLen)
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)
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)
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)
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)
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.