Lesson 1: COM Client Applications

In this lesson, you will create a simple application that uses the Encoder COM component you created in the exercises in Chapter 9. The application will use the header files generated by the MIDL compiler from the EncodeServer.idl file. In the latter part of the lesson, you will learn about the features of the Visual C++ compiler that support the creation of COM client applications and components, and you will use these features to re-implement the EncodeClient application.

After this lesson, you will be able to:

  • Describe how the header files generated by the MIDL compiler can be used by a client application.
  • Describe the features of the Visual C++ COM compiler support.
  • Describe how to import a type library into a project.
Estimated lesson time: 30 minutes

COM Server Header Files

In Chapter 9, you learned how the MIDL compiler generates header files that make a COM server's interface and GUID definitions available to C and C++ clients. The files generated for the Encoder component are named EncodeServer.h and EncodeServer_i.c.

The EncodeServer_i.c file contains the following code, which declares the GUIDs defined by the Encoder component as easy to read (and easy to type!) constants:

const IID IID_IEncoder =  {0x69F4B926,0x6641,0x11D3,{0x93,0x4B,0x00,0x80,0xC7,0xFA,0x0C,0x3E}}; const IID LIBID_ENCODESERVERLib =  {0x69F4B91A,0x6641,0x11D3,{0x93,0x4B,0x00,0x80,0xC7,0xFA,0x0C,0x3E}}; const CLSID CLSID_Encoder =  {0x69F4B917,0x6641,0x11D3,{0x93,0x4B,0x00,0x80,0xC7,0xFA,0x0C,0x3E}};

The IID and CLSID types are defined earlier in the file as C structures that represent a GUID.

At the heart of the EncodeServer.h file is the following C++ declaration (comments have been removed for clarity):

MIDL_INTERFACE("69F4B926-6641-11D3-934B-0080C7FA0C3E") IEncoder : public Iunknown {      public:      virtual HRESULT STDMETHODCALLTYPE EncodeString(          const BSTR instring, BSTR __RPC_FAR *outstring) = 0;      virtual HRESULT STDMETHODCALLTYPE get_Key(          short __RPC_FAR *pVal) = 0;      virtual HRESULT STDMETHODCALLTYPE put_Key(          short newVal) = 0; };

This code defines an abstract class named IEncoder (derived from the abstract class IUnknown), which contains member functions corresponding to the methods exposed by the IEncoder COM interface. The MIDL_INTERFACE macro uses the Microsoft specific __declspec(uuid()) declarator to associate the interface GUID with the class. The keyword __uuidof() can be applied to retrieve a constant GUID attached to a class in this way.

Client programs use the definitions contained in these header files to create pointers to the IEncoder class, which can be passed to the CoCreateInstance() function, as shown in the following example:

IEncoder * pServer; HRESULT hr = ::CoCreateInstance(CLSID_Encoder, NULL,     CLSCTX_INPROC_SERVER,     IID_IEncoder,     (void **) &pServer);

If this function call succeeds in creating an Encoder COM object, the pServer variable receives a pointer to the vtable of the COM object. The pServer pointer can then be used to call the methods of the IEncoder interface.

To demonstrate the use of the EncodeServer.h and EncodeServer_i.c header files, you will develop a simple console application, EncodeHello, that uses an Encoder COM object to display an encoded "Hello World" string.

  • To create the EncodeHello application
    1. On the Visual C++ File menu, click New. Choose Win32 Console Application as the project type and enter EncodeHello as the project name. Click OK.
    2. In Step 1 of the Win32 Console Application Wizard, select A simple application and click Finish. Click OK to create the project.
    3. Using Windows Explorer, copy the files EncodeServer.h and EncodeServer_i.c from the EncodeServer project folder to the EncodeHello project folder.
    4. In FileView, locate and open the EncodeHello.cpp file. At the top of the file, add the following lines:
    5. #include <iostream.h> #include "EncodeServer.h" #include "EncodeServer_i.c"

    6. Add code to the body of the Main() function so that it appears as follows:
    7. (This code can be found in CH10_01.cpp, installed from the companion CD.)

      int main(int argc, char* argv[]) {     ::CoInitialize(NULL);     IEncoder * pServer;     HRESULT hr = ::CoCreateInstance(CLSID_Encoder, NULL,         CLSCTX_INPROC_SERVER,         IID_IEncoder,         (void **) &pServer);     if(SUCCEEDED(hr))     {         short nKey = 1;         cout << "Enter a code key between -5 and 5: ";         cin >> nKey;         wchar_t wstrHello[16] = L"Hello World!";         BSTR bstrHello = ::SysAllocString(wstrHello);         BSTR bstrCodedHello;         HRESULT hr = pServer->put_Key(nKey);         if(FAILED(hr)) goto ComError;         hr = pServer->EncodeString(bstrHello, &bstrCodedHello);         if(FAILED(hr)) goto ComError;         char strOut[16];         wcstombs(strOut, bstrCodedHello, 16);         cout << "\n" << strOut << "\n\n";          ComError:             if(FAILED(hr)) cout << "COM Error" << "\n\n";             ::SysFreeString(bstrHello);             ::SysFreeString(bstrCodedHello);         pServer->Release();      }      ::CoUninitialize();      return 0; }

      The CoInitialize() function calls the CoInitializeEx() function with the COINIT_APARTMENTTHREADED parameter. In Lesson 4 of Chapter 8, you learned how the CoInitializeEx() function must be called to initialize the COM libraries on the current thread and specify the threading model to use for the COM objects that are created. Each call to CoInitialize() or to CoInitializeEx() must be balanced with a call to the CoUninitialize() function, which closes the COM library on the current thread.

      The code contained in EncodeHello.cpp is pretty straightforward—an instance of the Encoder COM object is created, the Key property is set to a value supplied by the user, and the EncodeString() method is called to encode the "Hello World!" string.

      Notice the call to IUnknown::Release() toward the end of the function. In Lesson 1 of Chapter 8, you learned that when CoCreateInstance() is used to obtain a pointer to an interface, the server reference count is incremented. It is the client's responsibility to call Release() to decrement the reference count after the client has finished with the interface pointer.

    8. Build and run the EncodeHello application to ensure its performance.

    Visual C++ COM Compiler Support

    With Visual C++ 5.0, Microsoft introduced a number of classes and C++ language extensions that simplified the creation of COM client programs. These include _com_ptr_t, a smart pointer class that encapsulates a COM interface pointer; and the _bstr_t and _variant_t classes, which encapsulate the BSTR and VARIANT data types. Also provided is a COM exception class, _com_error. You can use these classes by including (using #include) the comdef.h file. Another new feature introduced with Visual C++ 5.0 is the #import directive, which generates C++ header files from information contained in a COM server object's type library. These header files make extensive use of the _com_ptr_t class and use _bstr_t and _variant_t where the BSTR and VARIANT data types are used.

    The features of the Visual C++ COM compiler support are described briefly in the following sections.

    _com_ptr_t

    The _com_ptr_t class is a templated class that encapsulates a COM interface pointer. The class provides some extra code that helps simplify reference counting. A _com_ptr_t object calls the IUnknown::AddRef() and IUnknown::Release() methods of the encapsulated interface on your behalf to ensure that the lifetime of the COM object that provides the interface is managed properly. AddRef() is called automatically when a _com_ptr_t object is created as a copy from an existing interface pointer, and Release() is called automatically when a _com_ptr_t object goes out of scope.

    Although this "smart pointer" behavior makes for more readable source code, you shouldn't let the _com_ptr_t class lull you into a false sense of security. You should always be aware of the current usage of COM objects in your code, and know when you should be calling AddRef() and Release()—even if you employ a smart pointer to do this for you.

    The simplest way to instantiate a _com_ptr_t template to a specific interface type is to create the _com_ptr_t object with the _COM_SMARTPTR_TYPEDEF macro. This macro takes an interface name and the unique GUID for a specific interface and declares a specialization of _com_ptr_t with the name of the interface plus a suffix of Ptr. For example, in a file that includes the EncodeServer.h and EncodeServer_i.c header files, the following line will declare the _com_ptr_t specialization IEncoderPtr:

    _COM_SMARTPTR_TYPEDEF(IEncoder, __uuidof(IEncoder));

    Instances of the instantiated type can then call the com_ptr_t member function CreateInstance() to obtain an interface pointer from a COM server, as follows:

    IEncoderPtr pEnc ; pEnc.CreateInstance(CLSID_Encoder);

    This pointer can then be used to call the methods of the interface using the com_ptr_t overload of the -> operator as follows:

    int n = 3; HRESULT hr = pEnc->put_Key(n);

    Note that the _com_ptr_t member functions are called by using the dot operator (as in the call to CreateInstance()); and that the methods of the interface are called through the overloaded -> operator (as in the call to the put_Key() method just shown).

    It is also possible for _com_ptr_t objects to be created from an existing pointer to a COM interface, or copied from another _com_ptr_t object. The Visual C++ Help file gives details on how the AddRef() and Release() methods are called by the assignment operators and copy constructors.

    _bstr_t

    A _bstr_t object encapsulates the BSTR data type. The class manages resource allocation and de-allocation through internal calls to SysAllocString() and SysFreeString(), and uses reference counting for efficient use of memory. The class provides a number of operators that enable you to use a _bstr_t object as easily as you would use a CString object. One object that isn't provided, however, is the & "address of" operator, so you cannot pass the address of a _bstr_t object to a function that expects a BSTR* object.

    _variant_t

    A _variant_t object is a thin wrapper for the VARIANT data type. The class manages creation and destruction of an encapsulated VARIANT through internal calls to the API functions VariantInit() and VariantClear(). A _variant_t object provides constructors and operators that allow easy creation and manipulation of a VARIANT.

    _com_error

    A _com_error object represents a COM exception condition and encapsulates the HRESULT code returned by nearly all COM interface methods. A _com_error object can be thrown by the _com_raise_error() function. For more information on _com_error, see Lesson 2 of Chapter 13.

    Import Directive

    The #import preprocessor directive enables you to generate C++ header information about the COM object and its interfaces from a type library. This is very useful if you don't have access to the MIDL-generated header files; the type library is nearly always available because it is usually bound to the server DLL or EXE file. The #import preprocessor also generates a set of smart pointers that enable you to access the COM object interfaces.

    When you use #import, the Visual Studio C compiler preprocessor creates two header files that reconstruct the type library contents as C++ source code. The primary header file is similar to that produced by MIDL in that it defines C++ functions that can be used to call methods of the interface exposed by the COM object. However, it also declares additional functions that wrap the direct interface methods to provide properties and methods similar to those expected by a Visual Basic client. Properties can be accessed as member variables of the class, and the methods are wrapped so that an [out, retval] parameter is passed back as a return value. The HRESULT value is intercepted—if it reports an error condition, it is thrown as a _com_error exception. The _bstr_t and _variant_t classes are used as argument types and return types for these wrapper functions wherever it is appropriate.

    The primary header file has the same name as the type library or the dynamic link library, with a .tlh extension. The secondary header file has the same name as the type library, with a .tli extension. The secondary header file contains the implementations for compiler-generated wrapper functions, and is included (using #include) in the primary header file.

    Both header files are placed in the output directory. They are read and compiled by the compiler as if the primary header file was named by a #include directive.

    The primary type library header file includes smart pointer declarations for the interfaces exposed by the COM object. The _COM_SMARTPTR_TYPEDEF macro is used to instantiate a _com_ptr_t template for every interface defined in the type library. The file comdef.h, which contains the definitions of the compiler support classes, is included in the .tlh file.

    In the following exercise, you will learn how to use the #import directive to import a type library, and how to use the generated smart pointer types and wrapper functions in your client application code. You will modify the EncodeHello application that you created earlier in this chapter.

  • To import the EncodeServer type library
    1. Return to the EncodeHello project. In FileView, locate and open the StdAfx.h file.
    2. Just before the //{{AFX_INSERT_LOCATION}} comment, add a line similar to the following:
    3. #import "C:\EncodeServer\Debug\EncodeServer.dll" no_namespace

      Make sure that you type the correct path to the EncodeServer.dll file. It will probably be different on your computer from the one shown above. You should specify the no_namespace attribute. This ensures that the classes generated from the type library will be defined within the global namespace.

    4. Save and close the StdAfx.h file. In FileView, right-click the StdAfx.cpp file, and click Compile StdAfx.cpp on the shortcut menu.
    5. When the compilation is complete, locate the files EncodeServer.tlh and EncodeServer.tli in the EncodeServer\Debug folder. Inspect the contents of the .tlh file. Note the declaration of the IEncoderPtr smart pointer provided by the following line:
    6. _COM_SMARTPTR_TYPEDEF(IEncoder, __uuidof(IEncoder));

      Also note the following member functions declared within the IEncoder structure definition:

      // Property data __declspec(property(get=GetKey,put=PutKey)) short Key;    // Wrapper methods for error-handling    _bstr_t EncodeString (_bstr_t instring);    short GetKey ();    void PutKey (short pVal);    // Raw methods provided by interface    virtual HRESULT __stdcall raw_EncodeString (BSTR instring, BSTR *        outstring) = 0;    virtual HRESULT __stdcall get_Key (short * pVal) = 0;    virtual HRESULT __stdcall put_Key (short pVal) = 0;

    The "raw" methods at the bottom are similar to those in the EncodeServer.h file generated by the MIDL compiler. The wrapper methods, however, look much more like the kind of member functions provided by a regular C++ class. Note that the declaration of the Key member variable uses the __declspec(property) declarator. This allows the user of the class to access the Get and Put functions by using the Key variable on the left and right sides of an assignment statement. Also note that these functions use the _bstr_t class for argument types and return values.

    The implementation of the wrapper functions can be found in the EncodeServer.tli file. The implementation of the GetKey() function, which follows, demonstrates how an [out, retval] parameter is passed back as a return value and the HRESULT value is intercepted and potentially thrown as an exception.

    inline short IEncoder::GetKey ()  {    short _result;    HRESULT _hr = get_Key(&_result);    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));    return _result; }

    Now that you have imported the type library, you can alter the application code to take advantage of the classes generated by importing the type library.

  • To alter the EncodeHello application code
    1. Remove the following #include directives from the top of the EncodeHello.cpp file:
    2. #include "EncodeServer.h" #include "EncodeServer_i.c"

    3. Re-implement the Main() function of the EncodeHello application as follows:
    4. (This code can be found in CH10_02.cpp, installed from the companion CD.)

      int main(int argc, char* argv[]) {     CoInitialize(NULL);     {         IEncoderPtr pServer;         HRESULT hr = pServer.CreateInstance(__uuidof(Encoder));         if(SUCCEEDED(hr))         {             short nKey = 1;             cout << "Enter a code key between -5 and 5: ";             cin >> nKey;             _bstr_t bstrHello = "Hello World!";             _bstr_t bstrCodedHello;             try             {                pServer->Key = nKey;                bstrCodedHello = pServer->EncodeString(bstrHello);            cout << "\n" << (const char *)                 bstrCodedHello << "\n\n";              }              catch(_com_error e)              {                cout << e.ErrorMessage() << "\n\n";              }         }    }    ::CoUninitialize();    return 0; }

    Note that in this example, the application code is placed within its own code block between the calls to CoInitialize() and CoUninitialize(). This ensures that the pServer variable goes out of scope before the CoUninitialize() function is called to close the COM libraries. When pServer goes out of scope, the _com_ptr_t destructor calls the Release() method on the encapsulated IEncoder pointer. If this occurs after the COM libraries have been closed, disaster will ensue.

    Notice that this code is much easier to read than the previous version, and that it closely resembles regular C++ code that does not use COM. Don't be fooled by appearances, however—it is impossible to write efficient, error-free COM code without a thorough understanding of the underlying technology.

    Lesson Summary

    The MIDL compiler generates header files that make a COM server's interface and GUID definitions available to C and C++ client application source code. These files define structures that can be used to access the interface methods of the instantiated COM objects. Readable definitions of GUIDs are also provided.

    Visual C++ COM compiler support provides the #import statement, which allows you to generate C++ header information about the COM object and its interfaces from a type library. The generated files make extensive use of the _com_ptr_t smart pointer class, the _com_error exception class, and other Microsoft-specific C++ language extensions to wrap the COM interface methods and simplify client development. The _bstr_t and _variant_t classes simplify the use of BSTR and VARIANT data types.



    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