Is COM Tedious?

[Previous] [Next]

With COM, every time you decide to implement a new interface, you must provide much more than just the implementation code for the methods exposed by that interface. Because every interface must inherit from IUnknown, you must also supply an implementation for IUnknown's AddRef, Release, and QueryInterface methods. Typically, you must also provide an associated class object that implements the methods defined by the IClassFactory interface. If the new interface must behave effectively in C++, Microsoft Visual Basic, and scripting environments such as Microsoft Active Server Pages, you'll probably want to declare the interface as a dual interface—meaning that it is derived from IDispatch—so you'll have to supply an implementation of the four IDispatch methods as well. The great thing about COM development using C++ is that you have complete control over the implementation of each of these interfaces and methods. In other languages, such as Visual Basic, you don't. In some cases, you must have a high level of coding control, especially when you're developing code that defines your application's business logic. But in many cases, the code for the IDispatch, IClassFactory, and IUnknown interfaces—among others—is boilerplate; that is, the implementation code for those interfaces is essentially the same for each of your custom interfaces. Certainly, to be a productive COM developer, you must have a solid understanding of the role of each of those core interfaces. But having to reimplement "stock" interfaces unnecessarily over and over again can reduce your efficiency and introduce coding errors.

The tendency toward tedium found in C++ COM development is compounded by the fact that the code required to work with COM objects on the client side is often both ugly and error prone. Compare the code shown in Listings 5-1 and 5-2. Both listings show the code required to automate a COM-based application. Listing 5-1 shows the code required in Visual Basic, and Listing 5-2 shows the code required in Microsoft Visual C++. As you can see, the Visual Basic code couldn't be simpler—but the C++ code isn't a pretty picture. Why is the Visual Basic code so much cleaner? Three fundamental reasons account for the simplicity of the Visual Basic code:

  • Visual Basic automatically manages dynamic interface discovery and object lifetime, meaning that the client code never explicitly calls any of the IUnknown methods QueryInterface, AddRef, and Release.
  • Visual Basic inherently supports the BSTR and VARIANT data types.
  • Visual Basic hides the details required to make calls to IDispatch-based methods.

Listing 5-1. Visual Basic client code.

 Dim App As Object Private Sub Form_Load()     Set App = CreateObject("MSDEV.Application")     App.Visible = True     App.Quit End Sub 

Listing 5-2. Visual C++ client code.

 HRESULT CountArgsInFormat(LPCTSTR pszFmt, UINT FAR *pn);  LPCTSTR GetNextVarType(LPCTSTR pszFmt, VARTYPE FAR* pvt);  HRESULT CreateObject(LPOLESTR pszProgID,      IDispatch FAR* FAR* ppdisp); void OnFormLoad() {     CoInitialize();     CLSID clsid;     CLSIDFromProgID(L"MSDEV.APPLICATION", &clsid);     IDispatch* pdisp = NULL;     hr = CoCreateInstance(clsid, NULL, CLSCTX_SERVER,         IID_IDispatch, (void**)&pdisp);     Invoke(pdisp, DISPATCH_PROPERTYPUT, NULL, NULL, NULL,          OLESTR("Visible"), "b", VARIANT_TRUE);         Invoke(pdisp, DISPATCH_METHOD, NULL, NULL, NULL,          OLESTR("Quit"), NULL);       pdisp->Release();      CoUninitialize(); } HRESULT Invoke(LPDISPATCH pdisp,  WORD wFlags, LPVARIANT pvRet,      EXCEPINFO FAR* pexcepinfo, UINT FAR* pnArgErr,     LPOLESTR pszName, LPCTSTR pszFmt, ...)  {      va_list argList;      va_start(argList, pszFmt);        DISPID dispid;      HRESULT hr;      VARIANTARG* pvarg = NULL;      if(pdisp == NULL)          return E_INVALIDARG;            hr = pdisp->GetIDsOfNames(IID_NULL, &pszName, 1,         LOCALE_USER_DEFAULT, &dispid);      if(FAILED(hr))          return hr;     DISPPARAMS dispparams;      _fmemset(&dispparams, 0, sizeof dispparams);      if(pszFmt != NULL)          CountArgsInFormat(pszFmt, &dispparams.cArgs);     DISPID dispidNamed = DISPID_PROPERTYPUT;      if(wFlags & DISPATCH_PROPERTYPUT)      {          if(dispparams.cArgs == 0)              return E_INVALIDARG;          dispparams.cNamedArgs = 1;          dispparams.rgdispidNamedArgs = &dispidNamed;      }        if(dispparams.cArgs != 0)      {          pvarg = new VARIANTARG[dispparams.cArgs];          if(pvarg == NULL)              return E_OUTOFMEMORY;             dispparams.rgvarg = pvarg;          _fmemset(pvarg, 0, sizeof(VARIANTARG) * dispparams.cArgs);          LPCTSTR psz = pszFmt;          pvarg += dispparams.cArgs - 1;   // Parameters go in                                          //  opposite order.          while (psz = GetNextVarType(psz, &pvarg->vt))          {              if(pvarg < dispparams.rgvarg)              {                  hr = E_INVALIDARG;                  goto cleanup;                }              switch (pvarg->vt)              {              case VT_I2:                  V_I2(pvarg) = va_arg(argList, short);                  break;              case VT_I4:                  V_I4(pvarg) = va_arg(argList, long);                  break;              case VT_R4:                  V_R4(pvarg) = va_arg(argList, float);                  break;               case VT_DATE:             case VT_R8:                  V_R8(pvarg) = va_arg(argList, double);                  break;              case VT_CY:                  V_CY(pvarg) = va_arg(argList, CY);                  break;              case VT_BSTR:                  V_BSTR(pvarg) = SysAllocString(va_arg(argList,                     OLECHAR FAR*));                 if(pvarg->bstrVal == NULL)                   {                      hr = E_OUTOFMEMORY;                        pvarg->vt = VT_EMPTY;                      goto cleanup;                    }                  break;              case VT_DISPATCH:                  V_DISPATCH(pvarg) = va_arg(argList, LPDISPATCH);                  break;              case VT_ERROR:                  V_ERROR(pvarg) = va_arg(argList, SCODE);                  break;              case VT_BOOL:                  V_BOOL(pvarg) = va_arg(argList, BOOL) ? -1 : 0;                  break;              case VT_VARIANT:                  *pvarg = va_arg(argList, VARIANTARG);                   break;              case VT_UNKNOWN:                  V_UNKNOWN(pvarg) = va_arg(argList, LPUNKNOWN);                  break;                case VT_I2|VT_BYREF:                  V_I2REF(pvarg) = va_arg(argList, short FAR*);                  break;              case VT_I4|VT_BYREF:                  V_I4REF(pvarg) = va_arg(argList, long FAR*);                  break;              case VT_R4|VT_BYREF:                  V_R4REF(pvarg) = va_arg(argList, float FAR*);                  break;              case VT_R8|VT_BYREF:                  V_R8REF(pvarg) = va_arg(argList, double FAR*);                  break;             case VT_DATE|VT_BYREF:                  V_DATEREF(pvarg) = va_arg(argList, DATE FAR*);                  break;              case VT_CY|VT_BYREF:                  V_CYREF(pvarg) = va_arg(argList, CY FAR*);                  break;              case VT_BSTR|VT_BYREF:                  V_BSTRREF(pvarg) = va_arg(argList, BSTR FAR*);                  break;              case VT_DISPATCH|VT_BYREF:                  V_DISPATCHREF(pvarg) = va_arg(argList,                      LPDISPATCH FAR*);                  break;              case VT_ERROR|VT_BYREF:                  V_ERRORREF(pvarg) = va_arg(argList, SCODE FAR*);                  break;              case VT_BOOL|VT_BYREF:                   {                      BOOL FAR* pbool = va_arg(argList, BOOL FAR*);                      *pbool = 0;                      V_BOOLREF(pvarg) = (VARIANT_BOOL FAR*)pbool;                  }                   break;                            case VT_VARIANT|VT_BYREF:                   V_VARIANTREF(pvarg) = va_arg(argList,                      VARIANTARG FAR*);                  break;              case VT_UNKNOWN|VT_BYREF:                  V_UNKNOWNREF(pvarg) = va_arg(argList,                      LPUNKNOWN FAR*);                  break;                default:                  {                      hr = E_INVALIDARG;                      goto cleanup;                    }                  break;              }                --pvarg; // Get ready to fill the next argument.          } //while      } //if            if(pvRet)          VariantInit(pvRet);       hr = pdisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,         wFlags, &dispparams, pvRet, pexcepinfo, pnArgErr);  cleanup:      // Clean up any arguments that need cleaning up.      if(dispparams.cArgs != 0)      {          VARIANTARG FAR* pvarg = dispparams.rgvarg;          UINT cArgs = dispparams.cArgs;                       while (cArgs--)          {              switch (pvarg->vt)              {              case VT_BSTR:                  VariantClear(pvarg);                  break;              }              ++pvarg;          }      }      delete dispparams.rgvarg;      va_end(argList);      return hr;     }       HRESULT CountArgsInFormat(LPCTSTR pszFmt, UINT FAR *pn)  {      *pn = 0;        if(pszFmt == NULL)          return NOERROR;            while (*pszFmt)        {          if(*pszFmt == `&')              pszFmt++;            switch (*pszFmt)          {              case `b':              case `i':               case `I':              case `r':               case `R':              case `c':              case `s':              case `e':              case `d':              case `v':              case `D':              case `U':                  ++*pn;                   pszFmt++;                  break;              case `\0':                default:                  return E_INVALIDARG;         }      }      return NOERROR;  }    LPCTSTR GetNextVarType(LPCTSTR pszFmt, VARTYPE FAR* pvt)  {         *pvt = 0;      if(*pszFmt == `&')       {          *pvt = VT_BYREF;           pszFmt++;              if(!*pszFmt)              return NULL;          }       switch (*pszFmt)      {          case `b':              *pvt |= VT_BOOL;              break;          case `i':               *pvt |= VT_I2;              break;          case `I':               *pvt |= VT_I4;              break;          case `r':               *pvt |= VT_R4;              break;          case `R':               *pvt |= VT_R8;              break;          case `c':              *pvt |= VT_CY;              break;          case `s':               *pvt |= VT_BSTR;              break;          case `e':               *pvt |= VT_ERROR;              break;          case `d':               *pvt |= VT_DATE;               break;          case `v':               *pvt |= VT_VARIANT;              break;          case `U':               *pvt |= VT_UNKNOWN;               break;          case `D':               *pvt |= VT_DISPATCH;              break;            case `\0':               return NULL;     // End of format string          default:              return NULL;      }       return ++pszFmt;    }  

Admittedly, the sample code in Listing 5-1 and Listing 5-2 represents an extreme example of the difference between the client-side COM support provided by Visual Basic and by Visual C++, but it does illustrate the difficulty of using COM objects from C++. If you're a big fan of C++, you don't need to get defensive. A framework such as Visual Basic, which takes care of all the details, is like valet service for your car. It's a great convenience if you're in a hurry, but it requires a degree of trust and a significant overhead expense.

In an ideal world, C++ developers would have complete control over any details considered necessary—which might change from object to object—but also have a lightweight, unobtrusive framework to handle any details deemed unnecessary behind the scenes. For example, because nearly all the code shown in Listing 5-2 consists of helper functions that can be used in every COM application that makes calls to an IDispatch interface, encapsulating those functions within a reusable class makes sense. That's where ATL comes in. It takes care of many of the messy COM details yet leaves you a great degree of flexibility and control. For most C++ developers, ATL represents an ideal trade-off. It provides a lean, optimized, default implementation of many tedious COM idioms while still allowing you the ability to override, replace, or ignore any of those idioms.(In case you're wondering, ATL encapsulates the lengthy code in Listing 5-2 in a handy CComDispatchDriver class.)

Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127 © 2008-2017.
If you may any questions please contact us: