OLE Automation

For a long time, users have wanted the ability to write macros that would affect more than one application at a time. It is extraordinarily useful to create a script that could take data automatically from a spreadsheet, plot it in a graphics program, copy that image into a word processor document, format the document and update its fields, print 40 copies of the document merged with a mailing list, and then send a piece of e-mail to an assistant who would take the pages from the printer and mail them out. It is even more useful to run this script automatically when something like an elapsed timer custom control fires an event once a month. The whole process, with the exception of the human stuffing envelopes, can be automated in software.

The critical part of making this level of integration possible is the ability to drive components programmatically, without an end user's presence but instead with an end-user script of some sort. In more technological terms, it means having various components expose their end-user–level functionality (that is, what the user understands the components to do) through interfaces that a scripting tool can use to invoke that functionality in a user-defined sequence.

There are two sides to this picture. On the one hand, we have "programmable" components, which are called automation objects. These objects provide type information, through a type library associated with the object's CLSID, that describes everything the object can do: names of interfaces, their member functions and properties, and the types of properties and arguments. On the other hand, we have some component that provides a programming environment in which a developer or other user can write scripts or create applications that drive automation objects. These are called automation controllers. Chapters 14 and 15, respectively, deal with each.

OLE Automation is centered on the interface named IDispatch. Any object that implements this interface is an automation object, regardless of what other interfaces might also be available. But in and of itself, IDispatch doesn't do a lot—it has only a few member functions. Yet somehow a component can expose all of its methods and properties through this interface.

The answer to this puzzle is that any particular object's implementation of IDispatch actually implements a more complex entity known as a dispatch interface, or dispinterface, in which each method and property is given a unique identifier, called the dispID. The dispinterface is an instance of IDispatch that responds only to a certain set of dispIDs. The object's type information for this dispinterface, which can also be obtained through an IDispatch member function, provides the controller with the means to convert the names of functions and properties to dispIDs and to learn about the types of everything involved.

When a controller wants to access a property to call a function, it passes the dispID of that element to IDispatch::Invoke, which then either gets or sets the property, or calls the method, depending on other flags also passed to Invoke. Arguments to a method call are packaged and passed to Invoke as well. The idea of invoking a method or a property through a dispID is the crux of late binding to the dispinterface because you can easily retrieve the dispIDs and all the type information about the interface at run time. Furthermore, it is relatively easy to write an implementation of IDispatch that can respond to any underlying dispinterface. It is also possible to write a "dual" interface that provides the same functionality through both early-bound vtable and late-bound dispatch interfaces.

As we'll see in Chapter 14, OLE Automation includes a number of guidelines for automating an entire application, by which, instead of implementing one automation object, you're actually implementing an object hierarchy. If you have a reasonably object-oriented application already, as I have with the sample application in that chapter, adding OLE Automation support can be done in a few days, even without any additional supporting tools such as the Microsoft Foundation Classes (MFC), which would make the job even easier.

Microsoft Visual Basic (and Visual Basic for Applications) is one of the primary automation controllers available at the time of writing. Visual Basic supports specific code constructs that directly generate IDispatch::Invoke calls, meaning that VB can drive any other object because everything is reduced to the binary standard of interfaces. Thus VB, like any other controller for that matter, drives automation objects written in C, C++, Smalltalk, Pascal, VB, COBOL, or whatever other esoteric language might support OLE. The converse of this statement is that no matter how you implement an automation object, it's usable from any controller.

This is important because some years ago, Microsoft considered making one standard, systemwide macro language. Bad idea: it would have limited the choice of language and the choice of macro tool to one. That's a bit autocratic, wouldn't you say? OLE Automation makes it possible for anyone to write a scripting tool (controller) that works with any language whatsoever and to have complete integration with all available automation objects. This gives users the choice of language and the choice of tool. With a wide enough availability of automation controllers and objects, we'll finally be rid of the cumbersome and cryptic application-specific macro languages that we all hate to learn, and instead we'll need to learn only once how to use a tool we actually like.