Lesson 1: Creating COM Components with ATL

ATL is a set of templated C++ classes with which you can easily create efficient COM components. It has special support for key COM features including the IUnknown and IDispatch interfaces, and dual interfaces. ATL can also be used to create ActiveX controls. ATL code can also be used to create single-threaded, apartment-threaded, or free-threaded objects.

In addition to ATL, Microsoft Visual Studio supplies wizards that simplify the process of using ATL as the basis of your development framework.

This lesson shows you how to create simple COM components using ATL.

After this lesson, you will be able to:

  • Describe how to use the ATL COM AppWizard to create a COM server to host COM objects.
  • Describe how to use the ATL Object Wizard to create a COM object.
  • Describe how to create and implement a property for an ATL COM object.
  • Describe how to create and implement a method for an ATL COM object.
Estimated lesson time: 30 minutes

Using ATL

A significant amount of boilerplate code can be used when creating COM objects. With ATL, Visual Studio provides an easily tailored approach to generating and implementing COM objects. A number of ATL wizards generate boilerplate code, leaving you free to concentrate on the component-specific methods that perform the real work of your COM object.

The code generated by the ATL wizards is based around the core set of templated classes and macros that form ATL. The use of templates (as opposed to the kind of deep inheritance structure used by MFC) enables ATL to produce fast, lightweight code that is suitable for developing components and controls.

A COM object is created using ATL by the following process:

  1. Create an ATL COM project using the ATL COM AppWizard. The type of project you create will determine the type of server (in-process or out-of-process) that will host your COM objects.
  2. Insert a new ATL object using the ATL Object Wizard.
  3. Add methods to your object using the Add Method to Interface Wizard.
  4. Add properties to your object using the Add Property to Interface Wizard.
  5. Provide the implementation of your object's methods.

In this lesson, you will follow these steps to construct a simple ATL-based COM object that provides encryption services. This COM object, called Encoder, supports a single interface named IEncoder. This interface exposes a single method, named EncodeString(), which returns an encoded version of a single string through the use of a simple encryption algorithm that increases or decreases the value of each character by a specified number. This number is specified as a configurable property named Key.

Creating an ATL COM Project

In this exercise, you will use the ATL COM AppWizard to create an ATL COM project for your COM server.

  • To create the EncodeServer project
    1. On the File menu, click New, and then click the Projects tab.
    2. The project category options are listed in the window on the left side of the dialog box. Click ATL COM AppWizard.
    3. In the Project name box, type EncodeServer. Click OK.
    4. In the ATL COM AppWizard dialog box, ensure that Dynamic Link Library (DLL) is selected as shown in Figure 9.1, and then click Finish.
    5. click to view at full size.

      Figure 9.1 The ATL COM AppWizard dialog box configured to create a DLL

      If you choose to create a DLL to host your COM components, you are indicating that you want to create an in-process COM server. If you select the Executable (EXE) option, you will create a server that can be an out-of- process server or a remote server, depending on how it is implemented. These terms are defined in Lesson 2 of Chapter 8. The Service option allows you to create a Windows NT service—a program that runs in the background when Windows NT starts up.

    6. The New Project Information dialog box describes the files generated for your COM project, as shown in Figure 9.2. Click OK to continue.
    7. click to view at full size.

      Figure 9.2 The New Project Information dialog box

    You have now created the DLL host environment for an in-process COM server, as well as all the code necessary to register your COM object in the system registry. This type of COM object is known as a self-registering component.

    Inserting a New COM Component

    Now that you have the hosting environment defined and configured, you can add an actual COM object. You use the ATL Object Wizard to perform this task.

  • To add the Encoder COM component
    1. On the Insert menu, click New ATL Object.
    2. In the Category list, click Objects.
    3. In the Objects box, click the Simple Object icon shown in Figure 9.3, and then click Next.
    4. click to view at full size.

      Figure 9.3 The ATL Object Wizard dialog box

    5. In the ATL Object Wizard Properties dialog box, click the Names tab.
    6. In the Short Name box, type the name of your server class—Encoder. All remaining fields will update automatically based on the contents of the Short Name box, as shown in Figure 9.4.
    7. click to view at full size.

      Figure 9.4 The ATL Object Wizard Properties dialog box

      The left pane of this dialog box indicates that the wizard will create a C++ class named CEncoder (defined in the Encoder.h and the Encoder.cpp files). You will add code to this class to provide the implementation of your COM component. The pane on the right indicates that the name of the component will be Encoder, and that it will expose the default interface IEncoder. Note also the ProgID EncodeServer.Encoder. This is the name you can use to retrieve the object's GUID using the CLSIDFromProgID() function.

    8. In the ATL Object Wizard Properties dialog box, click the Attributes tab. Set the following attributes as shown in Figure 9.5:
      • Under Threading Model, select Single.
      • Under Interface, select Custom.
      • Under Aggregation, select No.
      • click to view at full size.

        Figure 9.5 The Attributes tab of the ATL Object Wizard Properties dialog box

      ATL follows the recommended practice of implementing dispatch interfaces as dual interfaces. By selecting a custom interface, you are choosing not to implement a dispatch interface for your COM component. Only clients that are capable of attaching directly to the component's vtable will be able to call the interface methods.

    9. Ensure that all check boxes are cleared, and then click OK to add the Encoder object.
    10. In ClassView you will see that you have inserted the CEncoder component class and the IEncoder interface, as shown in Figure 9.6.

      Figure 9.6 ClassView displaying the CEncoder class and IEncoder interface

    With the CEncoder component class, you acquire a templated version of a class factory, which will be used to create your COM component when it is instantiated. You also inherit default implementations for the IUnknown methods QueryInterface(), AddRef(), and Release(), which manage client access to interface pointers and control the lifetime of your COM object.

    Adding Methods to the Component Interface

    After you have the host environment, a class factory, and the IUnknown methods added to your COM component, you insert component-specific methods. A client can execute these methods to use the services of your COM object. When adding a method to your component, you use the Add Method to Interface Wizard.

  • To add the EncodeString() method
    1. In ClassView, right-click the IEncoder interface.
    2. On the shortcut menu, click Add Method. The Add Method to Interface dialog box appears.
    3. In the Return Type box, select HRESULT.
    4. In the Method Name box, type EncodeString.
    5. Insert the following code into the Parameters box.
    6. [in] const BSTR instring, [out, retval] BSTR * outstring

      In the Implementation box, the Add Method to Interface Wizard shows a complete MIDL listing of the method signature based upon the data you entered, as shown in Figure 9.7.

    Figure 9.7 The Add Method to Interface dialog box

    When you specify parameters using the Add Method to Interface Wizard, you must provide the parameter list in the format used by the Interface Definition Language (IDL). This format requires that you use attributes to indicate the direction of data transfer. By indicating the direction [in], you establish that the first parameter passes data into the method. The [in] parameter is passed by value.

    The EncodeString() function will take the [in] parameter and create an encoded string using a simple encryption algorithm. A pointer to the encoded string will be passed to the client as the [out] parameter.

    Note the use of the Automation string data type BSTR. Using Automation-compatible data types for your interface method parameters means that languages such as Microsoft Visual Basic or Microsoft Visual J++ will be able to use the services provided by your component.

    Most COM interface methods return an HRESULT—a COM-specific 32-bit value that indicates a success or failure condition. HRESULTS are explained in detail in Lesson 2 of Chapter 13. Some client languages (Visual Basic, for example) are not capable of using the HRESULT data type directly. The [retval] attribute indicates the parameter that is used as a return value in such a case. The Visual Basic run-time error-handling system will process the HRESULT returned by the function, but as far as the Visual Basic code is concerned, the return value of the EncodeString() function is the value referred to by the [retval] parameter. Thus the following piece of Visual Basic code will cause the encrypted form of "Hello" to be displayed in a message box:

    Dim comobj As Encoder Set comobj = New Encoder MsgBox comobj.EncodeString("Hello")

    Click OK to add the EncodeString() method. The Add Method to Interface Wizard places an entry in your project's IDL file based on the descriptive information you entered. The wizard also adds a C++ method to the CEncoder component class.

    Adding Properties to the Component Interface

    Properties are public data members of a COM object. Languages that support COM properties can get and set the value of an object property in much the same way that you set member variables of a C++ class. For example, the following piece of Visual Basic code displays the current value of the Key property in a message box, and assigns it a new value of 3:

    Dim comobj As Encoder Set comobj = New Encoder MsgBox comobj.Key comobj.Key = 3

    Because a COM interface is essentially a table of pointers to functions, C++ implements COM properties as a pair of functions—one to set the value of the function and another to get the value. The Add Property to Interface Wizard automatically creates Get and Put methods for each property you define, although you can choose not to implement a Put method and thus create a read-only property.

  • To add the Key property
    1. In ClassView, right-click the IEncoder interface.
    2. On the shortcut menu, click Add Property.
    3. In the Return Type box, select HRESULT, and in the Property Type box, select short.
    4. In the Property Name box, type Key. Note the MIDL signatures of the get_Key and put_Key() functions that appear in the Implementation box.
    5. Click OK to create the functions that implement the property.

    In the class that implements your COM object, you must define a member variable to hold the data. You must also provide implementations of the Get and Put methods to pass data to and from this member variable.

  • To implement the Key property
    1. Add a short protected member variable named m_Key to the CEncoder class.
    2. Add the following line to the constructor CEncoder::CEncoder() to initialize the m_Key variable to a default value:
    3. m_Key = 1;

    4. In ClassView, expand the IEncoder interface under the CEncoder class item to locate the get_Key() and put_Key() functions. Implement these functions as shown in the following code:
    5. STDMETHODIMP CEncoder::get_Key(short *pVal) {      *pVal = m_Key;      return S_OK; } STDMETHODIMP CEncoder::put_Key(short newVal) {      newVal = newVal > 5 ? 5 : newVal;      newVal = newVal < -5 ? -5 : newVal;      m_Key = newVal;      return S_OK; }

    Note the simple bounds-checking code incorporated into the put_Key() function.

    Implementing Component Methods

    Now that you have implemented the Key property, you can add code to implement the EncodeString() method.

  • To implement the EncodeString() method
    1. On the View menu, click Workspace, and then click the ClassView tab.
    2. Click the plus sign next to CEncoder to expand the contents of this class.
    3. Click the plus sign next to IEncoder, within the CEncoder class, to expand the contents of this interface as shown in Figure 9.8.
    4. click to view at full size.

      Figure 9.8 ClassView displaying the methods of the IEncoder interface

    5. Double-click the EncodeString() method underneath the IEncoder interface, which is subordinate to the CEncoder class. This opens an edit window into the body of the EncodeString() implementation function within the Encoder.cpp file.
    6. Add code to the body of the EncodeString() function so that it appears as follows:
    7. 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; }

    It may help you to understand the function above if you know a little more about BSTRs. A BSTR is essentially a pointer to a wide-character string (*wchar_t). You can see how the code just shown uses a BSTR as a parameter to the wscpy() standard library function. However, a BSTR is more than just an array of characters—it is prefixed with a 4-byte integer that indicates the number of bytes that the string contains. This means that you must allocate space for a BSTR using the Win32 function SysAllocString(). The example above allocates a BSTR from an existing BSTR. Alternatively, you can allocate a BSTR from a wchar_t array as shown here:

    wchar_t wszPeas[] = L"Visualize Whirled Peas"; BSTR bstrPeas = ::SysAllocString(wszPeas);

    When you have finished with a BSTR, deallocate it using SysFreeString(). Note that the BSTR outstring in our example is not deallocated because it needs to be passed back to the client. In COM, it is the client's responsibility to free resources allocated by a server at a client's request.

    You will also notice that the EncodeString() function makes use of the SysStringLen() function to discover the length (in characters) of the BSTR.

    You can now build your EncodeServer project. If compilation and linking are successful, Visual Studio takes the additional step of registering your Encoder object on the local computer so that client applications will be able to use the services of the Encoder object. In Chapter 11, you will develop a client program that does just that.

    Lesson Summary

    When you want to create COM objects, the simplest approach is to employ ATL. This framework provides several wizards to aid you, including the ATL COM AppWizard, the ATL Object Wizard, the Add Method to Interface Wizard, and the Add Property to Interface Wizard. These wizards let you harness ATL to generate all the COM-specific boilerplate code necessary to implement a COM object. Your focus can thus be on the implementation of the interface properties and methods exposed by your COM object. The net result is higher productivity.



    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