John M. Dlugosz
Have you had trouble with two versions of the same product installed at once? John shows how to make sure applications you write can be well-behaved no matter how many different versions are installed on the same machine. He also explores the registry issues involved in moving back and forth between versions of less well-behaved applications.
Recently, I was offered the chance to use a pre-release version of a product I use every day, so that I could provide valuable feedback before the final release was ready. That's usually a good thing. But the new version couldn't be installed without first uninstalling the old: They can't co-exist on the same machine. The new stuff is pretty raw and is unsuitable for normal work, which makes me wonder just how much "valuable feedback" the company will get from its testers. I suspect most people will install it, try it once, then re-install the old one and forget about it. You can bet the same people will complain about bugs in the final release—bugs they could have found when there was still time to do something about it.
I resolved not to have this problem in my own products, and I encourage everyone to do likewise. So, just what's the extent of the problem, and what can we do about it?
Changing the PATHIn the old days, distinguishing between more than one version of the same program was a simple matter of changing the PATH variable. Although this is no longer a complete solution, it's still an important step. When an application uses multiple DLLs—perhaps even shared DLLs—the loader will look for the DLLs on the PATH. (If you run the program from a command prompt, it also looks in the current directory, but when you double-click an EXE file in the shell, there really isn't a current directory.)
Imagine you've developed a program called Lamprophyre. The main EXE file will be installed in \Program Files\Lamprophyre\Lamprophyre.EXE, with its support files (HLP and DLL) placed in \Program Files\Lamprophyre\System\. Shared files from \Program Files\Common Files\System\ will also be used.
As an aside, the actual names are found in the registry as ProgramFilesDir and CommonFilesDir under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion. I changed mine from C:\Program Files to D:\apps, because I was out of room on drive C and didn't like the long name. Even so, that's just the default used by the setup program. The user could choose "Custom Install" and specify any path at all. The point is, before trying to execute Lamprophyre.EXE, the PATH needs to be set to point to the other components—in this example, C:\Program Files\Lamprophyre\System;C:\Program Files\Common Files\System\Dlugosz. The install program will take care of this with the registry key called:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\
CurrentVersion\AppPaths\Lamprophyre.EXE\
The unnamed (Default) value is the full path to the executable:
C:\Program Files\Lamprophyre\Lamprophyre.EXE
And the Path value is the extra path information:
C:\Program Files\Lamprophyre\System;C:\Program Files\
Common Files\System\Dlugosz
Now, if you go into the Explorer and click on Lamprophyre.EXE, it will set the path before actually launching the file. Specifically, the two locations in the Path key will be inserted before the existing PATH contents. What's more, if you run the program by typing Lamprophyre in the Start, Run… dialog box instead, this will still work. Even through the Lamprophyre directory isn't on the PATH, the system looks up the program's location using the default (unnamed) key in the AppPaths list.
Alas, if you run the program from a command prompt, the AppPaths registry entry will be completely ignored. Use a batch file to set the PATH before running the program itself.
Keeping two versionsTwo versions of a program should work just as well as two different programs. Even if they have DLLs with the same name (say, MyLib.DLL), the paths are different and they won't conflict. Unfortunately, the AppPaths feature uses the unqualified name of the EXE file, and you can't have two keys with the same name in the registry. The simple solution is to always name your EXE files with the version number. For example, I might have Lamprophyre 2.3.EXE and Lamprophyre 3.0 beta.EXE as different programs (note that the extra dot in the name is harmless). Many users will launch the application from the Start menu or a shortcut, and won't care about the actual file name. Power users and developers can use shell aliases, batch files, shortcuts, or other features to simplify the names.
In short, the two versions of the program are different programs as far as the system is concerned. The only issues involved are giving unique names to the main EXE files and keeping support files local to the application and out of the system at large.
Shared filesIt's a little more difficult when you have common shared files, not just secondary support files. If you have an "application suite" with several distinct main EXE files but a common infrastructure, then files shared among the suite behave just like the support files discussed above. That is, they go under the directory tree for the application suite, not in a "common" area.
If components are generic reusable things—perhaps third-party libraries—you might not care that a component got revised. For example, installing Lamprophyre 3.0 beta overwrites the file Program Files\Common Files\Dlugosz\logo.bmp with a newer version. This new version will be used when Lamprophyre 2.3 is run, as well. Who cares?
Sometimes you do care. It might be important that each installed version use the exact files that will be shipped. Or, the shared components might themselves be in a pre-release state. In this case, don't install those files in the same directory that you'd normally use. Just because these files can be shared doesn't mean that they must be shared. A good install program, such as ThumbsPlus, (see Figure 1) will let you change the locations of files, and during pre-release testing, you should change the default for your testers as well. For example, install shared components under Program Files\Common Files\Dlugosz\Beta, or better yet, as a subdirectory under the main program's location.
Figure 1: Installing DLLs into their own directory prevents accidental upgrading of other programs that use those DLLs
The identity of DLLsEarlier, I said that two DLLs with the same file name but different paths will remain distinct. Let's explore that in more detail. When an EXE file is being loaded, the loader must drag in all of the linked DLLs as well. If it finds the DLL already in memory, the system can share physical memory between the two logical copies. But, unlike 16-bit Windows, Win32 systems (Windows 95, Windows 98, and Windows NT) keep the full path name of the DLL associated with a "module," not just the base name.
When the loader looks for a DLL, it looks according to a well-defined search strategy that includes the content of the PATH environment variable, among other things that can vary between invocations of a program. The loader uses LoadLibraryEx to pull in the linked DLLs, with the flag bits (which modify the search order) taken from analogous fields from the EXE header. The DLL to use is first found, then loaded. The order is important. The system doesn't simply see that there's a DLL with the same name (only the base name of linked DLLs is included in the EXE's header) loaded and use that. First, it finds the "correct" DLL to use, based on the current directory, contents of the PATH, and so on. Then, it loads that exact DLL. If that exact DLL is already loaded, the process is more efficient, but this savings is an internal optimization transparent to the application.
To verify this, I wrote a pair of simple programs—a DLL that reports its fully qualified module file name, and an EXE that's linked to this DLL. The EXE code is as follows:
#include <stdio.h>
#include <stdlib.h>
__declspec (dllimport) void dll_function();
int main (int argc, char* argv[])
{
const char* path= getenv ("PATH");
printf ("path is: %s\n", path);
dll_function();
getchar(); //to pause
return 0;
}
The DLL that exports dll_function looks like this:
#include <stdio.h>
#include <windows.h>
__declspec (dllexport) void dll_function()
{
HMODULE h= GetModuleHandle ("DLL_FUNCTION.DLL");
const int buflen= 1024;
char buf[buflen];
GetModuleFileName (h, buf, buflen);
printf ("DLL has been called\n");
printf ("handle is %X, full name is %s\n", h, buf);
}
To verify the basic behavior, put the EXE in one directory, and copies of the DLL in two other directories. Set the PATH to include one of the DLL's locations and run the program. You'll see that it finds the DLL where you expected. Leave it running, so the DLL is still in memory, but change the PATH to refer to the other copy and run a second copy of the program. The same program will now find the other DLL, because that's the one on the PATH. The presence of another DLL with the same base name already attached to a running process is irrelevant.
COMNow that DLLs are well in hand, what else can get mixed up between programs? COM objects open up a new set of problems. A COM object is specified by a GUID, which is resolved via the registry to a DLL or EXE server that provides that object. The GUID mappings are global to the system.
Perhaps, as with some shared files, you might not care that a component gets updated with a later version. If this is a third-party component or doesn't otherwise affect the core functionality being tested, it probably doesn't matter. You might want to use the exact shipping versions of all components for testing (and more importantly, for running old releases during support), but it might be difficult to justify the need.
There's one case, however, where you can't get away from the need: What if it's the component itself being tested? Suppose that you have a new version of an ActiveX component. You give the server program to a few select users known for their lucid complaints. Because the control is already in use in various Web pages, documents, forms, and applications, you can't call it a "different" component with a new GUID. For testing, you want existing uses to start using the new implementation.
If you distribute the new version, the installer might overwrite the physical file containing the older version and will certainly change the registry so that the new file handles requests for that object. Not overwriting the old file is a big step in the right direction. Suppose that the testers run into a show-stopping bug and need to go back to the previous version to get real work done, or perhaps your internal developers want to compare the old and new functionality side-by-side. Because both versions of the server still exist, you can switch between the old and the new simply by changing the registry. To this end, I would suggest that at a minimum, the "setup" program have an option for simply re-registering a component without actually copying the files from the distribution media. If it's easy to go back and forth, your pre-release users will be more likely to use your new version beyond the discovery of the first bug.
You can, of course, get fancier. Imagine a little applet that registers itself to impersonate the component. It can then forward requests to create new objects to one version or the other, depending on the value of a radio-button control, as well as report when new objects are created, and how many are in the system, and even identify which are which by making them flash in their containers.
Other registry usageIn addition to mapping GUIDs to server applications, the registry can refer to applications for use in the GUI shell. For example, when I right-click on a ZIP file, I get several ZIP-related items on the context menu, which are provided by and handled by WinZip.EXE. It's common to have the default Open menu item as well as a Print menu item mapped to a program for its particular file type. In addition, programs like WinZip might offer more options, and some programs are primarily designed as shell extensions. To effectively support two versions of a program, you need to take care of these registry entries as well.
Suppose that files ending with .LAMP are associated with the Lamprophyre program. As discussed earlier, you're free to run Lamprophyre 2.3.EXE or Lamprophyre 3.0 beta.EXE at will, as they're separate programs, and both can be in your Start menu, exist as desktop shortcuts, or whatnot. But which should be run if I double-click on a .LAMP file?
WinZip has a good solution: It provides an easy way to explicitly re-associate the files with either version. Within the program (not in a separate install utility, but in the Options menu of the actual program) you can change these settings. If I install another program that grabs the ZIP file extension, I could run WinZip and tell it to put it back the way I wanted it (see Figure 2). The same principle applies to multiple versions of the same program: When running either, you should be able to bring up a dialog box to cause the registry settings to point to this program.
Figure 2: Well-behaved programs help users control which application is launched when a file is double-clicked.
You could also support both versions at once. Although you can't have two context menu items for "open," which is what double-clicking will trigger, you could have "open with 2.3" and "open with 3.0" as two separate context menu items. You can access either with a right click, and one or the other will be the default that you get with a double-click.
Another idea: The default "open" action could launch whichever version was most recently used. The obvious way to implement this would be to have each application implicitly reregister itself each time it was run, but this can be inefficient, if not annoying or incomplete. A better solution is to use a level of indirection. If you have a shortcut simply called Lamprophyre that's referring to either one of the EXE files, it makes sense that you could have the registry entries point to this shortcut. Then, no matter what registry entries are used, how many there are, or whether you added your own or modified anything, simply changing which EXE this shortcut refers to will change everything. Right?
Well, it doesn't work out in practice. A shell shortcut isn't a symbolic link. It's a distinct kind of file, and support for shortcuts isn't seamless. They're a feature of the GUI shell, not a primitive OS-level feature. Surprisingly, the shell's own support for them is erratic. You can "run" a file that's a shortcut for an EXE by double-clicking it, but if the same shortcut appears as the target for an "open" context menu command, it doesn't work. Presumably the code uses different logic to "run the named program" in each place. So as a general solution for any reference to a program, anywhere, the shortcut idea won't work. Not all potential users understand shortcuts.
Shell shortcuts aren't the symbolic links that UNIX programmers know and love. But wait a minute—NT does support symbolic links to files! They're only available on drives formatted with NTFS (not FAT), and they're hardly ever used, but they do work. A symbolic link is indistinguishable from the original file to any program that uses file-level API functions, including the operating system itself. You could make Lamprophyre.EXE a symbolic link to one of the specific versions, and relink it to the specified version each time the program is run. A less exotic solution that also works with FAT-formatted drives would be to have a little program that simply launches the correct program and then terminates itself. It can use a registry key or any desired means to configure which program it starts, and again, it's a simple matter for the real program to change the configuration, as one setting in one place.
What to do with big, bad applicationsAt the beginning of this article, I said I'd ensure my programs could have multiple versions happily co-existing on any hard drive. But, none of this advice fixes the unfriendly application I have sitting on my own machine. What can be done about it?
What I imagine Microsoft expects us to do is use a second dedicated machine to try out the pre-release software. Naturally, you need a different dedicated machine for each thing you're testing, because you don't want other beta components to interfere with the thing being tested. So, even if you happen to have a few other machines handy, this idea breaks down quickly. The next best thing is to install a second copy of the OS. This gives you a fresh set of system files and a totally different registry. You can boot with the alternate configuration and install different programs there. Still a pain, as you have to reboot—not to mention reinstall—all applications you plan on using in this alternate state.
A more desirable solution would be to quickly modify the registry so that I can uninstall one application and re-install the other, in a more streamlined manner than actually doing that with the supplied install programs. I looked into this, and found that the Windows NT Resource Kit comes with several registry tools that help, but nothing that does the complete job. There are a pair of programs (REGBACK and REGREST) for backing up and restoring the entire registry or any specific hive, but this is overkill. Taking a snapshot of the entire HKEY_LOCAL_MACHINE\Software hive and restoring it later will clobber other things that might have changed. A more refined surgical strike is needed.
There's a COMPREG program that compares two specified branches of the registry, but it doesn't work well enough for this task. In testing it, I found that it didn't report all of the changes I made. Also, the output isn't in a format that's immediately usable by any other tool, so further processing would be needed anyway.
A promising candidate is REGINI, which reads a simple script and makes changes to the registry. It can handle any data type and can delete as well as modify keys. So, I wrote my own program called REGDIFF that reads two registry keys and reports the differences, writing out two files in the format used by REGINI. One of the files will modify one to look like the other, and the other file will reverse the process, restoring the key to its original state.
The program needs only to compare two registry keys, and need not take care of dumping the "before" image and reading the "after" registry state and the dump side-by-side in different ways. That's because you can use REGBACK to good advantage to make a snapshot of the "before" registry. The file produced by REGBACK is that of the registry itself, so you can use the registry editor to "Load Hive" to make the contents of the "before" file appear in the registry itself! In my case, I loaded the backed-up SOFTWARE hive as HKEY_USERS\old_software, and then used my REGDIFF tool to do the comparison. The REGDIFF utility (200 lines of Perl) is available on my Web site, www.dlugosz.com. Its use for speedily swapping incompatible applications is still experimental, but I've immediately found that the process is valuable for seeing just what some program changed in the registry, and easily retouching or undoing the results.
ConclusionThe bottom line: Support simultaneous multiple versions of an application. Please! There are issues involved, but you'd do well to address the issues rather than ignoring the problem. Your pre-release testers will thank you, and your tech support personnel will thank you.
Download sample code here.John is a Senior Software Engineer with Kodak Health Imaging in Dallas. In addition to programming and writing, he enjoys computer art, photography, and science fiction. john@dlugosz.com, www.dlugosz.com.