Variation IV: A Dual Interface

Both Beeper2 and Beeper3 have a dispinterface and a custom interface that provide exactly the same methods and properties. It makes sense to combine them into a dual interface, which we'll do in the Beeper4 sample (CHAP14\BEEPER4).

For the most part, we make a dual interface by pulling the IDispatch member functions that are part of CImpIDispatch in the previous Beepers and making them part of CBeeper along with all the IBeeper functions. (This includes pulling Exception straight from Beeper3 with all the TLS handling intact.) Most of the code in Beeper4's BEEPER.H file looks the same, but it's been reorganized a little and is slightly different in a few ways as well.

The first trick with a dual interface is to define the beast in your ODL file. This step is a little more concise than defining an interface and a dispinterface separately, as shown in the following:


[Attributes]
library BeeperTypeLibrary
{
§

[..., dual]
interface IBeeper : IDispatch
{
//Properties
[propget, helpstring("The current sound")]
HRESULT Sound([out, retval] long *plSound);

[propput]
HRESULT Sound([in] long lSound);

//Methods
[helpstring("Play the current sound")]
HRESULT Beep([out, retval] long *plSoundPlayed);
}

§
}

The interesting feature of a dual interface is that it is a custom interface and a dispinterface at the same time because the dual interface is polymorphic with IDispatch. You'll also notice that our coclass refers only to IBeeper now because DIBeeper no longer exists.

In making IBeeper a dual interface, however, the most significant change is that all of the member functions are now required to return an HRESULT for OLE's automatic marshaling support to work properly. This means that for methods and property get functions to return a value, they have to declare an extra argument with the attributes out and retval, which becomes, in effect, the actual return value. The retval attribute can apply to only one argument, of course, because it implies a return value from a property or a method.

The function signatures in your ODL file will also end up in a header generated by MKTYPLIB, so the implementations of your custom interface functions will change as well. These are minor changes, however, as you can see in Beeper4's version of get_Sound, put_Sound, and Beep:


STDMETHODIMP CBeeper::get_Sound(long *plSound)
{
if (NULL==plSound)
return ResultFromScode(E_POINTER);

*plSound=m_lSound;
return NOERROR;
}

STDMETHODIMP CBeeper::put_Sound(long lSound)
{
if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
&& MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
&& MB_ICONASTERISK!=lSound)
{
Exception(EXCEPTION_INVALIDSOUND);
return ResultFromScode(DISP_E_EXCEPTION);
}

m_lSound=lSound;
return NOERROR;
}

STDMETHODIMP CBeeper::Beep(long *plSoundPlayed)
{
if (NULL==plSound)
return ResultFromScode(E_POINTER);

*plSound=m_lSound;
MessageBeep((UINT)m_lSound);
return NOERROR;
}

The only other point to make about dual interfaces is that you might want to improve the performance of the marshaling of the custom portion of the dual interface. To do this, you can create your own proxy/stub object server DLL, as described in Chapter 6, and register that DLL under your dual interface's IID in the Interface section of the registry. You must list IDispatch, IID {00020400-0000-0000-C000-000000000046}, as your BaseInterface so that OLE will use its own marshaling for the IDispatch portion and instantiate your proxy/stub objects for the remaining member functions. And just as a reminder, all dual interfaces must be completely automation compatible. Even if you provide your own proxy/stub objects, the dispinterface portion must always be compatible.

12 At the time of writing, ITypeInfo::Invoke never asks for this interface, let alone calls it: Invoke simply calls GetErrorObject, which could potentially report bogus exceptions if you don't call SetErrorObject(0L, NULL) and if something else in your thread calls SetErrorObject at some point with other garbage.