ActiveX and Automation

< BACK  NEXT >
[oR]

So far, all the code in this chapter has accessed COM interfaces and their functions directly. This is relatively easy from C++, since the vtable structure used by COM can be accessed from C++ code. However, other languages in particular, scripting languages like VBScript may not be able to access the vtable. This is because COM interface function calls would need to be resolved at run time, and the vtable does not contain information about the names of the functions, the arguments they take, or the order of the functions. For this reason, Automation was developed. Automation allows functionality in COM components to be called by creating and passing data structures detailing the nature of the call, rather than direct function calls. Automation is implemented by a single COM interface called IDispatch.

ActiveX controls generally use Automation rather than direct COM interface calls, although many ActiveX controls support both Automation and COM interface calls by providing "dual interfaces." With dual interfaces, the client application can decide whether to use the IDispatch Automation interface or call functions directly through the vtable. IDispatch Automation interfaces are known as "dispinterfaces" or "dispatch interfaces" note that they are not COM interfaces although they are invoked through the IDispatch COM interface. Automation does not use standard C++ data types for parameters and return types. Instead, parameters are passed using the variant data type. While this limits to a certain extent the type of data that can be passed, it allows data coercion and data casting to be performed at run time.

Some components do not support dual interfaces, so the dispinterface must be used. Therefore, you may need to call Invoke through IDispatch, passing the relevant parameter information.

_bstr_t and _variant_t Classes

In the same way that smart pointers make accessing COM interfaces easier, you can use the classes _bstr_t and _variant_t to make accessing BSTR and VARIANT variables easier and more reliable. These classes provide constructors that create BSTR directly from strings without having to use SysAllocString, and the BSTR variables are automatically de-allocated. These two classes are described in Chapter 16 (ADOCE).

Automation DispInterfaces

An Automation DispInterface provides access to a COM component's functionality through the following:

  • Methods, which are similar to functions in that they are passed parameters and return values

  • Properties, which are values associated with a COM component

Each method and property has a "dispid," or dispatch identifier, that is unique within the Automation object. A client application calls a method or accesses a property through the dispid rather than using the method or property's name directly. Note that dispids are not GUIDs but are simple integer numbers. Although properties are used like variables, they are actually accessed through method-like calls into the automation object.

Information on the DispInterface is included in the type library information associated with the COM component. If the type library information is included in the COM component's DLL or EXE, the client application can interrogate this information at run time through the IDispatch interface.

The IDispatch Interface

Automation is implemented using a single interface call, IDispatch, which has four interface functions:

  • Invoke Called to execute a method or property. It is passed the dispid of the method or property and the parameters and return value.

  • GetIDsOfNames Allows a client application to convert the name of the method or property to a dispid.

  • GetTypeInfoCount Used to determine if the Automation object can return type library information at run time.

  • GetTypeInfo Used to return type library information at run time for an automation object.

Generally, Invoke and GetIDsOfNames are used to execute methods and properties, and GetTypeInfoCount and GetTypeInfo are used to obtain information about the methods and properties. You will find code examples showing how to call GetTypeInfoCount and GetTypeInfo at the end of Chapter 14.cpp in the Examples project on the CDROM.

Obtaining an IDispatch Interface Pointer

Any COM component that provides an Automation interface must implement the IDispatch interface. The following function returns an IDispatch smart interface pointer for the Pocket Outlook Object Model:

 #include <dispex.h> // for IID_IDispatch definition _COM_SMARTPTR_TYPEDEF(IDispatch, IID_IDispatch); BOOL GetIDispatch(IDispatchPtr& pDispatchPtr) {   HRESULT hr;   hr = pDispatchPtr.CreateInstance(CLSID_Application);   if (FAILED(hr))   {     cout   _T("Could not create object")   endl;     return FALSE;   }   return TRUE; } 

In previous uses of _COM_SMARTPTR_TYPEDEF, the __uuidof macro was used to obtain the interface identifier (IID) from the interface definition. This assumes that the interface is declared using the DECLSPEC_UUID attribute, as follows:

 interface DECLSPEC_UUID       ("5B43F691-202C-11d2-8F18-0000F87A4335") IAppointment : public IDispatch { ... 

Many interface declarations do not use DECLSPEC_UUID, so you will need to use the IID directly (such as IID_IDispatch from the header file dispex.h).

Obtaining Dispatch Identifiers

Before calling a property or method, you must first obtain its dispatch identi- fier (DispId) using the IDispatch interface function GetIDsOfNames (Table 14.4). GetIDsOfNames can convert several property and method names in one call, which is more efficient than making single calls.

Table 14.4. GetIDsOfNames Returns DispIDs for method and property names
GetIDsOfNames
REFIID riid Not used, pass as IID_NULL. Using this constant may require you to include coguid.h.
OLECHAR FAR* FAR* rgszNames An array of null-terminated property and method names. OLECHAR are Unicode NULL-terminated strings.
unsigned int cNames Number of property and method names in rgszNames.
LCID lcid Local used for conversion, generally pass return result of calling the function GetUserDefaultLCID.
DISPID FAR* rgDispId Array of DISPID to receive the DispIds returned by the function call.
HRESULT Return Value HRESULT indicating success or failure.

Potentially, an Automation interface could be localized so that method and property names are translated into different languages. For this reason, GetIDsOfNames requires that a locale be specified. Generally, the result of calling GetUserDefaultLCID (which obtains the locale of the current user) is passed to GetIDsOfNames.

The following code shows how to get the DispID for the Version property in IPOutlookApp:

   LPTSTR names[] = {_T("Version")};   DISPID dispLogoff;   HRESULT hr;   // first get the dispatch ID for IPOutlookApp::Version   hr = pDispatchPtr->GetIDsOfNames(IID_NULL,            names, 1,            GetUserDefaultLCID(), &dispVersion);   if(FAILED(hr))   {     cout   _T("Could not get dispid for Version")            endl;     return FALSE;   } 

The VARIANT Data Type

All parameters and return values used when calling Automation methods and properties are passed using the VARIANT structure. This structure is also referred to a VARIANTARG. The vt member of this structure contains a constant that describes the type of data contained in the structure and an unnamed union with members for all the different data types that can be stored in a VARIANT. The first few members of this structure are the following:

 typedef struct tagVARIANT {   VARTYPE vt;   unsigned short wReserved1;   unsigned short wReserved2;   unsigned short wReserved3;   union {     unsigned char bVal;     short iVal;     long lVal;     float fltVal;     double dblVal;     ... 

The basic data types that can be stored in a variant are shown in Table 14.5. The VARIANT structure can store IUnknown and IDispatch pointers; this allows Automation methods and properties to return COM pointers. This in turn allows COM components to create object hierarchies and object models like POOM.

Table 14.5. Basic VARIANT data types
vt Constant Member Description
VT_UI1 bVal Unsigned char
VT_I2 iVal Two-byte signed integer
VT_I4 lVal Four-byte signed integer
VT_R4 fltVal Four-byte floating point
VT_R8 dblVal Eight-byte floating point
VT_BOOL boolVal VARIANT_BOOL Boolean value
VT_ERROR scode SCODE error value
VT_CY cyVal Currency data type
VT_DATE date DATE data type
VT_BSTR bstrVal BSTR variable-length string
VT_UNKNOWN punkVal IUnknown pointer
VT_DISPATCH pdispVal IDispatch pointer

The data types in Table 14.5 are used for passing data by value. When a property or method needs to return data, a "by reference" data type must be used. All the data types in Table 14.5 can be combined with the vt constant VT_BYREF. The union members for data passed by reference are all preceded by "p", so the VT_I2 member used when passing by reference is piVal.

Two important VT values are used to signify that the VARIANT structure does not contain data. VT_EMPTY means that no data is stored in the VARIANT structure the value is 0 for numeric data, and an empty string for string data. VT_NULL signifies that the VARIANT structure intentionally does not contain data.

The VariantInit function can be called to set the vt member to VT_EMPTY, and VariantClear sets it to VT_NULL. Each of these functions takes a pointer to a VARIANT structure and returns an HRESULT:

 VARIANT varg; HRESULT hr; hr = VariantInit(&varg); 

The VariantChangeTypeEx function can be used to coerce (or cast) a VARIANT structure from one data type to another. The function determines whether the coercion is legal and returns S_OK for success, or DISP_E_TYPEMISMATCH if the coercion failed. The function is passed the following:

  • A pointer to the VARIANT structure in which the coerced VARIANT will be returned

  • A pointer to the VARIANT structure to be coerced

  • The locale identifier used to determine date and currency formats

  • A flags parameter that will normally be passed as zero

  • The VT_data type to coerce to

The following code shows coercion for a VT_I2 to VT_R4 data type:

 VARIANT varI2, varR4; HRESULT hr; varI2.vt = VT_I2; varI2.iVal = VT_R4; hr = VariantChangeTypeEx(&varR4, &varI2,       GetUserDefaultLCID(), 0, VT_R4); 

There are also functions used for explicitly converting from one data type to another (such as VarR4FromUI2); however, the advantage of using VariantChangeTypeEx is that you do not need to determine the current data type of a variant before attempting to coerce it to a different data type.

Using an Automation Property

An Automation property is accessed through calling the IDispatch Invoke function, specifying whether the property value is to be retrieved or set. When a property value is to be set, the new value for the property is passed as a parameter to the invocation. A property value is retrieved through the return value of the invocation.

In Listing 14.11a, the function DisplayVersion is passed a smart pointer to the POOM object model and first obtains the dispid for the Version property (which was described in the previous section, "Obtaining Dispatch Identifiers").

Listing 14.11a Accessing the Version property
 BOOL DisplayVersion(IDispatchPtr& pDispatchPtr) {   DISPID dispVersion;   HRESULT hr;   VARIANTARG varResult;   DISPPARAMS disparms = {       NULL, NULL,       0,       // zero arguments       0        // zero named arguments       };   LPTSTR names[] = {_T("Version")};   // first get the dispatch ID for IPOutlookApp::Version   hr = pDispatchPtr->GetIDsOfNames(IID_NULL, names,         1, GetUserDefaultLCID(), &dispVersion);   if(FAILED(hr))   {     cout   _T("Could not get dispid for Version")            endl;     return FALSE;   }   hr = pDispatchPtr->Invoke(dispVersion, IID_NULL,         GetUserDefaultLCID(),         DISPATCH_PROPERTYGET,         &disparms, &varResult, NULL, NULL);   if(FAILED(hr))   {     cout   _T("Could not invoke Version")   endl;     return FALSE;   }   cout   _T("Version invoked:")   varResult.bstrVal          endl;   SysFreeString(varResult.bstrVal);   return TRUE; } 

The Invoke function (Table 14.6) is called, passing the following parameters:

  • dispVersion The dispid of the version property.

  • IID_NULL This is a reserved value.

  • GetUserDefaultLCID() The locale used for data formats and so on. This function returns the locale for the current user.

  • DISPATCH_PROPERTYGET Invoke is used to retrieve a property's value.

  • disparms A pointer to a DISPPARAMS structure describing the parameters being passed to the Automation call. This structure is initialized to specify "no parameters" when performing a DISPATCH_PROPERTYGET.

  • varResult A pointer to a VARIANTARG structure that receives the return result that is, in this case, the property's value.

  • NULL and NULL No error information is requested.

The Automation object will interpret the data passed to the Invoke and execute the necessary code to obtain the property's value and return the result.

On return from Invoke the "varResult" variable contains the property's value, and the value is accessed through the statement varResult.bstrVal. Since this is a BSTR the function SysFreeString must be called to free the memory associated with the string.

Calling Automation Methods

Calling Automation methods is very similar to accessing property values. However, you will need to initialize a DISPPARAMS structure to specify the parameters to be passed. The DISPPARAMS structure has four members:

Table 14.6. Invoke Executes an Automation method or property
Invoke
DISPID dispIdMember DISPID of the property or method to invoke.
REFIID riid Reserved, set to IID_NULL.
LCID lcid Local used to interpret parameter value formats such as dates and currencies.
WORD wFlags Flag indicating how to interpret the invocation:
DISPATCH_METHOD Method call.
DISPATCH_PROPERTYGET Return property value.
DISPATCH_PROPERTYPUT Set property value.
DISPATCH_PROPERTYPUTREF Reference assignment rather than a value assignment.
DISPPARAMS FAR* pDispParams Pointer to a DISPPARAMS structure describing the parameters passed to the invocation.
VARIANT FAR* pVarResult Pointer to a VARIANT structure containing the return value from the invocation.
EXCEPINFO FAR* pExcepInfo Pointer to an EXCEPINFO structure describing an error raised by the Automation object.
unsigned int FAR* puArgErr Pointer to an integer that contains, on return, an index to array of parameters indicating the first parameters that caused an error.
HRESULT Return Value HRESULT containing a dispatch error, such as DISP_E_BADPARAMCOUNT, indicating a wrong number of arguments, or DISP_E_TYPEMISMATCH if an argument is of the wrong type. S_OK for success.

  • rgvarg Pointer to an array of VARIANTARG structures specifying the values for the invocation

  • rgdispidNamedArgs Pointer to an array of DISPIDs specifying named arguments

  • cArgs Number of arguments specified in rgvarg

  • cNamedArgs Number of arguments specified in rgdispidNamedArgs

Automation allows parameters to be passed by name rather than order. This allows default parameters, since the call to Invoke does not have to supply a VARIANT structure for each of the parameters, and the Automation object can default those not supplied.

In the simplest case, a DISPPARAMS structure can be initialized to specify "no parameters", as is done in Listing 14.11a. In the following code VARIANTARG and DISPPARAMS structures are initialized for a call to the "Logon" method, and this method is passed a single HWND argument. A VARIANTARG variable is declared to hold the parameter value. Then, the DISPPARAMS structure is initialized to have one parameter, and this parameter will be specified in the first element of the varg array:

   VARIANTARG varg;   DISPPARAMS disparms = {       &varg, NULL,       1,    // one parameter       0     // zero named parameters       }; 

Next, calling VariantInit initializes the VARIANTARG structure, and the data type is set to VT_I4. The hWnd value is assigned to the lVal union member in VARIANT:

   VariantInit(&varg);   varg.vt = VT_I4;   varg.lVal = (LONG)hWnd; 

Parameters are passed in reverse order in the VARIANTARG array. For example, the following Automation method takes two arguments:

 AutomationFunction( VT_I4 argument1, VT_R8 argument2) 

The VARIANTARG array should be declared and initialized in the following way:

 VARIANTARG varg[2]; DISPPARAMS disparms = {     &varg, NULL,     2,    // two parameters     0     // zero named parameters     }; VariantInit(&varg[0]); varg[0].vt = VT_R8; varg[0].lVal = 1.20;    // initialize VT_R8 VariantInit(&varg[1]); varg.vt[1] = VT_I4; varg.lVal[1] = 42;      // initialize VT_I4 

The entire code for calling "Logon" is shown in Listing 14.11b. There is obviously much more code in calling Logon using the Automation interface and IDispatch::Invoke. C++ programmers should generally call COM interfaces directly rather than using IDispatch::Invoke, since it is easier, generates less code, and provides faster execution. However, some components do not provide a dual interface, leaving no option but to use Automation. MFC, described later in the chapter, provide helper classes for making the calling of Automation objects easier.

Listing 14.11b Passing parameters to a method
 BOOL Logon(IDispatchPtr& pDispatchPtr, HWND hWnd) {   DISPID dispLogon;   HRESULT hr;   VARIANTARG varg;   DISPPARAMS disparms = {       &varg, NULL,       1,    // one parameter       0     // zero named parameters       };   LPTSTR names[] = {_T("Logon")};   // first get the dispatch ID for IPOutlookApp::Logon   hr = pDispatchPtr->GetIDsOfNames(IID_NULL,     names, 1, GetUserDefaultLCID(), &dispLogon);   if(FAILED(hr))   {     cout   _T("Could not get dispid for Logon")            endl;     return FALSE;   }   VariantInit(&varg);   varg.vt = VT_I4;   varg.lVal = (LONG)hWnd;   hr = pDispatchPtr->Invoke(dispLogon, IID_NULL,       GetUserDefaultLCID(), DISPATCH_METHOD,       &disparms, NULL, NULL, NULL);   if(FAILED(hr))   {     cout   _T("Could not invoke Logon")   endl;     return FALSE;   }   cout   _T("Logon invoked")   endl;   return TRUE; } 

< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net