OLE Automation is a technology through which a component or an entire application can expose the functionality (methods) and content (individual properties) of any automation objects within it in a late-bound, programmatic manner. Late binding means that method and property names are resolved into dispatch identifiers (dispIDs) at run time and then passed to a function that will invoke the appropriate code for any given dispID. This allows interpreted macro-programming languages and other tools running a script of some sort to access and manipulate objects without having compile-time knowledge of vtable layouts. Such tools, called automation controllers, can create objects from any components or applications as necessary, thus enabling end users or developers to write cross-application macros, a longtime user demand.
The core of automation is the dispatch interface, or dispinterface for short, which is a specific implementation of the interface named IDispatch, which responds only to certain dispIDs. Through IDispatch, a controller can retrieve the object's type information for the dispinterface, map names to dispIDs, and invoke methods and properties. The latter happens through IDispatch::Invoke. This function has a fixed compile-time signature by which it can accept any number of arguments for the invocation of a method call, including named and optional arguments. In return, Invoke can provide any type of return value as well as rich error information. This chapter explores the nuances of the various arguments of IDispatch::Invoke and describes how it works internally.
Arguments and return values handled through Invoke use the types VARIANTARG and VARIANT. Both types, which are structurally identical, contain a type identifier (VARTYPE) and a value appropriate to that type, whether it is a pointer, an integer, a string pointer, a date or currency value, and so on. The value is stored in one field of a large union of types within the VARIANT. Two of these types are used frequently in OLE Automation—the BSTR (Basic string) and the Safe Array (an array that carries its bounds with it). OLE provides services to coerce a VARIANT of one type into another, compatible, type if the conversion is at all possible.
An important class of automation object is called a collection, which is a grouping of other objects. Collections implement specific methods and properties to navigate their contained elements, including the interface IEnumVARIANT, which enumerates those elements.
Making a method call or accessing a property through IDispatch::Invoke is a process with a good deal of overhead, which results in much slower performance than the same functionality expressed through a vtable interface. For this reason, OLE Automation defines what is called a dual interface, which is a custom vtable interface that derives from IDispatch. A dual interface allows a client that can perform vtable binding to call functions in the interface efficiently without losing compatibility with automation controllers that can call only IDispatch members. A dual interface comes with the added bene-fit that OLE Automation provides its own marshaling for both IDispatch and custom portions of the interface as long as the interface uses only a limited number of automation-compatible types.
This chapter also examines five different techniques for implementing an automation object: a straight manual implementation of IDispatch; an implementation using the service of ITypeInfo; one using error objects for rich error reporting; one with a dual interface; and one that employs OLE's standard dispatch object, eliminating the need to have any of your own explicit IDispatch entry points.
This is all topped off with a discussion of fully automated applications, including a review of a few design principles, standards, and guidelines for methods and properties. These are applied in a demonstration of how to automate Chapter 12's version of the Cosmo sample.