Lesson 2: Understanding ATL COM Component Code

In this lesson, you will review aspects of the source code generated by the ATL wizards for your COM object. You will review specific code segments including the class declaration, the class body, the global entry-point functions, the registry script resource, and the interface definition language (IDL) file. After you get familiar with the code involved with the ATL approach, you learn about some alternative approaches to developing a COM object.

After this lesson, you will be able to:

  • Describe the functionality provided for a component by the CComObjectRootEx and the CComCoClass templated base classes.
  • Describe the global entry-point functions provided for a COM server.
  • Describe features of the registry script file created for an ATL project.
  • Describe features of the IDL file created for an ATL project.
  • Describe some of the alternative approaches to COM component development.
Estimated lesson time: 30 minutes

Component Class Definition

The definition for your component class appears in the Encoder.h file. This definition is a standard C++ class specification that uses multiple inheritance.

class ATL_NO_VTABLE CEncoder :      public CComObjectRootEx<CComSingleThreadModel>,      public CComCoClass<CEncoder, &CLSID_Encoder>,      public Iencoder { public:      CEncoder()      {           m_Key = 1;      } DECLARE_REGISTRY_RESOURCEID(IDR_ENCODER) DECLARE_NOT_AGGREGATABLE(CEncoder) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CEncoder)      COM_INTERFACE_ENTRY(IEncoder) END_COM_MAP() // IEncoder public:      STDMETHOD(get_Key)(/*[out, retval]*/ short *pVal);      STDMETHOD(put_Key)(/*[in]*/ short newVal);      STDMETHOD(EncodeString)(/*[in]*/ const BSTR instring,       /*[out, retval]*/ BSTR * outstring); protected:      short m_Key; };

Your class inherits from three base classes: CComObjectRootEx, CComCoClass, and IEncoder. The first two of these classes are ATL templated base classes. The remaining class is your interface, which has been declared in the EncodeServer.h file as an abstract base class. The interface methods you define are added to the IEncoder class as pure virtual functions. The derived-class versions of these functions that you provide implement the specific services of your COM server. In a public section at the end of the class definition, you can see the declaration of the EncodeString() implementation.

The CComObjectRootEx class provides a default implementation for the IUnknown interface methods QueryInterface(), AddRef(), and Release(). When you derive from this class, your client can use QueryInterface() to acquire an interface pointer to any interface that your COM object supports. The methods AddRef() and Release() perform reference counting to hold your COM object in memory while clients are using interface pointers.

Deriving from the CComCoClass base class provides you with a default implementation of a class factory. The class factory creates an instance of your COM server. You provide the name of the server class (CEncoder) and a reference to its GUID (CLSID_Encoder) as arguments to this templated base class. The ATL wizards use the UUIDGEN.EXE utility to generate all the GUIDs in this project.

Another important element of your class declaration is the COM map. This map contains a list of the interfaces that your COM object supports. You add an entry to the COM map by using the macro COM_INTERFACE_ENTRY. Behind the scenes, the framework maintains a corresponding array of ATL_INTMAP_ENTRY structures that associate interface GUIDs with functions that retrieve interface pointers. The COM map is used by the default QueryInterface() method that you inherited from the templated base class CComObjectRootEx. Whenever a client application executes QueryInterface(), the default implementation searches this map for a matching interface GUID. If a match is found, the associated function returns the corresponding interface pointer.

Component Method Implementation

The Encoder.cpp file contains the body of the EncodeString() method that you implemented in the previous lesson.

#include "stdafx.h" #include "EncodeServer.h" #include "Encoder.h" STDMETHODIMP CEncoder::EncodeString(const BSTR instring, BSTR *outstring) {      BSTR tempstring = ::SysAllocString(instring);      wcscpy(tempstring, instring);      for(UINT i = 0; i < ::SysStringLen(tempstring); i++)           tempstring[i] += m_Key;      *outstring = ::SysAllocString(tempstring);      ::SysFreeString(tempstring);      return S_OK; }

This code contains a couple of COM-specific features that were automatically added to the method on your behalf by the Add Method to Interface Wizard.

This method uses the macro STDMETHODIMP as the return data type. You can find this macro defined in the header file BASETYPS.H as:

HRESULT export stdcall

Because you have specified that the return value is to be an HRESULT, the Add Method to Interface Wizard automatically inserts a line of code to return the constant S_OK. This intrinsic constant meets the data structure format of an HRESULT and contains a success return code embedded within that structure. For more information on the HRESULT type, see Lesson 2 of Chapter 13.

Global Entry-Point Functions

On the ClassView tab of the Workspace view, you find a folder named Globals. If you expand this folder, you will see several global functions (with the prefix Dll) and a single global object named _Module. These elements appear in the file EncodeServer.cpp, and are added to your project by the ATL COM AppWizard to provide entry point functions that are exported by the DLL and called by COM and other system utilities. The following code is contained in EncodeServer.cpp:

#include "stdafx.h" #include "resource.h" #include <initguid.h> #include "EncodeServer.h" #include "EncodeServer_i.c" #include "Encoder.h" CComModule _Module; BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Encoder, CEncoder) END_OBJECT_MAP() ///////////////////////////////////////////////////////////////////////////// // DLL Entry Point extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) {      if (dwReason == DLL_PROCESS_ATTACH)      {           _Module.Init(ObjectMap, hInstance, &LIBID_ENCODESERVERLib);           DisableThreadLibraryCalls(hInstance);      }      else if (dwReason == DLL_PROCESS_DETACH)           _Module.Term();      return TRUE;    // ok } ///////////////////////////////////////////////////////////////////////////// // Used to determine whether the DLL can be unloaded by OLE STDAPI DllCanUnloadNow(void) {      return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; } ///////////////////////////////////////////////////////////////////////////// // Returns a class factory to create an object of the requested type STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {      return _Module.GetClassObject(rclsid, riid, ppv); } ///////////////////////////////////////////////////////////////////////////// // DllRegisterServer - Adds entries to the system registry STDAPI DllRegisterServer(void) {      // registers object, typelib and all interfaces in typelib      return _Module.RegisterServer(TRUE); } ///////////////////////////////////////////////////////////////////////////// // DllUnregisterServer - Removes entries from the system registry STDAPI DllUnregisterServer(void) {      return _Module.UnregisterServer(TRUE); }

Near the beginning of the file you can see the declaration of a single CComModule object named _Module. The CComModule class implements a COM server module, allowing a client to access the module's components. CComModule supports both in-process and out-of-process modules. Out-of-process (EXE) servers use an application-specific object that is derived from CComModule.

A CComModule instance uses a table called an object map to maintain a set of class object definitions. Behind the scenes, this object map creates and maintains an array of _ATL_OBJMAP_ENTRY structures, which include information used by the framework to:

  • Instantiate objects through a class factory.
  • Establish communication between a client and the root object in the component.
  • Perform lifetime management of class objects.
  • Enter and remove object descriptions in the system registry.

An OBJECT_ENTRY macro is placed in the object map for each COM object in the DLL. The OBJECT_ENTRY macro takes two parameters. The first parameter is a GUID that uniquely identifies the COM object. The second parameter is the name of the class that implements the COM object. Using this information, the macro expansion creates a corresponding _ATL_OBJMAP_ENTRY for the specified object.

When loading your DLL in response to a client request, COM executes your DllGetClassObject() function. This function calls the CComModule:: GetClassObject() base class function provided by the _Module global object to access the class factory of the specified object. CComModule::GetClassObject() accesses the table maintained by the object map to retrieve a pointer to the CreateInstance() method of the class factory. The GetClassObject() function uses the pointer to create a COM object, executes the COM object's QueryInterface() function, and returns the interface pointer to COM.

EXE servers do not implement the Dll… functions; they call the COM run-time function CoRegisterClassObject() on startup for each of the class factories that the function implements. Pointers to the class factories are cached in an internally maintained table.

The DllRegisterServer() and the DllUnregisterServer() functions provide an in-process COM object with its self-registering capability. These functions can be called by programs such as the command-line utility RegSvr32.exe to add or remove information about your COM object to or from the registry. EXE servers check for the command-line switches—RegServer or UnregServer—to determine whether they should register or unregister the server. Both EXEs and DLLs end up calling the CComModule::RegisterServer() and CComModule:: UnregisterServer() functions. These functions register or unregister all objects declared in the object map using information contained in a registry script resource, which is described in the following section.

Registry Script Resource

When a client application attempts to load a COM object, the COM run-time looks in the registry for specific information. This information appears in the registry under HKEY_CLASSES_ROOT (HKCR). The ATL wizards create a registry script for your COM component that contains the required information.

The registry script is included as a resource in your project. The script file has an .rgs extension, and can be found in your project folder. The following is the Encoder.rgs file:

HKCR {      EncodeServer.Encoder.1 = s 'Encoder Class'      {           CLSID = s '{69F4B917-6641-11D3-934B-0080C7FA0C3E}'      }      EncodeServer.Encoder = s 'Encoder Class'      {           CLSID = s '{69F4B917-6641-11D3-934B-0080C7FA0C3E}'           CurVer = s 'EncodeServer.Encoder.1' } NoRemove CLSID {      ForceRemove {69F4B917-6641-11D3-934B-0080C7FA0C3E}            = s 'Encoder Class'      {           ProgID = s 'EncodeServer.Encoder.1'           VersionIndependentProgID = s 'EncodeServer.Encoder'           InprocServer32 = s '%MODULE%'           {           }           'TypeLib' = s '{69F4B91A-6641-11D3-934B-0080C7FA0C3E}' } } }

The registry script just shown places data into the registry subtree HKEY_CLASSES_ROOT. Entries are created for the CLSID, the ProgID (both versioned and version-independent forms), and the type library. As the Encoder object is hosted in a DLL server, an InprocServer32 entry is created. This entry contains a variable called %MODULE%. When the registry script is processed by Visual Studio, the actual name of the project executable is inserted in place of this variable.

IDL File

An IDL file is compiled by the Microsoft IDL compiler (MIDL). MIDL creates the following files in your project folder:

  • Proxy/stub source code to implement standard marshaling.
  • A type library file.
  • A C file that defines the component and interface GUIDs.
  • A C/C++ header file that declares the interface methods exposed by the component.

The proxy/stub code generated for the Encoder object is contained in the files DllData.c and EncodeServer_p.c. You compile this code to create a proxy/stub DLL which is used by COM to marshal data across process boundaries. The type library file EncodeServer.tlb is linked to the DLL when you build the component.

The GUID definition file (EncodeServer_i.c) and the interface declaration header file (EncodeServer.h) are both used by the EncodeServer project. However, it is important to know about these files because they can be included (using #include) in C/C++ client code to create and use instances of the COM component. The CLSID CLSID_Encoder and the IID IID_IEncoder declared in EncodeServer_i.c can be used as parameters in a call to CoCreateInstance(). The EncodeServer.h file declares a class that represents the IEncoder interface, which provides the compiler with the signatures of the interface methods. You use an instance of this class to call the IEncoder methods when the Encoder object is instantiated.

You will learn how these files are used by client programs in Lesson 1 of Chapter 10.

The EncodeServer.idl file is as follows:

import "oaidl.idl"; import "ocidl.idl";      [           object,           uuid(69F4B926-6641-11D3-934B-0080C7FA0C3E),           helpstring("IEncoder Interface"),           pointer_default(unique)      ]      interface IEncoder : Iunknown      {           [helpstring("method EncodeString")] HRESULT            EncodeString([in] const BSTR instring,                 [out, retval] BSTR * outstring);           [propget, helpstring("property Key")]                 HRESULT Key([out, retval] short *pVal);           [propput, helpstring("property Key")]                 HRESULT Key([in] short newVal);      }; [      uuid(69F4B91A-6641-11D3-934B-0080C7FA0C3E),      version(1.0),      helpstring("EncodeServer 1.0 Type Library") ] library ENCODESERVERLib {      importlib("stdole32.tlb");      importlib("stdole2.tlb");      [           uuid(69F4B917-6641-11D3-934B-0080C7FA0C3E),           helpstring("Encoder Class")      ]      coclass Encoder      {           [default] interface IEncoder;      }; };

This IDL file defines the Encoder COM object (coclass), the IEncoder interface, and the ENCODESERVERLib type library. You can see from this example that IDL syntax is very similar to C++, with the most noticeable difference being the inclusion of attributes in square brackets in front of each object declaration.

MIDL attributes specify the characteristics of interfaces, coclasses, and libraries. For example, [uuid] is a required attribute that specifies the unique identifier (GUID) for each of these objects. The GUIDs that appear in this file are generated by the ATL wizards. Notice the [propput] and [propget] attributes, which inform languages such as Visual Basic that a method is to be treated as a property.

Notice that the coclass definition is enclosed within the type library definition. This means that a description of the COM object will be included in the type library. By declaring a coclass outside the library block you can prevent information about the COM object appearing in the type library. You might want to do this if you create COM objects that are used only internally—by other objects in the same DLL, for example.

Alternative Approaches To Development

In addition to ATL, there are several other approaches to developing COM objects. You can implement an object from scratch using C++. You can also use the Microsoft Foundation Classes (MFC).

C++

Implementing a COM object from scratch in C++ requires extensive coding. To achieve this you would perform the following process:

  1. Create a DLL for hosting an in-process COM server.
  2. Derive an interface class from the IUnknown class.
  3. Generate GUIDs for the object (CLSID) and the interface (IID).
  4. Declare a component class derived from the interface class.
  5. Implement the IUnknown methods for managing the lifetime of the COM object.
  6. Implement the component-specific methods for performing the real work of the COM object.
  7. Implement a class factory for creating the server inside the memory space of the DLL.
  8. Implement the methods DllGetClassObject() and DllCanUnloadNow() to interface the DLL in-process server to COM.
  9. Export the DllGetClassObject() and DllCanUnloadNow() methods to the operating system.
  10. Generate a registry script containing the object class and class interface GUIDs.
  11. Register the object using the registry script.

Most of this process is concerned with the mechanics of generating the equivalent of the COM boilerplate code that ATL provides automatically. While you can generate this boilerplate by editing existing code, it's a tedious and error-prone task. Using ATL saves you a lot of time, and generates efficient components that perform extremely well.

MFC

It is perfectly possible to use MFC to generate a COM object. However, MFC is really geared towards the creation of applications, and its COM support is framed in the context of OLE. OLE (now largely rebranded as ActiveX) is the COM-based technology that allows applications to export feature sets (aspects of their functionality) to other applications. For example, it is OLE technology that allows you to embed a Microsoft Excel spreadsheet inside a Microsoft Word document.

Because OLE features are generally implemented using dispatch interfaces, MFC COM support is based around dispatch mapping technologies implemented by the CCmdTarget class. If you want to create a component that exposes a custom interface, you have to resort to using raw C++ code.

Another drawback of MFC COM is that you have to distribute the weighty MFC libraries with your component. If you are distributing the component along with an application that already installs the MFC DLLs, this is not such a big issue, but having to check for and install the libraries for the sake of a single small component is tiresome. In certain situations, statically linking the MFC libraries to your component can make them unacceptably large. One of the reasons for the development of ATL was that MFC components and controls were not found to be suitable for deployment and activation in an Internet environment.

However, MFC is a perfectly good tool for the development of Automation-based components in environments where the MFC libraries can be easily deployed. You can use MFC to easily create ActiveX controls that can be reused many times in your MFC applications. This will be the subject of Chapter 11.

Lesson Summary

When you implement a COM server using the ATL wizards, you are provided with a framework based on ATL templates and macros. Your component class inherits from two base classes: CComObjectRootEx and CComCoClass. These classes are templated base classes that provide you with the methods of IUnknown and a class factory. The ATL wizards provide you with a set of global entry point functions that register your component and allow COM to create instances of your component to service client requests. An IDL file is created that is compiled by the MIDL compiler; this generates proxy/stub code, a type library, and C/C++ header files that define your component's GUIDs and declare the interfaces exposed by your component. These header files can be used by clients to create instances of your COM object and call its interface methods.

Although you can use straight C++ to implement your COM objects, the easiest and safest approach is to use ATL and the ATL wizards. MFC COM support is more geared towards the creation of larger-scale ActiveX applications and Automation-based components operating in an MFC environment.



Microsoft Press - Desktop Applications with Microsoft Visual C++ 6. 0. MCSD Training Kit
Desktop Applications with Microsoft Visual C++ 6.0 MCSD Training Kit
ISBN: 0735607958
EAN: 2147483647
Year: 1999
Pages: 95

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net