Lesson 3: Dispatch Interfaces

In Lesson 2, you learned that dispatch interfaces implement Microsoft's Automation technology. Automation allows components written in different languages to communicate with one another.

In this lesson, you will learn more about how the IDispatch interface and the VARIANT data type are used to allow other languages to access COM components. You will also learn how clients written in Microsoft development languages such as Visual Basic or Microsoft Visual J++ can access component interfaces directly without using the dispatch mechanism.

After this lesson, you will be able to:

  • Describe how a dispatch interface invokes methods on behalf of a client.
  • Describe the structure of the VARIANT data type.
  • Describe how type libraries enable Visual Basic clients to access component interfaces directly.
  • Describe how a dual interface exposes component methods.
Estimated lesson time: 30 minutes

IDispatch Interface

Dispatch interfaces enable client applications written in different languages to access your COM objects. For example, consider a scripting language such as Microsoft Visual Basic Scripting Edition (VBScript), which is used to create an instance of a COM component written in C++. VBScript does not enforce strict type checking and does not know about many of the C++ data types. To enable the VBScript client to communicate successfully with the server component, you must provide your component with a dispatch interface. This is achieved by implementing the standard COM interface IDispatch.

In certain development situations, you will only want to create COM objects that are going to be used exclusively by C++ clients. For example, objects created by a software development company for in-house use might fall into this category. In such a case you would not need to incur the overhead of implementing IDispatch. However, if you want your COM object to be accessible from a wider range of languages, you must implement the IDispatch interface. Clients written in other Microsoft development languages such as Visual Basic can use COM components written in C++ that do not implement the IDispatch interface. However, Visual Basic clients can only use interfaces that pass automation-compatible parameters to and from their methods. Automation-compatible data types are those that can be packaged into the VARIANT standard data format. A VARIANT is a COM-defined union data type, described in detail later in this lesson.

Figure 8.9 provides a simple illustration of how the IDispatch interface might be implemented for the Encoder class.

click to view at full size.

Figure 8.9 Implementing the IDispatch interface

The Encoder object vtable contains entries that point to the implemented IDispatch functions. A client application executes the GetIDsOfNames() method, providing the method name "Encode" as a string value, for example. The GetIDsOfNames() method implements an internal table, which maps the name of each method into a numerical dispatch identifier, known as a Dispatch ID, or DISPID. The DISPID is simply a numerical value—in the example illustrated in Figure 8.9, the DISPID of the Encode function is 1.

Once the client application has retrieved the DISPID of the method it wants to call, it can call the Invoke() method to execute the method. The Dispatch ID returned from GetIDsOfNames() is passed as an argument to the Invoke() method. Also passed to Invoke() are the parameters to the requested methods, packaged as an array of VARIANTs. Invoke() also takes a pointer to a VARIANT to hold any value returned by the requested method.

NOTE
This is a simplified account of the dispatch process. For example, parameters to invoked methods can be named and can also have Dispatch IDs.

Your implementation of the Invoke() method calls the requested method on behalf of the Automation client. Your implementation must maintain some kind of table to map DISPIDs to component methods. It must also unpack the arguments from the array of VARIANTs and pass them to the requested component method in the correct form. Any return values must be packed into the object referenced by the VARIANT pointer for return to the client.

VARIANT Data Type

The VARIANT data type is defined in the file OAIDL.IDL as follows:

 struct tagVARIANT {      union {           struct __tagVARIANT {                VARTYPE vt;                WORD    wReserved1;                WORD    wReserved2;                WORD    wReserved3;                union {                     LONG           lVal;      // VT_I4                     BYTE           bVal;      // VT_UI1                     SHORT          iVal;      // VT_I2                     FLOAT          fltVal;    // VT_R4                     DOUBLE         dblVal;    // VT_R8                     VARIANT_BOOL   boolVal;   // VT_BOOL                     _VARIANT_BOOL  bool;      // (obsolete)                     SCODE          scode;     // VT_ERROR                     CY             cyVal;     // VT_CY                     DATE           date;      // VT_DATE                     BSTR           bstrVal;   // VT_BSTR                      IUnknown *     punkVal;   // VT_UNKNOWN                     IDispatch *    pdispVal;  // VT_DISPATCH                     SAFEARRAY *    parray;    // VT_ARRAY                     BYTE *         pbVal;     // VT_BYREF|VT_UI1                     SHORT *        piVal;     // VT_BYREF|VT_I2                     LONG *         plVal;     // VT_BYREF|VT_I4                     FLOAT *        pfltVal;   // VT_BYREF|VT_R4                     DOUBLE *       pdblVal;   // VT_BYREF|VT_R8                     VARIANT_BOOL * pboolVal;  // VT_BYREF|VT_BOOL                     _VARIANT_BOOl *    pbool; // (obsolete)                     SCODE *        pscode;    // VT_BYREF|VT_ERROR                     CY *           pcyVal;    // VT_BYREF|VT_CY                     DATE *         pdate;     // VT_BYREF|VT_DATE                     BSTR *         pbstrVal;  // VT_BYREF|VT_BSTR                     IUnknown **    ppunkVal;  // VT_BYREF|VT_UNKNOWN                     IDispatch **   ppdispVal; // VT_BYREF|VT_DISPATCH                     SAFEARRAY **   pparray;   // VT_BYREF|VT_ARRAY                     VARIANT *      pvarVal;   // VT_BYREF|VT_VARIANT                     PVOID          byref;     // Generic ByRef                     CHAR           cVal;      // VT_I1                     USHORT         uiVal;     // VT_UI2                     ULONG          ulVal;     // VT_UI4                     INT            intVal;    // VT_INT                     UINT           uintVal;   // VT_UINT                     DECIMAL *      pdecVal;   // VT_BYREF|VT_DECIMAL                     CHAR *         pcVal;     // VT_BYREF|VT_I1                     USHORT *       puiVal;    // VT_BYREF|VT_UI2                     ULONG *        pulVal;    // VT_BYREF|VT_UI4                     INT *          pintVal;   // VT_BYREF|VT_INT                     UINT *         puintVal;  // VT_BYREF|VT_UINT                     struct __tagBRECORD {                          PVOID     pvRecord;                          IRecordInfo *     pRecInfo;                     } __VARIANT_NAME_4;          // VT_RECORD                } __VARIANT_NAME_3;           } __VARIANT_NAME_2;           DECIMAL decVal;      } __VARIANT_NAME_1; }; 

The VARIANT data structure contains two fields (if you discount the reserved fields). The vt field describes the type of data in the second field. To allow multiple data types to appear in the second field, a union structure is declared. Therefore, the name of the second field varies, depending on the value typed into the vt field. The constants used to specify the value of the vt field are indicated by a comment on the corresponding line of the union declaration.

Using the VARIANT and VARIANTARG data structure requires that you follow a two-step process. As an example, consider the following code:

long lValue = 555; VARIANT vParam; VParam.vt = VT_I4; vParam.lVal = lValue ;

In the first line, you specify a data type indicator. The VT_I4 constant states that a long data value appears in the second element. From the definition of the VARIANT type, you can see that the second field uses the name lVal when you store a long data value into the VARIANTARG data structure.

Using VARIANTs to pass parameters means that less strongly typed languages, such as VBScript, can invoke methods implemented in strongly typed languages such as C++. The implementation of the Invoke() method can check to see if the value encapsulated in a VARIANT parameter is of the correct type. If it is, then the type can be extracted and passed to the invoked method. If not, then the Invoke() method can attempt to coerce the value to the correct type using the VariantChangeType() API function.

Type Libraries

You might appreciate, from the previous discussion of the dispatch mechanism, that it is a relatively slow way of passing data between a server and a client. Also, the packing and unpacking of parameters into VARIANTs is not type-safe. High-performance development languages, such as Visual Basic 6.0, need to be able to access interface methods directly, through the vtable.

To make this possible, the Visual Basic client must know the number and types of parameters expected by the interface methods. This information is supplied to clients as a type library—a binary description of the interface properties and methods—and the method arguments. Type libraries can be thought of as a compiled, language-independent version of a C++ header file.

The Visual Basic environment reads the type library and presents a description of the interface to a programmer. The programmer creates an instance of the component using the Visual Basic syntax that allows direct access to the component's vtable. The information from the type library allows the programmer to pass the correct argument types to the interface methods. In this way, the Visual Basic programmer can bypass the dispatch mechanism and improve performance when using an in-process server. Remember that to make a component usable by a Visual Basic client, you must use only the Automation data types. A type library is described using IDL. The MIDL compiler outputs a type library file with a .tlb extension. This file is often linked to the .dll or .exe server that hosts the COM component.

For clients such as Visual Basic to access your type library, you must place additional entries into the registry for your COM object. These entries provide the COM run-time library with information about the location of your type library.

Figure 8.10 is a map of the registry entries associated with a COM object that supports a type library.

click to view at full size.

Figure 8.10 Registry entries for a type library

In fact, you only need two additional entries to locate the type library. Beneath the CLSID subkey for your COM object, you add the TypeLib subkey. The value that you assign to this subkey is the type library's LIBID—the GUID defined for the type library in the IDL file.

This LIBID is used as the second registry entry for your type library, which you place beneath the predefined HKEY_CLASSES_ROOT \TypeLib key. As shown in Figure 8.10, several other subkeys are found beneath your LIBID key. The win32 subkey specifies a path to the file containing your type library as shown here:

win32 = c:\Encoder\debug\Encoder.dll

The value that follows the equal sign is a path to the physical location of the type library file. In this case, it is linked to the COM server DLL. Applications that use type libraries know how to read them from .dll and .exe files.

When you instruct Visual Basic to reference a COM component, it uses this information to retrieve a path to the actual type library file. Once the file path is obtained, Visual Basic reads and parses the type library, constructs the programmatic representation, and enables Visual Basic programmers to write and execute code that uses the services of a COM server.

Dual Interfaces

The preferred way to implement a dispatch interface is as a dual interface. Dual interfaces make all of the Invoke() methods available directly, as entries in the component's vtable. Figure 8.11 illustrates a possible implementation of a dual interface—it shows how the addresses of the IEncoder methods are included in the vtable, as well as in the table consulted by the Invoke() function.

click to view at full size.

Figure 8.11 Implementation of a dual interface

If you implement a dual interface, properties and methods exposed by a component can still be accessed through a dispatch interface by languages such as VBScript; while languages such as Visual C++ can access methods directly through the vtable. Visual Basic can use either the dispatch interface or the vtable. Implementing a dual interface allows clients that can access the vtable to make faster method calls, but still supports those clients that require a dispatch interface.

Lesson Summary

The IDispatch interface enables a client environment such as VBScript to use a common entry point, the Invoke() method, to use your COM object. Arguments passed to the Invoke() method are packed into VARIANT data structures. These data structures contain one field that describes the type of data and a second field that stores the actual data.

To enable a development language such as Visual Basic to create a programmatic interface to your COM object, you generate a type library. The type library is a binary file that contains a description of your COM object's methods, properties, and the data types used. Visual Basic can use the type library information when making direct calls to methods using function pointers obtained from a COM component's vtable.

A dual interface allows calls through the dispatch interface or through the vtable, according to the capabilities of the client.



Microsoft Press - Desktop Applications with Microsoft Visual C++ 6. 0. MCSD Training Kit
Desktop Applications with Microsoft Visual C++ 6.0 MCSD Training Kit
ISBN: 0735607958
EAN: 2147483647
Year: 1999
Pages: 95

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