Flylib.com

Books Software

 
 
 

Threading Revisited

[Previous] [Next]

Threading Revisited

After a careful examination of the implementation of the TipOfTheDay coclass, we confirmed our original suspicions: we were able to design the component such that it doesn't store global data, per-class data, or even per-instance data. The state of the component is entirely determined by the cookie parameter stored by the client application, so restricting object access to a single thread is unnecessary. Because the TipOfTheDay component doesn't use worker threads, there's no reason we wouldn't allow it to be created in an STA, so it makes sense to support both threading models. We'll change the Registry setting from ThreadingModel=Apartment to ThreadingModel=Both by editing the TIPOFTHEDAY.RGS file that the ATL Object Wizard created and added to the project.

InprocServer32 = s `%MODULE%'
{
    val ThreadingModel = s `Both'
}

We'll also need to change some wizard-generated code to reflect the threading model. Replace

#define _ATL_APARTMENT_THREADED
Public CComObjectRootEx<CComSingleThreadModel>

with

#define _ATL_FREE_THREADED
Public CComObjectRootEx<CComMultiThreadModel>

NOTE
It wouldn't be accurate to say that the TipOfTheDay component is stateless, because the call history does indeed affect the data returned by the GetNextTip method. A client that calls GetNextTip three times will get a different result than a client that calls GetNextTip four times. More precisely, our component can be called "state estranged." A separate entity manages and stores its state.

[Previous] [ Next ]

Conclusion

The choices you make when you use the ATL COM AppWizard, the ATL Object Wizard, and the Add Method dialog box significantly impact the architecture and implementation of your COM components. Although each of these productivity features is beneficial, they all must be used judiciously and with an understanding of the effect they have on the outcome of your components. In this chapter alone, we've discussed several key concepts you'll need to understand when creating components with ATL:

  • The ATL preprocessor symbols
  • The AppWizard-generated build configurations
  • The choice of a threading model
  • The advantages and drawbacks of dual interfaces
  • The support needs of clients built using different languages, including ASP, Visual Basic, and Visual C++

In the next chapter, we'll see how ATL also makes using COM objects a little more painless with its client-side helper classes.

[Previous] [Next]

Chapter 5

Client-Side ATL

As you saw in Chapter 2, COM is a beautiful, logical model for component-based development. The underlying concepts that define the COM model—such as encapsulation, abstraction, and polymorphism—are essentially simple principles. Despite the simplicity of the model itself, however, many developers find COM development under C++ tedious . Many developers ( ourselves included) complain that a significant portion of day-to-day C++ COM development is spent implementing repetitive, low-level details. In this chapter, we'll focus on the client-side use of COM and take an in-depth look at the ATL classes that make client-side COM easier. Specifically, we'll look at the ATL smart pointer classes CComPtr and CComQIPtr , the data conversion classes CComBSTR and CComVariant , and the handy CComDispatchDriver class.

[Previous] [ Next ]

Is COM Tedious ?

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_I2VT_BYREF: 
                V_I2REF(pvarg) = va_arg(argList, short FAR*); 
                break; 
            case VT_I4VT_BYREF: 
                V_I4REF(pvarg) = va_arg(argList, long FAR*); 
                break; 
            case VT_R4VT_BYREF: 
                V_R4REF(pvarg) = va_arg(argList, float FAR*); 
                break; 
            case VT_R8VT_BYREF: 
                V_R8REF(pvarg) = va_arg(argList, double FAR*); 
                break;
            case VT_DATEVT_BYREF: 
                V_DATEREF(pvarg) = va_arg(argList, DATE FAR*); 
                break; 
            case VT_CYVT_BYREF: 
                V_CYREF(pvarg) = va_arg(argList, CY FAR*); 
                break; 
            case VT_BSTRVT_BYREF: 
                V_BSTRREF(pvarg) = va_arg(argList, BSTR FAR*); 
                break; 
            case VT_DISPATCHVT_BYREF: 
                V_DISPATCHREF(pvarg) = va_arg(argList, 
                    LPDISPATCH FAR*); 
                break; 
            case VT_ERRORVT_BYREF: 
                V_ERRORREF(pvarg) = va_arg(argList, SCODE FAR*); 
                break; 
            case VT_BOOLVT_BYREF:  
                { 
                    BOOL FAR* pbool = va_arg(argList, BOOL FAR*); 
                    *pbool = 0; 
                    V_BOOLREF(pvarg) = (VARIANT_BOOL FAR*)pbool; 
                }  
                break;               
            case VT_VARIANTVT_BYREF:  
                V_VARIANTREF(pvarg) = va_arg(argList, 
                    VARIANTARG FAR*); 
                break; 
            case VT_UNKNOWNVT_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 `
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_I2VT_BYREF: V_I2REF(pvarg) = va_arg(argList, short FAR*); break; case VT_I4VT_BYREF: V_I4REF(pvarg) = va_arg(argList, long FAR*); break; case VT_R4VT_BYREF: V_R4REF(pvarg) = va_arg(argList, float FAR*); break; case VT_R8VT_BYREF: V_R8REF(pvarg) = va_arg(argList, double FAR*); break; case VT_DATEVT_BYREF: V_DATEREF(pvarg) = va_arg(argList, DATE FAR*); break; case VT_CYVT_BYREF: V_CYREF(pvarg) = va_arg(argList, CY FAR*); break; case VT_BSTRVT_BYREF: V_BSTRREF(pvarg) = va_arg(argList, BSTR FAR*); break; case VT_DISPATCHVT_BYREF: V_DISPATCHREF(pvarg) = va_arg(argList, LPDISPATCH FAR*); break; case VT_ERRORVT_BYREF: V_ERRORREF(pvarg) = va_arg(argList, SCODE FAR*); break; case VT_BOOLVT_BYREF: { BOOL FAR* pbool = va_arg(argList, BOOL FAR*); *pbool = 0; V_BOOLREF(pvarg) = (VARIANT_BOOL FAR*)pbool; } break; case VT_VARIANTVT_BYREF: V_VARIANTREF(pvarg) = va_arg(argList, VARIANTARG FAR*); break; case VT_UNKNOWNVT_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; }
': 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 `
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_I2VT_BYREF: V_I2REF(pvarg) = va_arg(argList, short FAR*); break; case VT_I4VT_BYREF: V_I4REF(pvarg) = va_arg(argList, long FAR*); break; case VT_R4VT_BYREF: V_R4REF(pvarg) = va_arg(argList, float FAR*); break; case VT_R8VT_BYREF: V_R8REF(pvarg) = va_arg(argList, double FAR*); break; case VT_DATEVT_BYREF: V_DATEREF(pvarg) = va_arg(argList, DATE FAR*); break; case VT_CYVT_BYREF: V_CYREF(pvarg) = va_arg(argList, CY FAR*); break; case VT_BSTRVT_BYREF: V_BSTRREF(pvarg) = va_arg(argList, BSTR FAR*); break; case VT_DISPATCHVT_BYREF: V_DISPATCHREF(pvarg) = va_arg(argList, LPDISPATCH FAR*); break; case VT_ERRORVT_BYREF: V_ERRORREF(pvarg) = va_arg(argList, SCODE FAR*); break; case VT_BOOLVT_BYREF: { BOOL FAR* pbool = va_arg(argList, BOOL FAR*); *pbool = 0; V_BOOLREF(pvarg) = (VARIANT_BOOL FAR*)pbool; } break; case VT_VARIANTVT_BYREF: V_VARIANTREF(pvarg) = va_arg(argList, VARIANTARG FAR*); break; case VT_UNKNOWNVT_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; }
': return NULL; // End of format string default: return NULL; } return ++pszFmt; }
{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}

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.)