Team LiB
Chapter 3: COM in the Windows Installer World
In this chapter, you'll build a package to install a COM server, but first some background and history.
Let's all Share
You probably know that a COM object is created by a COM server in response to—and on behalf of—a client request for a unique Classid and Interface ID. Look at the specific case of a COM
server implemented as a DLL. When installed, this COM server has a Registry entry in HKCR\CLSID\{Classid Guid}\InprocServer32 that usually contains the path to the server. (I say usually
because you'll see cases later in the book where InprocServer32 isn't the full path to the DLL.) Consequently, every client that asks for this class causes Windows to go to this unique Registry
entry to find the path to load the server. The process whereby these Registry entries are created initially is called self-registration, "self" because the server itself contains the code to write
these Registry entries using an exported function called DllRegisterServer. Installation has historically meant that the DLL first is copied to the system, and then its DllRegisterServer entrypoint
is called. This is how Regsvr32.exe registers a server, although installation programs generated by proprietary software tools don't usually run Regsvr32.exe—they typically just load the DLL,
then use the GetProcAddress Win32 API call to find the location of DllRegisterServer and call it. (You can also have out-of-process COM servers implemented as executables—EXE files—that
have a different installation scheme because they don't export a DllRegisterServer function, but the general principle here is the same in both cases.)
What happens if multiple products want to use the same COM server? Clearly this is desirable because a COM component is most often used as a shared component. If a product P1 installs
the DLL to location A, and another product P2 installs it later to location B, the DllRegisterServer for the later product will clearly result in the InprocServer32 path now pointing to location B.
Three things are wrong with this scenario:
1. The product P2 might have installed an older version of the DLL to location B. Product P1 now fails if it requires new interfaces implemented in the DLL that the registration
entries no longer refer to.
2. If product P1 is uninstalled, it has no clue that someone else is using the COM server, so it happily deletes the registration entries and breaks product P2.
3. If product P2 is uninstalled, it uninstalls its Registry entries, and theCOM server and product P1 are broken.
The way out of these dilemmas consists for the most part in making sure that every client that uses the COM server arranges to install it to a specific unique location on the system. Windows
provides some help and a convention by encouraging use of the Registry to count the references to shared DLLs. What you'll examine here is the reference counting scheme that has
historically been used to manage the sharing of common files. I'll use it to show how sharing schemes need to work.
When an installation program copies a DLL to the system, it looks in HKLM\Software\Microsoft\Windows\CurrentVersion\SharedDLLs for the path where the DLL is going to be installed. If the
DLL's install path is in this Registry location, the counter for that path is incremented. If it isn't there already, there is usually an option in the installation development tool that causes the
path to be entered there with an initial count of one. When the product is uninstalled, the reference count is decremented. When the count becomes zero, this means that the DLL is no
longer being used and it can be removed from the system. The behavior you see at this point depends on the uninstall program. Some might ask for confirmation that it's acceptable to
remove the shared DLL that's no longer in use. The key point is that removal of the actual DLL is also the trigger that causes the registration entries to be removed as well. I should stress that
this SharedDLLs scheme is not the primary mechanism that Windows Installer uses to count references to installer components, although it supports the SharedDLLs mechanism.
Note that this reference counting scheme is based on the path to the DLL. If a COM DLL is installed to the wrong location, the reference counting breaks. It does no good to have the COM
DLL with a reference count of one in two separate paths in the SharedDLLs entries. Using the preceding example, you're still in trouble if product P1 has reference counted the DLL's path in
one location and product P2 in another because each believes it's the only remaining user of the DLL and removes the Registry entries.
It's worth noting here that you can use this reference counting in any case where a shared file is installed to a common location, not just COM server DLLs. It isn't unusual for a company's
shared DLLs to be installed in a common location and reference counted using SharedDLLs so that the last product to be uninstalled can remove the DLL.
The required convention for a shared COM server is that every product agrees on the common installation location for the file. Each product installs the file to that location, incrementing the
reference count in the SharedDLLs Registry entry at install time and decrementing at uninstall time. The critical point here is that by counting the references to the file, you're effectively also
counting the references to the registration entries for the server so that the registration entries are not removed until the DLL is removed.
Finally, there is a standard installation convention that a newer version of a versioned file replaces an older version of the same file. This is normal practice for installing versioned files, and it
is Windows Installer's default behavior. The result of putting all these together is that references to the COM server and its Registry entries are counted, and that existing client programs
depend on new versions of the COM component installed from other products being a superset of the previous versions.
Anyone that has done COM development should be aware of the concept of the COM contract between client and server, whereby the server promises to preserve a particular interface on a
class for all time. The contract states that if the server needs to provide new functionality, a new interface is added to an existing class, and this new interface extends the old interface. New
clients ask for the new interface to get the new functionality. Old clients continue to ask for the older interface, which provides their required functionality. If you think about this, the COM
contract must behave this way because of the installation constraints on sharing that I just discussed. In other words, a substantial part of the COM programming paradigm is driven by
installation requirements. If you thought that installation did not have much impact on software architecture, you should take note of its impact on the COM programming model.
Team LiB