To illustrate how a dispinterface works and how it differs from a vtable interface, let's consider a simple object named Beeper. The object has one property, named Sound, and one method, named Beep. We can implement this object in C++ as follows, sharing this implementation with the rest of the world through OLE:
class CBeeper
{
public:
long m_lSound; //"Sound" property
public:
long Beep(void); //"Beep" method
};
To share CBeeper through a custom interface, we'd define something such as IBeeper, shown in the following code, and a pointer to this interface would provide access to six member functions, as shown in Figure 14-1.
interface IBeeper : IUnknown
{
long get_Sound(void);
void put_Sound(long lSound);
long Beep(void);
};
Figure 14-1.
Vtable binding of the IBeeper custom interface to CBeeper functionality.
The limitation of vtable interfaces such as IBeeper is that any client of this object must bind to the interface members on the basis of their locations in the vtable. A line of client source code such as pIBeeper->Beep() would be compiled into a call instruction to a specific offset from the value in pIBeeper. While this works great for compiled code, it's not as useful from an interpreted language. For the latter, we want to share the object through a late-bound dispinterface (with its own IID) whose properties are expressed directly (shown here in ODL syntax, as described in Chapter 3):
dispinterface DIBeeper
{
properties:
[id(0)] long Sound;
methods:
[id(1)] long Beep(void);
};
The Sound property is assigned a dispID of zero, and the Beep method is assigned a dispID of one. A controller (client) can use these dispIDs at run time to dispatch method calls and property manipulations to the real implementation of the object, as illustrated in Figure 14-2. Instead of accessing object services by calling a member function directly, a controller passes a dispID to some magic member of a dispinterface along with whatever parame-ters are needed for the property or method. That magic member then maps the dispID to the correct piece of implementation.
Figure 14-2.
Binding a dispinterface means mapping the dispID to the implementation.
The object must implement this generic mapping function as part of the early-bound IDispatch interface. Late binding comes from the fact that the actual method or property invoked is determined by arguments passed to the mapping function in this interface.