[Previous] [Contents] [Next]

Implementing IDispatch

The first step in any COM+ project is to define the interfaces in IDL. You use different IDL attributes depending on whether you are implementing a pure IDispatch interface or a dual interface. An interface based solely on IDispatch is called a dispinterface. The v-table of a dispinterface is identical to that of IDispatch itself. The methods and properties of the dispinterface are accessible only via the IDispatch::Invoke method. A dual interface is an IDispatch-based interface that also has v-table entries for the methods in the custom interface. This makes a dual interface accessible either via Automation or via the v-table.

Designing a Pure Dispinterface

You use the dispinterface statement shown here to design a pure IDispatch-based interface:

[ uuid(10000001-0000-0000-0000-000000000001) ]
dispinterface ISum
{
properties:
    [id(1)] int x;
    [id(2)] int y;

methods:
    [id(3)] int Sum(int x, int y);
};

The dispinterface statement is an alternative to the standard interface statement and has several interesting aspects. You do not need the object or the oleautomation attribute to indicate to MIDL that this interface uses only Automation-compatible types—this condition is obvious from the dispinterface statement. Notice that the dispinterface statement is not derived from IUnknown or IDispatch; dispinterface implicitly inherits the IDispatch interface.

The Sum method defined in the dispinterface statement returns an integer value rather than an HRESULT. Errors that occur during calls to methods in a dispinterface are returned by IDispatch::Invoke, thus freeing the method to return a real value. The method is assigned a unique number called a dispatch identifier, or DISPID. Notice that dispinterfaces can support properties as well as methods, while a regular interface supports only methods. This support is for the benefit of high-level languages, such as Visual Basic, which depend heavily on properties. Of course, all properties are actually implemented via the IDispatch::Invoke method.

Designing a Dual Interface

Using the dispinterface statement is not recommended because doing so restricts clients to using only the IDispatch interface to access an object. Instead of implementing a pure dispinterface, you can mark an object with the dual attribute. A coclass that sports a dual interface indicates that it allows clients to access its services via IDispatch or via direct access to its custom interface. Like other features of COM+, support for a dual interface sounds more complicated than it is. Figure 5-3 shows the v-table of a pure Automation-based component. The IDispatch interface is the only way to access the functionality of the object.

Click to view at full size.

Figure 5-3. The v-table of an object that implements a pure dispinterface.

With a dual interface, the v-table of the interface includes the IDispatch members plus any members of the custom interfaces supported by the object. The object shown in Figure 5-4 supports the ISum custom interface in addition to IDispatch. The last entry in the object's v-table is the Sum method from the ISum interface. In this case, a client can choose whether to access the object via Automation or via the custom ISum interface.

Click to view at full size.

Figure 5-4. An object that supports a dual interface, which includes a custom interface in addition to IDispatch.

Dual interfaces have been strongly hyped, but the advantages of supporting the IDispatch interface are limited. Since high-level environments such as Visual Basic and Microsoft Visual J++ can access custom interfaces directly, only scripting languages such as VBScript and JScript remain v-table-challenged; these scripting languages can access components only via the IDispatch interface. If you want developers working in a scripting language to access your component, IDispatch support is required.

Dual interfaces are one way to provide this functionality; by supporting a dual interface, you offer a choice to clients that need to access your component. Visual Basic, for example, always checks to see whether a component supports a dual interface. If it does, and if a type library describing the custom interface is available, Visual Basic uses the custom interface and completely bypasses Automation. If no type library is available or if the object supports only IDispatch, Visual Basic makes do with that interface.

One danger in using dual interfaces is that they force you to concentrate on issues relating to client access from a scripting language—not usually the primary market of a component. Focusing the design of an object on issues of client accessibility from scripting languages that don't have easy access to the IUnknown::QueryInterface method can lead you to develop an object that has only one interface with many methods. A one-interface-per-object design is not a good choice because you ignore the richness afforded by the QueryInterface method. Also, from a performance point of view, it can be quite limiting to restrict an interface to Automation-compatible types.

To get around the one-interface-per-object limitation, some developers have built hacks by which QueryInterface-style semantics can be built into a dispinterface or have suggested identity tricks that allow multiple IDispatch interfaces to be implemented by one object. However, none of these techniques has proved satisfactory in the long run. A better solution to the problems posed by dual interfaces is to simply not use them. Instead, you can use standard v-table interfaces and then design one additional IDispatch-based interface containing methods created specifically for use by scripting languages.5 The Automation interface might even present a somewhat simplified view of the object's functionality to ease its use by scripts.

Regardless of which technique you choose, dual interfaces give clients a choice of how to access the services of a component, and they also ensure that the object is accessible to scripting languages such as VBScript. The IDL syntax required to indicate support for both IDispatch and a custom interface is shown here:

[ object, uuid(10000001-0000-0000-0000-000000000001),
  dual ]
interface ISum : IDispatch
{
    [id(1)] HRESULT Sum(int x, int y, [out, retval] int* retval);
}

Notice that in the IDL fragment, the syntax seems more standard. Now instead of deriving from IUnknown, the ISum interface derives from IDispatch, which is itself derived from IUnknown, so we still get the QueryInterface, AddRef, and Release methods added to the v-table. The interface header also includes the dual attribute to indicate a dual interface. Specifying a dual interface implies that the interface is Automation-compatible and that therefore, in the type library generated by MIDL, the interface has both the TYPEFLAG_FDUAL and TYPEFLAG_FOLEAUTOMATION flags set. When a component containing this type library calls RegisterTypeLib, this flag is detected and the HKEY_CLASSES_ROOT\Interface\{YourIID}\ProxyStubClsid32 registry entry is automatically set to the CLSID of the Automation marshaler (oleaut32.dll).

In the following example, we'll create a dual interface by implementing the four IDispatch methods. The class declaration for CInsideCOM after it has been modified to implement the IDispatch interface is shown here.

class CInsideCOM : public ISum
{
public:
    // IUnknown
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    HRESULT __stdcall QueryInterface(REFIID iid, void** ppv);

    // IDispatch
    HRESULT __stdcall GetTypeInfoCount(UINT* pCountTypeInfo);
    HRESULT __stdcall GetTypeInfo(UINT iTypeInfo, LCID lcid, 
        ITypeInfo** ppITypeInfo);
    HRESULT __stdcall GetIDsOfNames(REFIID riid, 
        LPOLESTR* rgszNames, UINT cNames, LCID lcid, 
        DISPID* rgDispId);
    HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, 
        LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, 
        VARIANT* pVarResult, EXCEPINFO* pExcepInfo, 
        UINT* puArgErr);

    // ISum
    HRESULT __stdcall Sum(int x, int y, int* retval);

    CInsideCOM() : m_cRef(1) { g_cComponents++; }
    ~CInsideCOM() { g_cComponents--; }
    bool Init(void);

private:
    ULONG m_cRef;
    ITypeInfo* m_pTypeInfo;
};

Notice that the class appears to derive only from ISum and not from IDispatch. In the component.h header file generated by MIDL, the ISum class has already been declared as inheriting from IDispatch, as shown in boldface in the following code fragment from component.h:

// Generated by MIDL in component.h
MIDL_INTERFACE("10000001-0000-0000-0000-000000000001")
ISum : public IDispatch
{
public:
    virtual /* [id] */ HRESULT STDMETHODCALLTYPE Sum( 
        int x,
        int y,
        /* [retval][out] */ int __RPC_FAR *retval) = 0;
    
};

Implementation Techniques

You can implement the IDispatch interface in several ways. As with any other interface, you can implement IDispatch simply by writing the code for its four methods. This approach is quite difficult because of the complexity of the parameters that are passed to the IDispatch::Invoke method, the workhorse of the IDispatch interface. To simplify the process, Microsoft provides several helper functions, which are described in the table below.

These helper functions range from the primitive DispGetParam function, which simply helps to unpack VARIANT parameters from the DISPPARAMS array, to the CreateStdDispatch function, which in a single call provides a complete aggregatable implementation of the IDispatch interface. In between are the DispInvoke and DispGetIDsOfNames functions, both of which are trivial wrappers for the ITypeInfo::Invoke and ITypeInfo::GetIDsOfNames methods. You can use these functions to provide canned functionality for the IDispatch::Invoke and IDispatch::GetIDsOfNames methods.

IDispatch Helper Functions Description
DispGetIDsOfNames Automatic ITypeInfo-driven implementation of IDispatch::GetIDsOfNames.
DispGetParam Retrieves a parameter from the DISPPARAMS structure, checking both named parameters and positional parameters, and coerces the parameter to the specified type. Helps implement the IDispatch::Invoke method.
DispInvoke Automatic ITypeInfo-driven implementation of IDispatch::Invoke.
CreateStdDispatch Creates an instance of the standard ITypeInfo- driven IDispatch implementation.
CreateDispTypeInfo Constructs basic type information from an INTERFACEDATA description.

With this plethora of choices, it can be difficult to decide how to implement the IDispatch interface. If you're like us, you want to do the least amount of work to get a satisfactory implementation of IDispatch that will work with major client applications such as Visual Basic and scripting languages such as VBScript. The first determining factor is whether you intend to generate type information. The CreateStdDispatch, DispInvoke, and DispGetIDsOfNames helper functions all require type information in order to work their magic.

Of course, the easiest way to create type information is to run your IDL file through the MIDL compiler. Or you can call the CreateDispTypeInfo helper function to generate basic type information at run time. CreateDispTypeInfo requires that you fill out the INTERFACEDATA, METHODDATA, and PARAMDATA structures (shown in Figure 5-5) to describe every parameter of every method in the interface. Using MIDL to create type information is so easy, however, that the CreateDispTypeInfo function should only be used by specialized applications that require type information to be generated at run time.

Click to view at full size.

Figure 5-5. The INTERFACEDATA, METHODDATA, and PARAMDATA structures used by the CreateDispTypeInfo helper function.

The CreateStdDispatch Function

The primary drawback to using CreateStdDispatch is that it supports only one national language. Even so, CreateStdDispatch is a good choice for components that are not hobbled by this restriction. To aggregate the standard dispatch object, you simply call the CreateStdDispatch function on startup, as shown in boldface in the following code:

HRESULT CInsideCOM::Init(void)
{
    ITypeLib* pTypeLib;
    if(FAILED(LoadRegTypeLib(LIBID_Component, 1, 0, 
        LANG_NEUTRAL, &pTypeLib)))
        return E_FAIL;
    HRESULT hr = 
        pTypeLib->GetTypeInfoOfGuid(IID_ISum, &m_pTypeInfo);
    pTypeLib->Release();
    if(FAILED(hr))
        return hr;

    return CreateStdDispatch(this, this, m_pTypeInfo, 
        &m_pUnknownStdDisp);
}

When a client requests the IDispatch interface, you delegate the QueryInterface call to the dispatch object so the client application gets the standard dispatch object's IDispatch implementation, as shown in boldface in the code below. You should also release the standard dispatch object before destroying the InsideCOM object.

HRESULT CInsideCOM::QueryInterface(REFIID riid, void** ppv)
{
    if(riid == IID_IUnknown)
        *ppv = (IUnknown*)this;
    else if(riid == IID_ISum)
        *ppv = (ISum*)this;
    else if(riid == IID_IDispatch)
        return m_pUnknownStdDisp->
            QueryInterface(IID_IDispatch, ppv);
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}

The IDispatch::GetTypeInfoCount Method

If you opt for a slightly more hands-on approach to implementing the IDispatch interface, you'll probably decide to implement the four IDispatch methods yourself but delegate some of the more arduous work to COM+ helper functions. Here's how you do it. The IDispatch::GetTypeInfoCount function is called by the client to determine whether type information is available for the object. If the object provides type information, the integer pointed to by pCountTypeInfo should be set to 1; otherwise, it should be set to 0. Clients that want to determine whether to use early binding (which requires type information) or late binding when accessing the Automation component can call this function. A typical implementation of GetTypeInfoCount is shown here.

HRESULT CInsideCOM::GetTypeInfoCount(UINT* pCountTypeInfo)
{
    *pCountTypeInfo = 1;
    return S_OK;
}

Implementing IDispatch using this approach requires type information. The component shown in the following code loads type information from the type library using the LoadRegTypeLib function. This function is invoked from the custom Init method called during object instantiation. Next the ITypeLib:: GetTypeInfoOfGuid method is called to retrieve the type information for the ISum interface. A pointer to this type information is stored in the member variable m_pTypeInfo for use by other methods of the IDispatch interface.

bool CInsideCOM::Init(void)
{
    // Load the type library.
    ITypeLib* pTypeLib;
    if(FAILED(LoadRegTypeLib(LIBID_Component, 1, 0, 
        LANG_NEUTRAL, &pTypeLib)))
        return false;

    // Get the type info.
    HRESULT hr = 
        pTypeLib->GetTypeInfoOfGuid(IID_ISum, &m_pTypeInfo);
    if(FAILED(hr))
        return false;

    // Release the type library.
    pTypeLib->Release();
    return true;
}

The IDispatch::GetTypeInfo Method

After determining that type information is available using the GetTypeInfoCount method, a client might call IDispatch::GetTypeInfo, as shown in the following code. This method returns a pointer to the type information provided by the object, which can then be used to get the type information for an interface. The first parameter of the GetTypeInfo method, iTypeInfo, specifies the index number of the interface for which the client is requesting type information. Since this is an IDispatch interface, only the index number 0, which refers to IDispatch, is valid. If the client passes any other value, we simply return DISP_E_BADINDEX. The second parameter of GetTypeInfo, lcid, is a locale identifier. This parameter is important because some objects might want to return different type information based on the user's national language. Since our object does not support localized member names, this parameter is ignored. Before returning, AddRef must be called on the type information pointer in accordance with the reference counting rules in COM+.

HRESULT CInsideCOM::GetTypeInfo(UINT iTypeInfo, LCID lcid, 
    ITypeInfo** ppITypeInfo)
{
    *ppITypeInfo = NULL;
    if(iTypeInfo != 0)
        return DISP_E_BADINDEX;
    m_pTypeInfo->AddRef();
    *ppITypeInfo = m_pTypeInfo;
    return S_OK;
}

The IDispatch::GetIDsOfNames Method

IDispatch::GetIDsOfNames is called by a client that has a method name (Sum, for example) and wants to get the DISPID associated with that method to call it via IDispatch::Invoke. The first parameter, riid, is unused and must be IID_NULL. The second parameter, rgszNames, points to an array of names for which the client is requesting DISPIDs. The third parameter, cNames, tells the component the number of names in the array, and the fourth parameter, lcid, specifies the locale identifier of the caller. Only one method or property can be mapped to a DISPID at a time. The other members of the array can be used to map the parameter names of methods into DISPIDs. This technique can be useful for sophisticated clients such as Visual Basic that allow developers to specify values for named arguments when calling methods.

The last parameter, rgDispId, is a pointer to an array in which the client wants to receive the requested DISPIDs. The implementation of IDispatch::GetIDsOfNames shown in the following code simply delegates to the DispGetIDsOfNames helper function, which does the actual work of mapping the requested names to DISPIDs based on the information in the type library. Of course, you could implement this functionality yourself, but with a function as easy to use as DispGetIDsOfNames, who would want to?

HRESULT CInsideCOM::GetIDsOfNames(REFIID riid, 
    LPOLESTR* rgszNames, UINT cNames, LCID lcid, 
    DISPID* rgDispId)
{
    if(riid != IID_NULL)
        return DISP_E_UNKNOWNINTERFACE;
    return DispGetIDsOfNames(m_pTypeInfo, rgszNames, cNames, 
        rgDispId);
}

The IDispatch::Invoke Method

IDispatch::Invoke is used when the client is finally ready to call a method. The first parameter, dispIdMember, specifies the DISPID of the member you are invoking. This value can be obtained from a previous call to IDispatch::GetIDsOfNames. The second parameter, riid, is reserved and must be IID_NULL. The third parameter, lcid, specifies the locale identifier (LCID) of the client, and once more we will politely ignore this information. The fourth parameter, wFlags, can be chosen from the set of flags listed in the table below.

IDispatch::Invoke wFlags Description
DISPATCH_METHOD The member is invoked as a method. If a property has the same name, both this flag and the DISPATCH_PROPERTYGET flag can be set.
DISPATCH_PROPERTYGET The member is retrieved as a property or data member.
DISPATCH_PROPERTYPUT The member is changed as a property or data member.
DISPATCH_PROPERTYPUTREF The member is changed by a reference assignment rather than by a value assignment. This flag is valid only when the property accepts a reference to an object.

The fifth parameter, pDispParams, is a pointer to a DISPPARAMS structure containing the actual parameters to be passed to this method. The sixth parameter, pVarResult, is a pointer to the return value that the client expects to receive after the completion of the method. The last two parameters, pExcepInfo and puArgErr, return extended error information to the caller. The EXCEPINFO structure, which is shown below, should be filled in by the IDispatch::Invoke method when an error occurs. If the HRESULT return code of the Invoke method is DISP_E_EXCEPTION, the client can retrieve the exception information pointed to by the pExcepInfo parameter.

typedef struct tagEXCEPINFO {
    WORD  wCode;             // An error code describing 
                             //   the error
    WORD  wReserved;         // Should be 0
    BSTR  bstrSource;        // The source of the exception
    BSTR  bstrDescription;   // A description of the error
    BSTR  bstrHelpFile;      // Fully qualified filename for the
                             //   help file
    DWORD dwHelpContext;     // Help context ID of topic within 
                             //   the help file
    ULONG pvReserved;        // Must be NULL
    ULONG pfnDeferredFillIn; // Pointer to a function for 
                             //   deferred fill in
    SCODE scode;             // Should be 0
} EXCEPINFO;

This technique works acceptably when you build a completely custom implementation of the IDispatch::Invoke method. If, however, you use the DispInvoke or CreateStdDispatch helper function to implement the Invoke method, client calls are automatically routed to the appropriate methods. These methods (the ISum::Sum methods) do not have a pExcepInfo parameter and therefore cannot take advantage of the Automation-based exception handling described here. The solution to this conundrum lies in more standard exception-handling techniques offered by COM+; exceptions are covered in Chapter 6. Basically, the solution is to use standard COM+ exception-handling techniques to generate the exception. The DispInvoke (or CreateStdDispatch) helper function then obtains this error information and automatically plugs it into the pExcepInfo parameter.

The puArgErr parameter helps the client application identify the first argument that has an error when the IDispatch::Invoke method returns DISP_E_TYPEMISMATCH or DISP_E_PARAMNOTFOUND. Since arguments are stored in pDispParams->rgvarg in reverse order, the first argument is the one with the highest index in the array.

The following code shows a simple implementation of IDispatch::Invoke that defaults to DispInvoke, another helper function provided in COM+. By deferring to the ITypeInfo::Invoke method, DispInvoke builds a stack frame, coerces parameters using the standard type conversion functions, pushes them onto the stack, and then calls the correct member function in the v-table. Keep in mind that you can use the CreateStdDispatch and DispInvoke helper functions only for dual interfaces. Pure dispinterfaces do not have method entries in their v-table and thus cannot make use of the services offered by DispInvoke. Components that implement a pure dispinterface implement IDispatch::Invoke by examining the dispIdMember parameter and directly calling the corresponding function.

HRESULT CInsideCOM::Invoke(DISPID dispIdMember, REFIID riid, 
    LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, 
    VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
    if(riid != IID_NULL)
        return DISP_E_UNKNOWNINTERFACE;
    return DispInvoke(this, m_pTypeInfo, dispIdMember, wFlags, 
        pDispParams, pVarResult, pExcepInfo, puArgErr); 
}

A few minor changes remain to be made to the component's code. You must modify the object's IUnknown::Release method to release the type information before you exit, as shown here:

ULONG CInsideCOM::Release()
{
    if(--m_cRef != 0)
        return m_cRef;
    m_pTypeInfo->Release();
    delete this;
    return 0;
}

The IUnknown::QueryInterface function must be improved, as shown in the following code, so that clients requesting the IDispatch interface receive a valid pointer instead of being turned away with the E_NOINTERFACE return value:

HRESULT CInsideCOM::QueryInterface(REFIID riid, void** ppv)
{
    if(riid == IID_IUnknown)
        *ppv = (IUnknown*)this;
    else if(riid == IID_ISum)
        *ppv = (ISum*)this;
    else if(riid == IID_IDispatch)
        *ppv = (IDispatch*)this;
    else 
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
    AddRef();
    return S_OK;
}

Also, in the component's IClassFactory::CreateInstance method, the Init function must be called, as shown in the following code, to ensure that the type information is loaded from the type library:

HRESULT CFactory::CreateInstance(IUnknown *pUnknownOuter, 
    REFIID iid, void** ppv)
{
    if(pUnknownOuter != NULL)
        return CLASS_E_NOAGGREGATION;

    CInsideCOM *pInsideCOM = new CInsideCOM;
    if(pInsideCOM == NULL)
        return E_OUTOFMEMORY;

    // Call the Init method to load the type information.
    pInsideCOM->Init();
    HRESULT hr = pInsideCOM->QueryInterface(iid, ppv);
    pInsideCOM->Release();
    return hr;
}

Since the InsideCOM object implements a dual interface, you can test the component using any of the standard client programs we built in previous chapters. To test the Automation capabilities of the component, you must build a new client that uses the IDispatch interface.

Properties

Although interfaces are made up of methods, Visual Basic distinguishes among method types, calling some properties and others events. Designing an IDispatch-based interface that contains properties is not difficult. To define properties, IDL provides the propget, propput, and propputref attributes. The last parameter of a method defined with the propget attribute must be a pointer type declared with the out and retval attributes. The last parameter of a method defined with the propput attribute must be an in parameter. For example, the IDL interface definition shown below contains two properties named x and y:

[ object, uuid(10000001-0000-0000-0000-000000000001), dual ]
interface ISum : IDispatch
{
    // The Sum method, now with optional parameters and 
    // default values
    [id(1)] HRESULT Sum([optional, defaultvalue(-1)] int x, 
        [optional, defaultvalue(-1)] int y, 
        [out, retval] int* retvalue);

    // The x property
    [id(2), propget] HRESULT x([out, retval] int* retvalue);
    [id(2), propput] HRESULT x(int newvalue);

    // The y property
    [id(3), propget] HRESULT y([out, retval] int* retvalue);
    [id(3), propput] HRESULT y(int newvalue);
}

The interesting aspect of defining properties is that each property is defined as one, two, or even three distinct methods, depending on the property type. Standard properties are defined as a combination of two methods with the same name and DISPID; one method retrieves the property value, and the other sets it. The code fragment below shows the two methods used to implement the x property in C++. The name of every propget method is prefixed with get_, and the name of every propput method is prefixed with put_. Properties defined with the propputref attribute accept pointers and are implemented in methods prefixed with putref_.

// The propget method
HRESULT CInsideCOM::get_x(int* retvalue)
{
    *retvalue = m_x;
    return S_OK;
}

// The propput method
HRESULT CInsideCOM::put_x(int newvalue)
{
    m_x = newvalue;
    return S_OK;
}

Notice that in the ISum interface definition shown previously, the parameters of the Sum method were modified with the optional and defaultvalue attributes. The optional attribute indicates that the Sum method can be called without parameters; the defaultvalue attribute specifies the value to use when a client calls the Sum method without parameters. To adjust for this new definition of the Sum method, we'll modify the C++ implementation to check for the default values and use the property values instead:

HRESULT CInsideCOM::Sum(int x, int y, int* retval)
{
    // If the client did not specify parameters, then use 
    // the property values.
    if(x == -1 && y == -1)
        *retval = m_x + m_y;
    else
        // Otherwise just add the numbers as usual.
        *retval = x + y;
    return S_OK;
}

Collections

In addition to supporting standard coclasses, Visual Basic also provides special support for a unique type of coclass called a collection. A collection is basically a Visual Basic-friendly technique for exposing an enumerator object. (Enumerator objects are described in Chapter 8.) Although a collection looks like an array, it is usually implemented internally as a linked-list data structure. This allows for items to be easily added and removed from anywhere in a collection. In addition, the For Each...Next statement in Visual Basic is a special language construct designed to work with collection objects. Collection objects generally expose several standard methods and properties, as described in the table below.

Member Description Required?
Add method Adds an item to the collection No
Count property Read-only property that retrieves the number of items in the collection Yes
Item method Retrieves a specific item from the collection Yes
_NewEnum property Hidden read-only property that returns a pointer to the enumerator object that implements the IEnumVARIANT interface Yes
Remove method Removes an item in the collection No

Of the items described in the table, the _NewEnum property is the most interesting. When you create a collection object, the get__NewEnum method that implements this _NewEnum property must return an IUnknown pointer to an enumerator object that implements the IEnumVARIANT interface. Visual Basic then queries for the IEnumVARIANT interface and uses the implementation of that interface to iterate the collection as required by the For Each…Next statement. The IEnumVARIANT interface follows the pattern of standard COM+ enumeration interfaces for VARIANT data types; the interface definition is shown below in IDL notation:

interface IEnumVARIANT : IUnknown
{
    // Return the next 0 or more items.
    HRESULT Next([in] ULONG celt, 
        [out, size_is(celt), length_is(*pCeltFetched)] 
            VARIANT* rgVar, 
        [out] ULONG* pCeltFetched);

    // Skip the next 0 or more items.
    HRESULT Skip([in] ULONG celt);

    // Reset the enumerator.
    HRESULT Reset();

    // Create a new enumerator with the identical state.
    HRESULT Clone([out] IEnumVARIANT ** ppEnum);
}

Because the _NewEnum property is designed for use by Visual Basic only, it must be hidden from the programmer. To achieve this bit of subterfuge, you use the restricted IDL attribute to indicate that this property is not to be displayed to the user. Visual Basic also passes a special DISPID defined for the _NewEnum property, DISPID_NEWENUM (-4), to IDispatch::Invoke when it requests an enumerator object. These attributes are shown below in the IDL definition of the INumbers interface, a sample interface implemented by a coclass that defines a collection object:

[ object, uuid(10000001-0001-0000-0000-000000000001), dual ]
interface INumbers : IDispatch
{
    [propget, id(DISPID_NEWENUM), restricted] HRESULT _NewEnum(
        [out, retval] IUnknown** pVal);
    [propget] HRESULT Count([out, retval] long *pVal);
    [propget, id(DISPID_VALUE)] HRESULT Item(long index, 
        [out, retval] long* pVal);
    HRESULT Add(long Val);
    HRESULT Remove(long index);
}

We won't bore you with the details of a linked-list implementation of the INumbers interface or the enumerator object that implements IEnumVARIANT.6 You will, however, be pleasantly surprised by the client-side Visual Basic code you can write that uses this collection object:

Dim Numbers As New InsideCOM

Print Numbers.Count    ' Prints 0

' Add items to the collection.
Numbers.Add 5
Numbers.Add 10
Numbers.Add 15
Numbers.Add 20
Numbers.Add 25

Print Numbers.Count    ' Prints 5

' Remove items from the collection by index.
Numbers.Remove 2       ' Removes 10
Numbers.Remove 3       ' Removes 20

Print Numbers.Count    ' Prints 3

Dim Number As Variant

' Iterate through the collection and print each number (5, 15, 25).
For Each Number In Numbers
    Print "Number in the Numbers collection: " & Number
Next Number

The (New and Improved) IDispatchEx Interface

Recently, the IDispatch interface was extended through the definition of a new interface named IDispatchEx. The IDispatchEx interface derives from IDispatch, as you can see in the IDL definition below. In addition to the inherited IDispatch methods, IDispatchEx offers seven new methods that support the creation of dynamic objects (sometimes called "expando" objects) in which methods and properties can be added and removed at run time. In addition, unused parameters in the methods of IDispatch have been removed from IDispatchEx. For example, the unused interface identifier parameter passed to the IDispatch::Invoke and IDispatch::GetIDsOfNames methods has been removed from their respective methods in IDispatchEx (IDispatchEx::InvokeEx and IDispatchEx::GetDispID).7

interface IDispatchEx : IDispatch
{
    // Add a new member or get one DispID.
    HRESULT GetDispID(
        [in] BSTR bstrName,
        [in] DWORD grfdex,
        [out] DISPID *pid);

    // Invoke a member using its DispID.
    HRESULT InvokeEx(
        [in] DISPID id,
        [in] LCID lcid,
        [in] WORD wFlags,
        [in] DISPPARAMS *pdp,
        [out] VARIANT *pvarRes,
        [out] EXCEPINFO *pei,
        [in, unique] IServiceProvider *pspCaller);

    // Remove a member using its name.
    HRESULT DeleteMemberByName([in] BSTR bstrName, 
        [in] DWORD grfdex);

    // Remove a member using its DispID.
    HRESULT DeleteMemberByDispID([in] DISPID id);

    // Member type information is available here.
    HRESULT GetMemberProperties(
        [in] DISPID id,
        [in] DWORD grfdexFetch,
        [out] DWORD *pgrfdex);

    // Get the name of a member based on its DispID.
    HRESULT GetMemberName(
        [in] DISPID id,
        [out] BSTR *pbstrName);

    // Enumerate the DispIDs for the object.
    HRESULT GetNextDispID(
        [in] DWORD grfdex,
        [in] DISPID id,
        [out] DISPID *pid);

    // Some languages support namespaces.
    HRESULT GetNameSpaceParent([out] IUnknown **ppunk);
};

The primary limitation of the IDispatch interface is that it assumes objects are static. This means that the methods and properties offered by the object do not change at run time. While custom v-table based interfaces are required to offer a constant set of members, IDispatchEx-based interfaces are not. Instead, IDispatchEx lets client applications dynamically add methods and properties at run time that are not described by the static information in a type library. In fact, you can even use the IDispatch interface in this dynamic way, although the documentation seems to prohibit this by stating that "the member and parameter DISPIDs must remain constant for the lifetime of the object." One could argue, however, that merely adding members to an object, as long as the assigned DISPIDs remain valid, does not violate this rule.

One example of a component that makes use of this gray area is ActiveX Data Objects (ADO). By setting the Name property of a Command object, you enable that name to be used as a method of the Connection object to execute the command, as shown in the code fragment below. The fascinating thing about this code sample is that the Hello method (shown in boldface) does not exist in the ADO type library, yet Visual Basic calls it without complaint. This becomes even more interesting when you realize that the Connection object is not accessed via IDispatch; it is declared As Connection—not As Object. The explanation for this riddle is that in the case of dual interfaces, Visual Basic lets you access methods and properties that are not in the type library. For these unknown elements, Visual Basic has no choice but to resort to the IDispatch interface—even if the object was not declared As Object.

' Set up the connection (not using IDispatch).
Dim cn As New Connection
cn.Open _
    "Provider=SQLOLEDB;Server=DatabaseServer;Database=Pubs;", _
    "sa"

' Create a command named Hello.
Dim cd As New Command
cd.Name = "Hello"
cd.CommandText = "select * from authors"
cd.ActiveConnection = cn

' Set up the recordset.
Dim rs As New Recordset

' Execute the Hello command as a method of the connection.
' Use IDispatch::GetIDsOfNames and IDispatch::Invoke because 
' Hello is not in the type library
cn.Hello rs

' Display the records returned.
Do While Not rs.EOF
    Print rs(0), rs(1), rs(2)
    rs.MoveNext
Loop

Even though ADO proves that you can use the IDispatch interface to create dynamically expanding objects, IDispatch was definitely not designed to be used in this way. For example, you cannot delete members or get information about the nature of a member created at run time. The IDispatchEx interface was designed to formalize the idea of expandable objects and therefore adds methods to help support dynamic objects that might change at run time. Scripting languages such as VBScript and JScript offer more dynamic run-time models and therefore require a more flexible interface. The primary functionality offered by the new methods defined in IDispatchEx relates to adding, removing, and searching for members of an IDispatchEx-based interface.

Implementing IDispatchEx

Implementing the IDispatchEx interface is complicated because helper functions such as DispInvoke and DispGetIDsOfNames aren't provided for the methods of IDispatchEx. Remember that the IDispatch helper functions are based on type information, but since most objects that implement IDispatchEx generally do not update their type information at run time, helper functions that operate based on type information are not available. Since every object that implements IDispatchEx must also implement the methods of IDispatch, one relatively straightforward way to implement IDispatchEx is to delegate some IDispatchEx methods to their respective IDispatch methods. This works because most expandable objects have a steady core of methods defined by the interface's designer, on top of which the dynamic elements are added at run time.

For example, the simple implementation of the IDispatchEx::GetDispID method shown below delegates the call to IDispatch::GetIDsOfNames. Only if the IDispatch::GetIDsOfNames call fails with the return code DISP_E_UNKNOWNNAME does the IDispatchEx::GetDispID do any work. It first checks to see if the element name passed in by the client application is valid. In this case, a string comparison is done with a private member variable named m_newsum. If the bstrName string passed in by the client matches the m_newsum string, a new DISPID is returned to the client. In the sample implementation of IDispatchEx::GetDispID, the value of the DISPID returned has been hard-coded to 17.8

HRESULT CInsideCOM::GetDispID(BSTR bstrName, DWORD grfdex, 
    DISPID* pid)

{
    // Try GetIDsOfNames. If that works, simply return.
    HRESULT hr = GetIDsOfNames(IID_NULL, &bstrName, 1, 
        LOCALE_USER_DEFAULT, pid);

    // If GetIDsOfNames failed, then check the element name.
    if(hr == DISP_E_UNKNOWNNAME)

        // If the name is valid, assign a unique DispID 
        // and return.
        if(wcscmp(bstrName, m_newsum) == 0)
        {
            *pid = 17;
            return S_OK;
        }
    return hr;
}

The m_newsum variable, declared as a BSTR, is obtained from the client in the ISum::CreateNewSum method shown below. This method is used by client applications to define new names for the ISum::Sum method. Note that no custom creation method is required to add elements to an object at run time. Instead, the client application can call the IDispatchEx::GetDispID method with the fdexNameEnsure flag in the second parameter to indicate that a new element is being added.

HRESULT CInsideCOM::CreateNewSum(BSTR name)
{
    m_newsum = SysAllocString(name);
    return S_OK;
}

The heart of the IDispatchEx interface is the InvokeEx method. Although it is not too different from its cousin the IDispatch::Invoke method, InvokeEx is the method called by the client to invoke elements created at run time. In the sample implementation of IDispatchEx::InvokeEx shown here, the code first attempts to delegate the work to the DispInvoke helper function. If that succeeds, type information is available for the element invoked by the client and no special code is needed. If DispInvoke fails with the return code DISP_E_MEMBERNOTFOUND, no type information is available and so the client might request that an element be added at run time. The DISPID parameter is then checked, and if the value is 17 (the value returned by GetDispID), we know that the client is invoking the dynamic element. Since in this example the client request is only another name for the Sum method, the DispInvoke function is called with a DISPID of 1 (the DISPID of the Sum method).

HRESULT CInsideCOM::InvokeEx(DISPID id, LCID lcid, WORD wFlags, 
    DISPPARAMS* pdp, VARIANT* pvarRes, EXCEPINFO* pei, 
    IServiceProvider* pspCaller)
{
    ' Delegate to DispInvoke.
    HRESULT hr = DispInvoke(this, m_pTypeInfo, id, wFlags, pdp, 
        pvarRes, pei, NULL);

    ' If that failed, then check if the DISPID is 17.
    if(hr == DISP_E_MEMBERNOTFOUND)
        if(id == 17)
        {
            ' Yes, so invoke the Sum method (DISPID of 1).
            HRESULT hr = DispInvoke(this, m_pTypeInfo, 1, 
                wFlags, pdp, pvarRes, pei, NULL);
            return hr;
        }
    return hr;
}

The result of all this effort to create a dynamic object by implementing the IDispatchEx interface is that methods can be added to the InsideCOM coclass at run time. For example, the following VBScript code can be executed by the Windows Scripting Host. The Hurray method is added and then called using a reference to an InsideCOM object. The actual invocation of the Hurray method causes the Windows Scripting Host to get a pointer to the IDispatchEx implementation of the InsideCOM object using QueryInterface and then call IDispatchEx::GetDispID to get the DISPID of the Hurray method. Finally the Hurray method is invoked by calling IDispatchEx::InvokeEx, which eventually delegates the call to the Sum method.

Dim myRef
Set MyRef = CreateObject("Component.InsideCOM")
MsgBox "Sum(5, 3) = " & myRef.Sum(5, 3)

' Create a new method on the object.
myRef.CreateNewSum "Hurray"

' Execute the method added at run time.
' Calls IDispatchEx::GetDispID followed by 
' IDispatchEx::InvokeEx.
MsgBox "Hurray(2, 8) = " & myRef.Hurray(2, 8)