COM, ATL

IDispatch::Invoke

디버그정 2008. 8. 16. 18:02

IDispatch::Invoke

HRESULT Invoke( 
  DISPID  dispIdMember,      
  REFIID  riid,              
  LCID  lcid,                
  WORD  wFlags,              
  DISPPARAMS FAR*  pDispParams,  
  VARIANT FAR*  pVarResult,  
  EXCEPINFO FAR*  pExcepInfo,  
  unsigned int FAR*  puArgErr  
);
 

Provides access to properties and methods exposed by an object. The dispatch function DispInvoke provides a standard implementation of IDispatch::Invoke.

Parameters

dispIdMember
Identifies the member. Use GetIDsOfNames or the object's documentation to obtain the dispatch identifier.
riid
Reserved for future use. Must be IID_NULL.
lcid
The locale context in which to interpret arguments. The lcid is used by the GetIDsOfNames function, and is also passed to Invoke to allow the object to interpret its arguments specific to a locale.

Applications that do not support multiple national languages can ignore this parameter. For more information, refer to "Supporting Multiple National Languages" in Chapter 2, "Exposing ActiveX Objects."

wFlags
Flags describing the context of the Invoke call, include:
Value Description
DISPATCH_METHOD The member is invoked as a method. If a property has the same name, both this and the DISPATCH_PROPERTYGET flag may 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 a value assignment. This flag is valid only when the property accepts a reference to an object.

pDispParams
Pointer to a structure containing an array of arguments, an array of argument DISPIDs for named arguments, and counts for the number of elements in the arrays. See the Comments section that follows for a description of the DISPPARAMS structure.
pVarResult
Pointer to the location where the result is to be stored, or Null if the caller expects no result. This argument is ignored if DISPATCH_PROPERTYPUT or DISPATCH_PROPERTYPUTREF is specified.
pExcepInfo
Pointer to a structure that contains exception information. This structure should be filled in if DISP_E_EXCEPTION is returned. Can be Null.
puArgErr
The index within rgvarg of the first argument that has an error. Arguments are stored in pDispParams->rgvarg in reverse order, so the first argument is the one with the highest index in the array. This parameter is returned only when the resulting return value is DISP_E_TYPEMISMATCH or DISP_E_PARAMNOTFOUND. For details, see "Returning Errors" in the following Comments section.

Return Value

The return value obtained from the returned HRESULT is one of the following:

Return value Meaning
S_OK Success.
DISP_E_BADPARAMCOUNT The number of elements provided to DISPPARAMS is different from the number of arguments accepted by the method or property.
DISP_E_BADVARTYPE One of the arguments in rgvarg is not a valid variant type.
DISP_E_EXCEPTION The application needs to raise an exception. In this case, the structure passed in pExcepInfo should be filled in.
DISP_E_MEMBERNOTFOUND The requested member does not exist, or the call to Invoke tried to set the value of a read-only property.
DISP_E_NONAMEDARGS This implementation of IDispatch does not support named arguments.
DISP_E_OVERFLOW One of the arguments in rgvarg could not be coerced to the specified type.
DISP_E_PARAMNOTFOUND One of the parameter DISPIDs does not correspond to a parameter on the method. In this case, puArgErr should be set to the first argument that contains the error.
DISP_E_TYPEMISMATCH One or more of the arguments could not be coerced. The index within rgvarg of the first parameter with the incorrect type is returned in the puArgErr parameter.
DISP_E_UNKNOWNINTERFACE The interface identifier passed in riid is not IID_NULL.
DISP_E_UNKNOWNLCID The member being invoked interprets string arguments according to the LCID, and the LCID is not recognized. If the LCID is not needed to interpret arguments, this error should not be returned.
DISP_E_PARAMNOTOPTIONAL A required parameter was omitted.

In 16-bit versions, you can define your own errors using the MAKE_SCODE value macro.

Comments

Generally, you should not implement Invoke directly. Instead, use the dispatch interface create functions CreateStdDispatch and DispInvoke. For details, refer to "CreateStdDispatch" and "DispInvoke" in this chapter, and "Creating the IDispatch Interface" in Chapter 2, "Exposing ActiveX Objects."

If some application-specific processing needs to be performed before calling a member, the code should perform the necessary actions, and then call ITypeInfo::Invoke to invoke the member. ITypeInfo::Invoke acts exactly like IDispatch::Invoke. The standard implementations of IDispatch::Invoke created by CreateStdDispatch and DispInvoke defer to ITypeInfo::Invoke.

In an ActiveX client, IDispatch::Invoke should be used to get and set the values of properties, or to call a method of an ActiveX object. The dispIdMember argument identifies the member to invoke. The DISPIDs that identify members are defined by the implementor of the object and can be determined by using the object's documentation, the IDispatch::GetIDsOfNames function, or the ITypeInfo interface.

The information that follows addresses developers of ActiveX clients and others who use code to expose ActiveX objects. It describes the behavior that users of exposed objects should expect.

Calling a Method With No Arguments

The simplest use of Invoke is to call a method that does not have any arguments. You only need to pass the DISPID of the method, a LCID, the DISPATCH_METHOD flag, and an empty DISPPARAMS structure. For example:

HRESULT hresult;
IUnknown FAR* punk;
IDispatch FAR* pdisp = (IDispatch FAR*)NULL;
OLECHAR FAR* szMember = "Simple";
DISPID dispid;
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

hresult = CoCreateInstance(CLSID_CMyObject, NULL, CLSCTX_SERVER,
                IID_Unknown, (void FAR* FAR*)&punk);

hresult = punk->QueryInterface(IID_IDispatch,
                (void FAR* FAR*)&pdisp);

hresult = pdisp->GetIDsOfNames(IID_NULL, &szMember, 1,
                LOCALE_USER_DEFAULT, &dispid);

hresult = pdisp->Invoke(
        dispid,
        IID_NULL,
        LOCALE_USER_DEFAULT,
        DISPATCH_METHOD,
        &dispparamsNoArgs, NULL, NULL, NULL);

The example invokes a method named Simple on an object of the class CMyObject. First, it calls CoCreateInstance, which instantiates the object and returns a pointer to the object's IUnknown interface (punk). Next, it calls QueryInterface, receiving a pointer to the object's IDispatch interface (pdisp). It then uses pdisp to call the object's GetIDsOfNames function, passing the string Simple in szMember to get the DISPID for the Simple method. With the DISPID for Simple in dispid, it calls Invoke to invoke the method, specifying DISPATCH_METHOD for the wFlags parameter and using the system default locale.

To further simplify the code, the example declares a DISPPARAMS structure named dispparamsNoArgs that is appropriate to an Invoke call with no arguments.

Because the Simple method does not take any arguments and does not return a result, the puArgErr and pVarResult parameters are Null. In addition, the example passes Null for pExcepInfo, indicating that it is not prepared to handle exceptions and will handle only HRESULT errors.

Most methods, however, take one or more arguments. To invoke these methods, the DISPPARAMS structure should be filled in, as described in "Passing Parameters" later in this chapter.

Automation defines special DISPIDs for invoking an object's Value property (the default), and the members _NewEnum, and Evaluate. For details, see "DISPID" in Chapter 6, "Data Types, Structures, and Enumerations."

Getting and Setting Properties

Properties are accessed in the same way as methods, except you specify DISPATCH_PROPERTYGET or DISPATCH_PROPERTYPUT instead of DISPATCH_METHOD. Some languages can not distinguish between retrieving a property and calling a method. In this case, you should set the flags DISPATCH_PROPERTYGET and DISPATCH_METHOD.

The following example gets the value of a property named On. You can assume that the object has been created, and that its interfaces have been queried, as in the previous example.

VARIANT FAR *pVarResult;
// Code omitted for brevity.
szMember = "On";
hresult = pdisp->GetIDsOfNames(IID_NULL, &szMember, 1, 
                LOCALE_USER_DEFAULT, &dispid);

hresult = pdisp->Invoke(
        dispid,
        IID_NULL,
        LOCALE_USER_DEFAULT,
        DISPATCH_PROPERTYGET,
        &dispparamsNoArgs, pVarResult, NULL, NULL);

As in the previous example, the code calls GetIDsOfNames for the DISPID of the On property, and then passes the ID to Invoke. Then, Invoke returns the property's value in pVarResult. In general, the return value does not set VT_BYREF. However, this bit may be set and a pointer returned to the return value, if the lifetime of the return value is the same as that of the object.

To change the property's value, the call looks like this:

VARIANT FAR *pVarResult;
DISPPARAMS dispparams; 
DISPID mydispid = DISP_PROPERTYPUT

// Code omitted for brevity.

szMember = "On";
dispparams.rgvarg[0].vt = VT_BOOL;
dispparams.rgvarg[0].bool = FALSE;
dispparams.rgdispidNamedArgs = &mydispid;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 1;
hresult = pdisp->GetIDsOfNames(IID_NULL, &szMember, 1, 
                LOCALE_USER_DEFAULT, &dispid); 

hresult = pdisp->Invoke(
        dispid,
        IID_NULL,
        LOCALE_USER_DEFAULT,
        DISPATCH_PROPERTYPUT,
        &dispparams, NULL, NULL, NULL);

The new value for the property (the Boolean value False) is passed as an argument when the On property's Put function is invoked. The DISPID for the argument is DISPID_PROPERTYPUT. This DISPID is defined by Automation to designate the parameter that contains the new value for a property's Put function. The remaining details of the DISPPARAMS structure are described in the next section, "Passing Parameters."

The DISPATCH_PROPERTYPUT flag in the previous example indicates that a property is being set by value. In Visual Basic, the following statement assigns the Value property (the default) of YourObj to the Prop property:

MyObj.Prop = YourObj

This statement should be flagged as a DISPATCH_PROPERTYPUT. Similarly, statements like the following assign the Value property of one object to the Value property of another object.

Worksheet.Cell(1,1) = Worksheet.Cell(6,6)
MyDoc.Text1 = YourDoc.Text1

These statements result in a PROPERTY_PUT operation on Worksheet.Cell(1,1) and MyDoc.Text1.

Use the DISPATCH_PROPERTYPUTREF flag to indicate a property or data member that should be set by reference. For example, the following Visual Basic statement assigns the pointer YourObj to the property Prop, and should be flagged as DISPATCH_PROPERTYPUTREF.

Set MyObj.Prop = YourObj

The Set statement causes a reference assignment, rather than a value assignment.

The parameter on the right side is always passed by name, and should not be accessed positionally.

Passing Parameters

Arguments to the method or property being invoked are passed in the DISPPARAMS structure. This structure consists of a pointer to an array of arguments represented as variants, a pointer to an array of DISPIDs for named arguments, and the number of arguments in each array.

typedef struct FARSTRUCT tagDISPPARAMS{
    VARIANTARG FAR* rgvarg;            // Array of arguments.
    DISPID FAR* rgdispidNamedArgs;     // Dispatch IDs of named arguments.
    unsigned int cArgs;                // Number of arguments.
    unsigned int cNamedArgs;         // Number of named arguments.
} DISPPARAMS;

The arguments are passed in the array rgvarg[ ], with the number of arguments passed in cArgs. The arguments in the array should be placed from last to first, so rgvarg[0] has the last argument and rgvarg[cArgs –1] has the first argument. The method or property may change the values of elements within the array rgvarg, but only if it has set the VT_BYREF flag. Otherwise, consider the elements as
read-only.

A dispatch invocation can have named arguments as well as positional arguments. If cNamedArgs is 0, all the elements of rgvarg[ ] represent positional arguments. If cNamedArgs is not 0, each element of rgdispidNamedArgs[ ] contains the DISPID of a named argument, and the value of the argument is in the matching element of rgvarg[ ]. The DISPIDs of the named arguments are always contiguous in rgdispidNamedArgs, and their values are in the first cNamedArgs elements of rgvarg. Named arguments cannot be accessed positionally, and positional arguments cannot be named.

The DISPID of an argument is its zero-based position in the argument list. For example, the following method takes three arguments.

BOOL _export CDECL
CCredit::CheckCredit(BSTR bstrCustomerID,    // DISPID = 0.
                     BSTR bstrLenderID,        // DISPID = 1.
                     CURRENCY cLoanAmt)        // DISPID = 2.
{
// Code omitted.
}

If you include the DISPID with each named argument, you can pass the named arguments to Invoke in any order. For example, if a method is to be invoked with two positional arguments, followed by three named arguments (A, B, and C), using the following hypothetical syntax, then cArgs would be 5, and cNamedArgs would be 3.

object.method("arg1", "arg2", A := "argA", B := "argB", C := "argC")

The first positional argument would be in rgvarg[4]. The second positional argument would be in rgvarg[3]. The ordering of named arguments is not important to the IDispatch implementation, but these arguments are generally passed in reverse order. The argument A would be in rgvarg[2], with the DISPID of A in rgdispidNamedArgs[2]. The argument B would be in rgvarg[1], with the corresponding DISPID in rgdispidNamedArgs[1]. The argument C would be in rgvarg[0], with the DISPID corresponding to C in rgdispidNamedArgs[0]. The following diagram illustrates the arrays and their contents.

You can also use Invoke on members with optional arguments, but all optional arguments must be of type VARIANT. As with required arguments, the contents of the argument vector depend on whether the arguments are positional or named. The invoked member must ensure that the arguments are valid. Invoke merely passes the DISPPARAMS structure it receives.

Omitting named arguments is straightforward. You would pass the arguments in rgvarg and their DISPIDs in rgdispidNamedArgs. To omit the argument named B (in the preceding example) you would set rgvarg[0] to the value of C, with its DISPID in rgdispidNamedArgs[0]; and rgvarg[1] to the value of A, with its DISPID in rgdispidNamedArgs[1]. The subsequent positional arguments would occupy elements 2 and 3 of the arrays. In this case, cArgs is 4 and cNamedArgs
is 2.

If the arguments are positional (unnamed), you would set cArgs to the total number of possible arguments, cNamedArgs to 0, and pass VT_ERROR as the type of the omitted arguments, with the status code DISP_E_PARAMNOTFOUND as the value. For example, the following code invokes ShowMe (,1).

VARIANT FAR *pVarResult;
EXCEPINFO FAR *pExcepInfo;
unsigned int FAR *puArgErr;
DISPPARAMS dispparams; 

// Code omitted for brevity.

szMember = "ShowMe";
hresult = pdisp->GetIDsOfNames(IID_NULL, &szMember, 1,
                                LOCALE_USER_DEFAULT, &dispid) ;
dispparams.rgvarg[0].vt = VT_I2;
dispparams.rgvarg[0].ival = 1;
dispparams.rgvarg[1].vt = VT_ERROR;
dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND;
dispparams.cArgs = 2;
dispparams.cNamedArgs = 0;

hresult = pdisp->Invoke(
        dispid,
        IID_NULL,
        LOCALE_USER_DEFAULT,
        DISPATCH_METHOD,
        &dispparams, pVarResult, pExcepInfo, puArgErr);

The example takes two positional arguments, but omits the first. Therefore, rgvarg[0] contains 1, the value of the last argument in the argument list, and rgvarg[1] contains VT_ERROR and the error return value, indicating the omitted first argument.

The calling code is responsible for releasing all strings and objects referred to by rgvarg[ ] or placed in *pVarResult. As with other parameters that are passed by value, if the invoked member must maintain access to a string after returning, you should copy the string. Similarly, if the member needs access to a passed-object pointer after returning, it must call the AddRef function on the object. A common example occurs when an object property is changed to refer to a new object, using the DISPATCH_PROPERTYPUTREF flag.

For those implementing IDispatch::Invoke, Automation provides the DispGetParam function to retrieve parameters from the argument vector and coerce them to the proper type. For details, see "DispGetParam" later in this chapter.

Indexed Properties

When you invoke indexed properties of any dimension, you must pass the indexes as additional arguments. To set an indexed property, place the new value in the first element of the rgvarg[ ] vector, and the indexes in the subsequent elements. To get an indexed property, pass the indexes in the first n elements of rgvarg, and the number of indexes in cArg. Invoke returns the value of the property in pVarResult.

Automation stores array data in column-major order, which is the same ordering scheme used by Visual Basic and FORTRAN, but different from C, C++, and Pascal. If you are programming in C, C++, or Pascal, you must pass the indexes in the reverse order. The following example shows how to fill the DISPPARAMS structure in C++.

dispparams.rgvarg[0].vt = VT_I2;
dispparams.rgvarg[0].iVal = 99;
dispparams.rgvarg[1].vt = VT_I2;
dispparams.rgvarg[1].iVal = 2;
dispparams.rgvarg[2].vt = VT_I2;
dispparams.rgvarg[2].iVal = 1;
dispparams.rgdispidNamedArgs = DISPID_PROPERTYPUT;
dispparams.cArgs = 3;
dispparams.cNamedArgs = 1;

The example changes the value of Prop[1,2] to 99. The new property value is passed in rgvarg[0]. The right-most index is passed in rgvarg[1], and the next index in rgvarg[2]. The cArgs field specifies the number of elements of rgvarg[ ] that contain data, and cNamedArgs is 1, indicating the new value for the property.

Property collections are an extension of this feature.

Raising Exceptions During Invoke

When you implement IDispatch::Invoke, errors can be communicated either through the normal return value or by raising an exception. An exception is a special situation that is normally handled by jumping to the nearest routine enclosing the exception handler.

To raise an exception, IDispatch::Invoke returns DISP_E_EXCEPTION and fills the structure passed through pExcepInfo with information about the cause of the exception or error. You can use the information to understand the cause of the exception and proceed as necessary.

The exception information structure includes an error code number that identifies the kind of exception (a string that describes the error in a human-readable way). It also includes a Help file and a Help context number that can be passed to Windows Help for details about the error. At a minimum, the error code number must be filled with a valid number.

If you consider IDispatch another way to call C++ methods in an interface, EXCEPINFO models the raising of an exception or longjmp() call by such a method.

Returning Errors

Invoke returns DISP_E_MEMBERNOTFOUND if one of the following conditions occurs:

  • A member or parameter with the specified DISPID and matching cArgs cannot be found, and the parameter is not optional.
  • The member is a void function, and the caller did not set pVarResult to Null.
  • The member is a read-only property, and the caller set wFlags to DISPATCH_PROPERTYPUT or DISPATCH_PROPERTYPUTREF.

If Invoke finds the member, but uncovers errors in the argument list, it returns one of several other errors. DISP_E_BAD_PARAMCOUNT means that the DISPPARAMS structure contains an incorrect number of parameters for the property or method. DISP_E_NONAMEDARGS means that Invoke received named arguments, but they are not supported by the member.

DISP_E_PARAMNOTFOUND means that the correct number of parameters was passed, but the DISPID for one or more parameters was incorrect. If Invoke cannot convert one of the arguments to the desired type, it returns DISP_E_TYPEMISMATCH. In these two cases, if it can identify which argument is incorrect, Invoke sets *puArgErr to the index within rgvarg of the argument with the error. For example, if an Automation method expects a reference to a double-precision number as an argument, but receives a reference to an integer, the argument is coerced. However, if the method receives a date, IDispatch::Invoke returns DISP_E_TYPEMISMATCH and sets *puArgErr to the index of the integer in the argument array.

Automation provides functions to perform standard conversions of VARIANT, and these functions should be used for consistent operation. DISP_E_TYPEMISMATCH is returned only when these functions fail. For more information about converting arguments, see Chapter 7, "Conversion and Manipulation Functions."

Example

This code from the Lines sample file Lines.cpp implements the Invoke member function for the CLines class.

STDMETHODIMP
CLines::Invoke(
    DISPID dispidMember,
    REFIID riid,
    LCID lcid,
    WORD wFlags,
    DISPPARAMS FAR* pDispParams,
    VARIANT FAR* pVarResult,
    EXCEPINFO FAR* pExcepInfo,
    UINT FAR* puArgErr)
{ 
    return DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pDispParams,
        pVarResult, pExcepInfo, puArgErr); 
}

The next code example calls the CLines::Invoke member function to get the value of the Color property:

HRESULT hr;
EXCEPINFO excepinfo;
UINT nArgErr;
VARIANT vRet;
DISPPARAMS FAR* pdisp;
OLECHAR FAR* szMember;
DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};

// Initialization code omitted for brevity.
szMember = "Color";
hr = pdisp->GetIDsOfNames(IID_NULL, &szMember, 1, LOCALE_USER_DEFAULT,
    &dispid);

// Get Color property.
hr = pdisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
    DISPATCH_PROPERTYGET, &dispparams, &vRet, &excepinfo, &nArgErr);

See Also

CreateStdDispatch, DispInvoke, DispGetParam, ITypeInfo::Invoke