Binding Optimizations I: The Running Object Table

Binding a moniker does not mean that the moniker must always run some object to get the object from the passive state into the running state. There are two scenarios in which we can avoid excess work (and time):

The running object table handles the first scenario. When the source of linked objects runs those objects (for example, when a server opens a file in response to some other action), that source registers those objects in the table as running. Composite monikers and file monikers routinely use this table to avoid redundant actions.

What is called the alert object table would handle the second scenario, notifying interested clients when objects appeared in the running object table. However, this service is not implemented at this time, although it is described in the original OLE design specification.

OLE provides the running object table through a single object that implements—surprise, surprise—an interface named IRunningObjectTable. Through this interface, servers register the monikers for their running objects, and clients (and monikers) check whether an object is running. What this table really does (which is not exactly a binding optimization) is distinguish between passive objects and running objects. The table holds only those objects that are running, and anything not in the table is considered passive.

Both clients and link-source servers access the table by calling GetRunningObjectTable, which returns an IRunningObjectTable pointer. Be sure to call Release through this pointer when you are finished with it. This interface has the following member functions:


interface IRunningObjectTable : IUnknown
{
HRESULT Register(DWORD grfFlags, IUnknown *pUnkObject
, IMoniker *pmkObject, DWORD *pdwRegister);
HRESULT Revoke(DWORD dwRegister);
HRESULT IsRunning(IMoniker *pmkObject);
HRESULT GetObject(IMoniker *pmkObject, IUnknown **ppUnkObject);
HRESULT NoteChangeTime(DWORD dwRegister, FILETIME *pft);
HRESULT GetTimeOfLastChange(IMoniker *pmkObject, FILETIME *pft);
HRESULT EnumRunning(IEnumMoniker **ppEnum);
};

The Register function places an object's IUnknown pointer into the running object table along with a moniker to identify it. If you register the same moniker twice, the table will still keep the pointer in the table but will return MK_S_MONIKERALREADYREGISTERED to indicate that you've made a redundant call. If Register returns this code or NOERROR, it gives back a registration key that can then be passed either to Revoke to remove the object from the table or to NoteChangeTime.

The grfFlags argument passed to Register indicates the type of lock—strong or weak—that the running object table should make on pUnkObject. If this flag is 0, the lock is weak and the table will not call AddRef on the object. If this flag is ROTFLAGS_REGISTRATIONKEEPSALIVE, the table will call AddRef, as befits a strong lock. Remember from Chapter 6 that which sort of lock you need depends greatly on how this object will be used and how you will remove it from the table.

NoteChangeTime is the function a server can use to store a time stamp with the object in the table. Clients that are interested can call GetTimeOfLastChange to retrieve that time stamp. In both cases, the time stamp is a 64-bit OLE structure named FILETIME:


typedef struct  _FILETIME
{
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME;

To assist the server in filling one of these structures, OLE provides the API function CoFileTimeNow, which takes a FILETIME * argument identifying the structure to fill. Because many applications, both servers and clients, still deal with 16-bit MS-DOS time stamps, OLE provides two functions to convert between them: CoDosDateTimeToFileTime and CoFileTimeToDosDateTime. See the OLE Programmer's Reference for details about these functions.

Besides time changes, clients are usually interested in whether an object is currently running, and if so they may want to connect to it. The IsRunning and GetObject members of this interface serve these two needs, and both functions take a moniker to identify the object. In addition, clients might want to look at all running objects, in which case they can call EnumRunning to obtain an enumerator with IEnumMoniker. If you use this enumerator, be sure to call Release through every IMoniker pointer returned from IEnumMoniker::Next.

Obviously, the running object table is of great use to composite and file monikers in their binding processes and may also be of use to a custom moniker implementation. Monikers, however, inside their BindToObject and BindToStorage functions, are not allowed to call GetRunningObjectTable. Instead, they access the table through the bind context that flows throughout the entire binding process.


Server Requirements for Running Objects and Wildcard Monikers

The running object table is the sole place where objects are differentiated between passive and running states on a systemwide basis, so servers that bring possibly linked objects into memory are strongly recommended to register those objects as running. This is required only for objects in servers that supply link monikers and support binding to them; it is not necessary to register objects that are never named with any moniker.

This recommendation can seem complicated for something like a Microsoft Excel workbook that supports linking to individual cell ranges within it. Does the server need to register separate monikers for every possible combination of cell ranges in that spreadsheet?

Obviously there isn't enough memory to do this for a 16,384-by-16,384-by-256-cell workbook! Does this mean the server should try to remember what has possibly been linked? Not at all. OLE supports what is called a wildcard item moniker that you can compose with a file moniker to name "this file and everything in it." A wildcard item moniker has the text "\" (a single backslash). When the server opens a file, it can register a single File!Item moniker with a wildcard item. This ties into the item moniker's implementation of IMoniker::IsRunning, which checks whether Left·"\" (the moniker to its left with a \ item) is running. If it is, there is a wildcard match. The use of "\" in an item is peculiar to the standard item monikers. Custom monikers that support wildcards must define their own convention.