Server Emulation

In a number of scenarios, it is useful to have a server that supports one particular CLSID act in the place of—or emulate—the server for a different component CLSID. For example, an OLE 2 server with a new CLSID would like to replace the OLE 1 server (that is, overwrite the EXE) that uses an older CLSID, but it doesn't want to break any existing clients that use that old CLSID. Another example is a vendor that would like its component to be compatible and interchangeable with another vendor's component. This is highly useful for workgroups that exchange electronic documents that contain content objects from, for example, different graphics editors. Emulation allows an end user to create a document using one class of graphics objects so that another end user with a different but compatible graphics editor can still open the document and view and manipulate its content as if he or she had the same graphics editor as the first end user.

In both cases, obviously, the objects in question must implement the same set of interfaces to retain compatibility with existing clients. In other words, from the client's point of view, objects from either server must be polymorphic through their interfaces. The newer "emulating" server can, however, supply interfaces beyond those supported from the original server's objects. Again, QueryInterface keeps those interfaces isolated unless a client specifically asks for those interfaces. The end result is that new clients can use new interfaces while old clients use the original interfaces, all from within a single module. This is a vast improvement over less robust versioning schemes in which over time you end up with an armload of different versions of the same module, like VBRUN100.DLL, VBRUN200.DLL, VBRUN300.DLL, VBRUN400.DLL, and so on. The presence of multiple versions of a module is confusing to end users who don't know whether they can safely delete a module and free up disk space. COM's emulation facilities, along with QueryInterface and the idea of interfaces being the sole difference between object revisions, solves the problem by allowing a single module to handle all versions.

The biggest part of implementing this feature is ensuring that the emulating server's components are compatible with those being emulated. After that is done, however, registry entries named TreatAs and AutoTreatAs, stored under the CLSID of the server being emulated, point to the new emulating server:


\
CLSID
{42754580-16b7-11ce-80eb-00aa003d7352} = Original Component
TreatAs = {6fa820f0-2e48-11ce-80eb-00aa003d7352}
AutoTreatAs = {6fa820f0-2e48-11ce-80eb-00aa003d7352}
InprocServer32 = c:\older\original.dll

{6fa820f0-2e48-11ce-80eb-00aa003d7352} = New Emulating Component
InprocServer32 = c:\newer\emulator.dll

In this example, the New Emulating Component is registered to emulate Original Component. Whenever a client asks COM to create an instance of the CLSID {42754580-16b7-11ce-80eb-00aa003d7352}, it detects the TreatAs key and uses the server entry under the CLSID {6fa820f0-2e48-11ce-80eb-00aa003d7352} instead. (A local server can emulate an in-process server and vice versa without restriction except that such a mixture is risky because clients may be restricting their use to a specific type of server. It is recommended that you match server types when implementing emulation.)

The presence of AutoTreatAs doesn't affect the functionality of CoGetClassObject or CoCreateInstance. This key is used to describe a permanent emulation, whereas TreatAs simply describes a temporary emulation. Here's how it works. When New Emulating Component is installed over Original Component (as happens when updating versions), it creates both the TreatAs and AutoTreatAs keys, the latter indicating that Original Component is simply no longer available. Now let's say another component is installed—we'll call it Third Component—that emulates Original Component but doesn't know about New Emulating Component. Third Component, however, doesn't intend to overwrite Original Component (which it would not do if Third Component is not a new version of Original Component), so it only changes the TreatAs key. Now creating an instance of the Original Component CLSID will create an instance of Third Component instead.

Imagine now that Third Component is removed from the system, or for some other reason the end user wants to end Third Component's emulation of Original Component. However, because New Emulating Component overwrote Original Component, the TreatAs value must revert to the CLSID stored with AutoTreatAs. This is the entire reason why AutoTreatAs exists: to store the permanent emulating CLSID.

We can see more of how TreatAs and AutoTreatAs work through the two COM API functions that deal with emulation. The first, CoGetTreatAsClass, takes a CLSID and either returns the emulating CLSID read from the TreatAs key or returns S_FALSE to indicate that no emulation exists. CoGetClassObject uses this function to find the correct server CLSID.

The second function, CoTreatAsClass, takes the old CLSID and the new CLSID. If the two CLSIDs are different, this function creates the TreatAs entry under the old CLSID with the value of the new CLSID and replaces any existing TreatAs entry. Installation programs use this function to create TreatAs, although they create AutoTreatAs manually. If, however, CoTreatAsClass receives the same CLSID in both arguments, it does the following:

CoTreatAsClass will also delete the TreatAs entry if the new CLSID given is CLSID_NULL. This is how you turn off any temporary emulation; to turn off permanent emulation, you have to explicitly delete the AutoTreatAs key.