When writing an automation script, a developer or a user might want to connect to an instance of some object type that is already running. One might, for example, want to try to connect to the automation object attached to a running application so one can ask it to create a new document in which one can execute another task. One might also want to connect to a document that is already open and extract information from it instead of trying to reload that document, which would most likely fail.
To facilitate connections to a running object, which is optional for an automation object, OLE utilizes the running object table, as we saw in Chapter 9, to maintain an active object for each CLSID that is running. The choice of the word active is an unfortunate one because object activation has a much different meaning in the context of OLE Documents. In the context of Automation, the active object would be better called the running object for a given CLSID. But we're stuck with the active name, which is part of the three enumerator object. As we've seen before, an enumerator is a separate object that is generally tied to the collection itself, but because the collection can provide any number of separate enumerator objects, and because enumerators can make copies of themselves through IEnum<Type>::Clone, the enumerator cannot be the same as the collection. The older documentation even mentions how the collection doesn't implement IClassFactory. We all know better at this point in this book because we understand the role of the class factory and its separation from the class of objects it instantiates.
API functions dealing with active object instances. These functions are nothing but simple wrapper functions around the GetRunningObjectTable API and the IRunningObjectTable interface:
Function | Description |
RegisterActiveObject | Registers an object (given its IUnknown pointer and CLSID) as active by creating a moniker from the CLSID (converted to a string) and registering it in the running object table with IRunningObjectTable::Register. This returns a DWORD registration key that is returned from RegisterActiveObject. |
RevokeActiveObject | Given the DWORD key from the registration, unregisters an object. This function employs IRunningObjectTable::Revoke. |
GetActiveObject | Retrieves the IUnknown pointer for the most recently registered active object or a given CLSID through IRunningObjectTable::GetObject. |
It is an automation object that calls RegisterActiveObject and RevokeActiveObject and usually an automation controller that calls GetActiveObject, as you might expect. But as OLE API functions, any program can call these functions at any time for whatever reason.
How does this facilitate connections to running objects? When an application starts, it generally registers its application-level automation object with RegisterActiveObject. At this point a controller, executing some script, can call GetActiveObject with that application object's CLSID and get an IUnknown pointer to it. The controller can then query for IDispatch and drive that application programmatically. When the application shuts down, it calls RevokeActiveObject, which removes it from the running object table and makes it unavailable to controllers. An application can do exactly the same thing for a document that it opens: when creating the document, register its automation object as active (meaning the document class has its own CLSID), and revoke that object when closing the document. In this way, a controller can call GetActiveObject to connect to the open document.
It is very likely, of course, that the application might create and register additional documents; it is also possible that the user would launch another instance of the application. In these cases, the most recently registered object will be the active one as stipulated by the behavior of the running object table itself. So if an application created three documents and registers each one on creation, the last document created will be the active document object. If that document is closed, the second document created will become active because it is the next most recently registered. If the first document is closed, the active object does not change because the second document is still the most recently registered. If, however, the application were to create another new document, it would become the new active object. The same principle applies to instances of applications or to any other type of object that is being registered and revoked in this manner.
Simple enough? Well, as you might expect, there is one catch when using the running object table. If I have a document object with a reference count, I will usually use that reference count to destroy the document when the reference count goes to 0. However, RegisterActiveObject itself usually creates a strong lock by calling AddRef on the object you register, and that reference count is released only in RevokeActiveObject. You would call RevokeActiveObject only if the document were being destroyed, but you can't destroy the document because it has a reference count on it. The same problem applies to the application object and the application itself. Sound familiar? We discussed similar problems in Chapter 6. The solution is to use a strong or weak lock as necessary.
When calling RegisterActiveObject, you can specify either ACTIVEOBJECT_STRONG or ACTIVEOBJECT_WEAK to control the type of lock kept in the running object table. This works similarly to the first argument to IRunningObjectTable::Register. If you register a strong lock, the table calls AddRef on your object and that reference can be removed only with a call to IRunningObjectTable::Revoke. This isn't a problem when you don't need to destroy the object (and accordingly destroy a document or shut down an application). In these cases, the user is explicitly closing a window associated with the object, and the reference count is quite meaningless. If, however, you want to allow a zero reference count to destroy the object, you have to register a weak lock or you have to implement the IExternalConnection interface on the object, as we discussed in Chapter 6.