Accessing Members Through IDispatch

To bind to exposed objects at run time, use the IDispatch interface.

    To create an ActiveX client using IDispatch
  1. Initialize OLE.
  2. Create an instance of the object you want to access. The object's ActiveX component creates the object.
  3. Obtain a reference to the object's IDispatch interface (if it has implemented one).
  4. Manipulate the object through the methods and properties exposed in its IDispatch interface.
  5. Terminate the object by invoking the appropriate method in its IDispatch interface, or by releasing all references to the object.
  6. Uninitialize OLE.

The following table shows the minimum set of functions necessary to manipulate a remote object.

Function Purpose Interface
OleInitialize Initializes OLE. OLE API function
CoCreateInstance Creates an instance of the class represented by the specified CLSID, and returns a pointer to the object's IUnknown interface. Component object API function
QueryInterface Checks whether IDispatch has been implemented for the object. If so, returns a pointer to the IDispatch implementation. IUnknown
GetIDsOfNames Returns dispatch identifiers (DISPIDs) for properties and methods and their parameters. IDispatch
Invoke Invokes a method, or sets or gets a property of the remote object. IDispatch
Release Decrements the reference count for an IUnknown or IDispatch object. IUnknown
OleUninitialize Uninitializes OLE. OLE API function

The code that follows is extracted from a generalized Windows-based ActiveX client. The controller relies on helper functions provided in the file Invhelp.cpp, which is available in the Browse directory of the samples. Error checking is omitted to save space, but would normally be used where an HRESULT is returned.

The two functions that follow initialize OLE, and then create an instance of an object and get a pointer to the object's IDispatch interface (Invhelp.cpp):

BOOL InitOle(void)
{
    if(OleInitialize(NULL) != 0)
        return FALSE;

    return TRUE;
}
HRESULT CreateObject(LPSTR pszProgID, IDispatch FAR* FAR* ppdisp)
{
    CLSID clsid;                  // CLSID of ActiveX object.
    HRESULT hr;
    LPUNKNOWN punk = NULL;        // IUnknown of ActiveX object.
    LPDISPATCH pdisp = NULL;      // IDispatch of ActiveX object.

    *ppdisp = NULL;

    // Retrieve CLSID from the ProgID that the user specified.
    hr = CLSIDFromProgID(pszProgID, &clsid);
    if (FAILED(hr))
        goto error;

    // Create an instance of the ActiveX object and ask for the
    // IDispatch interface.
    hr = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, 
                            IID_IUnknown, (void FAR* FAR*)&punk);
    if (FAILED(hr))
        goto error;

    hr = punk->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
    if (FAILED(hr))
        goto error;

    *ppdisp = pdisp;
    punk->Release();
    return NOERROR;
    
error:
    if (punk) punk->Release();
    if (pdisp) pdisp->Release();
    return hr;
}

The CreateObject function is passed a ProgID and returns a pointer to the IDispatch implementation of the specified object. CreateObject calls the OLE API CLSIDFromProgID to get the CLSID that corresponds to the requested object, and then passes the CLSID to CoCreateInstance to create an instance of the object and get a pointer to the object's IUnknown interface. (The CLSIDFromProgID function is described in the OLE Programmer's Reference.) With this pointer, CreateObject calls IUnknown::QueryInterface, specifying IID_IDispatch, to get a pointer to the object's IDispatch interface.

HRESULT FAR
Invoke(LPDISPATCH pdisp,
    WORD wFlags,
    LPVARIANT pvRet,
    EXCEPINFO FAR* pexcepinfo,
    UINT FAR* pnArgErr,
    LPSTR pszName,
    char *pszFmt,
    ...)
{
    va_list argList;
    va_start(argList, pszFmt);
    DISPID dispid;
    HRESULT hr;
    VARIANTARG* pvarg = NULL;

    if (pdisp == NULL)
        return ResultFromScode(E_INVALIDARG);

    // Get DISPID of property/method.
    hr = pdisp->GetIDsOfNames(IID_NULL, &pszName, 1,
        LOCALE_SYSTEM_DEFAULT, &dispid);
    if(FAILED(hr))
        return hr;

    DISPPARAMS dispparams;
    _fmemset(&dispparams, 0, sizeof dispparams);

    // Determine number of arguments.
    if (pszFmt != NULL)
        CountArgsInFormat(pszFmt, &dispparams.cArgs);

    // Property puts have a named argument that represents the value
    // being assigned to the property.
    DISPID dispidNamed = DISPID_PROPERTYPUT;
    if (wFlags & DISPATCH_PROPERTYPUT)
    {
        if (dispparams.cArgs == 0)
            return ResultFromScode(E_INVALIDARG);
        dispparams.cNamedArgs = 1;
        dispparams.rgdispidNamedArgs = &dispidNamed;
    }
    if (dispparams.cArgs != 0)
    {
        // Allocate memory for all VARIANTARG parameters.
        pvarg = new VARIANTARG[dispparams.cArgs];
        if(pvarg == NULL)
            return ResultFromScode(E_OUTOFMEMORY);
        dispparams.rgvarg = pvarg;
        _fmemset(pvarg, 0, sizeof(VARIANTARG) * dispparams.cArgs);

        // Get ready to traverse the vararg list.
        LPSTR psz = pszFmt;
        pvarg += dispparams.cArgs - 1;   // Params go in opposite order.

        while (psz = GetNextVarType(psz, &pvarg->vt))
        {
            if (pvarg < dispparams.rgvarg)
            {
                hr = ResultFromScode(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;
            // Additional cases omitted to save space.
            default:
                {
                    hr = ResultFromScode(E_INVALIDARG);
                    goto cleanup;  
                }
                break;
            }
            --pvarg;             // Get ready to fill next argument.
        } //while
    } //if

    // Initialize return variant, in case caller forgot. Caller can pass
    // Null if no return value is expected.
    if (pvRet)
        VariantInit(pvRet);
    // Make the call.
    hr = pdisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, wFlags,
        &dispparams, pvRet, pexcepinfo, pnArgErr);

cleanup:
    // Clean up any arguments that need it.
    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;
}

In this example, the Invoke function is a general-purpose function that calls IDispatch::Invoke to invoke a property or method of an ActiveX object. As arguments, it accepts the object's IDispatch implementation, the name of the member to invoke, flags that control the invocation, and a variable list of the member's arguments. It can be found in the Browse sample in the file Invelp.cpp.

Using the object's IDispatch implementation and the name of the member, it calls GetIDsOfNames to get the DISPID of the requested member. The member's DISPID must be used later, in the call to IDispatch::Invoke.

The invocation flags specify whether a method, PROPERTYPUT, or PROPERTYGET function is being invoked. The helper function simply passes these flags directly to IDispatch::Invoke.

The helper function next fills in the DISPPARAMS structure with the parameters of the member. DISPPARAMS structures have the following form:

typedef struct FARSTRUCT tagDISPPARAMS{
    VARIANTARG FAR* rgvarg;           // Array of arguments.
    DISPID FAR* rgdispidNamedArgs;    // DISPIDs of named arguments.
    UINT cArgs;                       // Number of arguments.
    UINT cNamedArgs;                  // Number of named arguments.
} DISPPARAMS;

The rgvarg field is a pointer to an array of VARIANTARG structures. Each element of the array specifies an argument, whose position in the array corresponds to its position in the parameter list of the method definition. The cArgs field specifies the total number of arguments, and the cNamedArgs field specifies the number of named arguments.

For methods and property get functions, all arguments can be accessed as positional, or they can be accessed as named arguments. Property put functions have a named argument that is the new value for the property. The DISPID of this argument is DISPID_PROPERTYPUT.

To build the rgvarg array, the Invoke helper function retrieves the parameter values and types from its own argument list, and constructs a VARIANTARG structure for each one. (For a description of the format string that specifies the types of the parameters, see the file Invhelp.cpp.) Parameters are put in the array in reverse order, so that the last parameter is in rgvarg[0], and so forth. Although VARIANTARG has the following five fields, only the first and fifth are used.

typedef struct FARSTRUCT tagVARIANT VARIANTARG;

struct FARSTRUCT tagVARIANT{
    VARTYPE vt;
    unsigned short wReserved1;
    unsigned short wReserved2;
    unsigned short wReserved3;
    union {
        short        iVal;        /* VT_I2        */
.
.    // The rest of this union specifies numerous other types.
.

    };
} VARIANTARG;

The first field contains the argument's type, and the fifth contains its value. To pass a long integer, for example, the vt and iVal fields of the VARIANTARG structure would be filled with VT_I4 (long integer) and the actual value of the long integer.

In addition, for property put functions, the first element of the rgdispidNamedArgs array must contain DISPID_PROPERTYPUT.

After filling the DISPPARAMS structure, the Invoke helper function initializes pvRet, a variant in which IDispatch::Invoke returns a value from the method or property. The following is the actual call to IDispatch::Invoke:

hr = pdisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, wFlags,
        &dispparams, pvRet, pexcepinfo, pnArgErr);

The variable pdisp is a pointer to the object's IDispatch interface. DISPID indicates the method or property being invoked. The value IID_NULL must be specified for all IDispatch::Invoke calls, and LOCALE_SYSTEM_DEFAULT is a constant denoting the default locale identifier (LCID) for this system. In the final two arguments, pexcepinfo and pnArgErr, IDispatch::Invoke can return error information.

If the invoked member has defined an exception handler, it returns exception information in pexcepinfo. If certain errors occur in the argument vector, pnArgErr points to the errant argument. The function return value hr is an HRESULT that indicates success or various types of failure.

For more information, including how to pass optional arguments, see "IDispatch::Invoke" in Chapter 5, "Dispatch Interface and API Functions."