All of our variations on the Beeper object so far have supported both English and German. If you don't need to support more than one language in an automation object, you can simplify your implementation even more by completely eliminating your own IDispatch entry points, as demonstrated in the Beeper5 sample, which is English-only (CHAP14\BEEPER5).
This is made possible through a standard implementation of IDispatch that OLE provides through the function CreateStdDispatch. This function creates an object on which your own object must aggregate, as you learned in Chapter 2. In other words, the standard dispatch object provides only an IDispatch interface, and in order for it to provide the correct behavior through its IUnknown functions, it has to know about your object—the outer object.
The standard dispatch object supports only one locale because you have to pass a language-specific ITypeInfo pointer to CreateStdDispatch, so this is the only language that the object will recognize. If you need to support multiple languages from within a single instance of an object, it is best to stick with implementing IDispatch using ITypeInfo's help. This doesn't mean that you can make only a binary that supports a single language with CreateStdDispatch; it's just that each instance will be fixed for a particular language, and at crea-tion time you do not yet know which language the controller will ask for later. But if you want to support only a neutral language or the user's local language, CreateStdDispatch is a great convenience.
Some documentation says that the standard dispatch object doesn't support custom exceptions, the kind of exceptions we've been raising in the last few variations. With the advent of the error object feature, this is no longer true. Internally, the standard dispatch looks about the same as the IDispatch in Beeper3 except that it doesn't handle multiple locales in GetTypeInfo. But it still uses ITypeInfo::Invoke, which in turn looks for an error object, so you can support exceptions now when using the standard dispatch object.
I got on a cleaning binge when I removed the CImpIDispatch class from Beeper5, so I also got rid of all the exception handling code to keep Beeper5 as simple as possible. What is left to show is how we create the standard dispatch object and how we have to modify CBeeper::QueryInterface. The rest of the implementation is the same as for Beepers 2 and 3, using an implementation of IBeeper (although put_Sound no longer raises exceptions).
To call CreateStdDispatch, as we do in CBeeper::Init, we must first have a pointer to the correct ITypeInfo, which we obtain using code similar to what we used in our IDispatch::GetTypeInfo:
BOOL CBeeper::Init(void)
{
LPUNKNOWN pIUnknown=this;
ITypeLib *pITypeLib;
HRESULT hr;
if (NULL!=m_pUnkOuter)
pIUnknown=m_pUnkOuter;
if (FAILED(LoadRegTypeLib(LIBID_BeeperTypeLibrary, 1, 0
, LANG_NEUTRAL, &pITypeLib)))
{
if (FAILED(LoadTypeLib(OLETEXT("BEEP0000.TLB"), &pITypeLib)))
return FALSE;
}
hr=pITypeLib->GetTypeInfoOfGuid(IID_IBeeper, &m_pITINeutral);
pITypeLib->Release();
if (FAILED(hr))
return FALSE;
hr=CreateStdDispatch(pIUnknown, (IBeeper *)this, m_pITINeutral
, &m_pIUnkStdDisp);
if (FAILED(hr))
return FALSE;
return TRUE;
}
You'll see that CreateStdDispatch also takes a pointer to our controlling unknown—our custom interface (which in turn is given to ITypeInfo::Invoke)—and always returns an IUnknown pointer. This last point is true because our Beeper object aggregates the IDispatch interface from the standard dispatch object. According to the aggregation rules, a newly created object in an aggregate must return an IUnknown pointer initially.
This IUnknown pointer is then used in the implementation of QueryInterface as follows:
STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
{
*ppv=NULL;
if (IID_IUnknown==riid)
*ppv=this;
if (IID_IDispatch==riid śś IID_IBeeper==riid)
return m_pIUnkStdDisp->QueryInterface(IID_IDispatch, ppv);
if (NULL!=*ppv)
{
((LPUNKNOWN)*ppv)->AddRef();
return NOERROR;
}
return ResultFromScode(E_NOINTERFACE);
}
You might be slightly surprised to know that CreateStdDispatch also works with a dual interface implementation. The standard dispatch object still implements IDispatch for you, although in having a dual interface you'll still have your own set of IDispatch entry points. Those entry points, however, need do nothing more than delegate the call to the standard dispatch's IDispatch, which you query for from the IUnknown returned from CreateStdDispatch. After you obtain this pointer, you should release that original IUnknown to keep your outer object's reference count correct.