OLE Automation is one of those pieces of Mac Office which probably isn't understood by most people, even people here in the company. Strictly speaking, it's not really part of Office per se the Windows version of Office doesn't include it, as it is a part of the OS1. But what the heck does it do?
Mainly, it allows a programmer to do is to define an application's object model and make that model available to be coded against or scripted. The central mechanism for this is what is called the Type Library. A type library contains a list of classes and their properties and methods, along with some annotations2. It is roughly analogous to an AppleScript dictionary, though there are some marked differences3. Most often, a type library is stored in a file, but it can be implemented in code as well, which allows for some extra dynamism4. A scripting client of OLE Automation can use it to read type information out of the type library to determine what can be called. If you go into Visual Basic for Applications, and choose View/Object Browser, the contents of that browser are just a visual representation of the type library (excluding items that are marked as hidden or restricted). Then VBA knows that if you're working with an object that is the Excel Application object, that only so many properties or methods are allowed, and it can autocomplete your code as you're typing. When you run your code, VBA will call into OLE Automation, and OLE Automation will point it at the code in Excel that needs to run.
The type library provides several ways to be a client of the code which the type library describes. At the base level, you can either call into a method directly through an interface pointer's vtable, or you can call into a method by referring to it by name. This latter functionality is defined by the
IDispatchinterface, specifically the
Invokemethod. A class can be set up to allow one or the other or both, and in the last case, it's called a dual interface. If you want to use an interface from C/C++ and it supports vtable-calling, then the
mktyplibutility, which generates the type libraries, can also emit .h files for including into client projects. Otherwise, you can call
IDispatch::Invokeand have it figure out how to call that function. If you don't know what methods you will need when you're compiling your code or script, it's still possible to perform late-time binding, and ask the type library what's available at runtime. This is what VBA does, and why it can handle scripting arbitrary OLE Automation objects, so long as they can be acquired from one of the Office applications' object models, or the object model of VBA itself.
For years, the eldest Office applications, Word, Excel, and PowerPoint, all had two different object models. One was the OLE Automation / VBA model, which was shared with our Windows counterparts. One was the AppleScript model. They may have had overlap, but they were created largely in parallel, and they did not have any kind of feature parity. As a result, whereas sometimes the OLE Automation model would get new methods, the AppleScript model might not get the same methods, and the disparity would increase. Eventually, we had the idea that we could try and unify these things.
I made our first attempt at it, and it didn't succeed. The OLE Automation to AppleScript mapping proved to be sufficiently inadequate for a simple translation layer. There were enough oddities in the way the applications' type libraries were created (and subsequently had scripts writing assuming those oddities would remain), that the AppleScript experience would have been awful.
For Office 2004, Jim Murphy took a slightly different approach; start with the mapping, and then write some custom code to handle the rough edges. As of this writing, there are still some rough edges left, but ultimately, both VisualBasic and AppleScript now code to a (largely) unified object model, and more importantly to a (largely) unified implementation. Less code and more functionality. What could be better?
Under the covers, OLE Automation looks at the type library and performs calculations to figure out how to form a call to the actual function. Implementing
IDispatch::Invokeinvolves knowing how, for this architecture, ABI, and calling convention, to put arguments on the stack or in registers, call the method, and then retrieve the results of the operation. It's one of the few places in the Office products where hand-written assembly is actually necessary5.
But back to my initial caveat. While type libraries allow you to advertise arbitrary object models, one of the main issues has always been, "How do you create an instance of one of these objects?" On both Mac and Windows, the answer has always lived in the registry. When you register a type library, you place registry entries that list what version the type library is, where to find it on disk, and where to find the application or framework (er, DLL) code that implements the objects and methods. Then later, when asked to create a new object of a particular kind (e.g., when you Insert/Object "Microsoft Excel Worksheet" from within Microsoft Word), OLE can look it up in the registry and launch it (if it's an app) or load the shared library, and call the appropriate object creation code.
Now you get into two sets of annoying problems. (1) The files move. On Windows, if you move files from where they were installed, you'd better make sure to re-register the type libraries, or things will (possibly transparently) break. On the Mac, it's a little more resilient (we have aliases after all), but you can still make it break. (2) You have more than one version or more than one copy of the same version. If you register a type library, you're registering just it and just the application or shared library you pick. There can be only one. If you have two or more, you'll get rerouted to the one that was registered last. This is another form of DLL-hell, and whereas DLLs can be distributed with their applications (removing the sharing aspect of shared libraries) to avoid DLL-hell, type libraries can't be fixed the same way.
Ultimately, the problem is no different than the one that LaunchServices (or previously, the Desktop DB) solves. It keeps tracks of applications it's seen, and when asked for "com.microsoft.excel", it can launch it, and it has disambiguating rules it uses to pick the most appropriate "com.microsoft.excel" if there's more than one. As an aside, there's currently no way to leverage LaunchServices by having it cache additional data about files, so it could also track what info is currently in the registry.
My acquaintance with OLE Automation began when MacBU acquired the privilege of maintaining the Mac version of the OLE code base. I've seen its transition to CodeWarrior, through Carbonization, and now we're working on Xcode/Universalization. My best, and frankly irreplaceable, reference for all of this, beyond having access to the code itself, has been Inside OLE, Second Edition, by Kraig Brockschmidt, ISBN 1-55615-843-2, sadly out of print. It goes into much, much more technical detail about all of this and the rest of OLE, and explains it all very clearly.
1 Windows users will know it by a different name,
2 These annotations include such things as alignment and indications of where to find help on this type or method. There's also a mechanism to add custom annotations.
3 Most OLE Automation types have analogues in the AppleScript world, but one of the hard ones is how to deal with methods that return an interface. There's no idea of returning a pointer in the AppleScript world you have to return an object reference instead. Unfortunately, in some cases, a given interface won't necessarily have a way to turn itself into an object reference. If you arbitrarily created a "temporary" object store just so an object reference would get returned, the script writer would have to clean up after themselves, making sure to delete that object reference when done. While this is certainly possible, it's goes against the general AppleScript paradigm.
4 A type library could be generated on-the-fly at runtime. Usually, this wouldn't be necessary. But it would allow the type library system to be used to interact with a completely different scripting system whose contents wouldn't be known until runtime (or at least might change between the compile time and runtime). For example, it would be theoretically possible to write code that returned an
ITypeLibraryinterface which represented the contents of an AppleScript dictionary, and have that code be able to translate between OLE Automation types and AppleScript types so that you could use VBA to script a "normal" Macintosh scriptable application.
5 Another such arcane place involves the code that allows an interface to be hosted in a process other than where the implementation code exists. In these cases, we have to intercept the calls, send them via interprocess communication to OLE Automation running in the other process, and it will perform the call on our behalf. (The reverse process occurs when the result is returned.)