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
#define _ATL_APARTMENT_THREADED Public CComObjectRootEx<CComSingleThreadModel> |
with
#define _ATL_FREE_THREADED Public CComObjectRootEx<CComMultiThreadModel> |
NOTE[Previous] [
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 entitymanages and stores its state.
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
In the next chapter, we'll see how ATL also makes using COM objects a little more painless with its client-side helper classes.
Chapter 5
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
With COM, every time you decide to implement a new interface, you must provide much more than just the implementation code for the
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:
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 `
|
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
In an ideal world, C++ developers would have complete control over any details