Deleting the Executable File of the Running Process
Some applications, particularly those that uninstall software, would like to delete their own executable file as the final step before they terminate. However, simply calling DeleteFile() with the name of the executable file of the running process does not work, as testing confirms. There are at least three commonly encountered approaches to solving this problem:
1. Call MoveFileEx() with the MOVEFILE_DELAY_UNTIL_REBOOT flag and a null pointer for the new filename.
2. Start a batch file that waits for the process to terminate and then deletes the executable file and its own batch file.
3. Copy the executable file to a new file created with the FILE_FLAG_DELETE_ON_CLOSE flag and then run the executable from the new file (see Jeffrey Richters January 1996 column in Microsoft Systems Journal).
This tip considers why the straightforward approach of calling DeleteFile() does not work and what can be done to make it work. Figure 1 shows the kernel data structures that reference the in-memory representation of the executable file (the file control block); the solid arrows represent pointers, and the dotted arrows represent handles.
The reason that the call to DeleteFile() fails is that there is a reference (from the file object) to the file control block that is incompatible with deletion of the file. There are essentially two reasons why the reference to the file control block persists:
1. The existence of a VAD (Virtual Address Descriptor), which records that the file is mapped into the virtual address space.
2. The existence of the section object from which the process was created; a handle to the section object is kept in the process object.
The reference from the VAD is easily eliminated by calling UnmapViewOfFile() on the module handle of the executable file (which itself is easily obtained by calling GetModuleHandle(0)); unmapping the file causes the VAD to be deleted. The executable file cannot be unmapped by calling FreeLibrary() because the loader data structures record that the module was loaded statically. (The 16-bit load count is set to 0xFFFF, as can be verified by the debugger extension command !ntsdexts.dlls.)
The reference from the section object could be easily eliminated by calling CloseHandle() if you know the value of the handle; the handle is the only reference to the section object and when the reference count falls to zero, the section object is deleted. Empirical observations indicate that the value of the handle is always four, the lowest valid handle value.
The code in selfdel.cpp (Listing 1) shows how the obstacles to deleting the executable file can be overcome. The initial steps of the program are straightforward: the module handle and file name are obtained, and the handle to the section object is closed. The next logical step would be to unmap the view of the executable file prior to deleting the file and exiting the process. The problem is that the code is still executing in the view! The approach Ive taken is to chain together a sequence of calls to routines in kernel32 by using return addresses on the stack and to start unwinding the chain by executing the ret instruction. Effectively, UnmapViewOfFile(), DeleteFile(), and ExitProcess() are called in sequence with appropriate arguments, without referencing code in the view.
The example program can be compiled as a standalone executable for demonstration purposes, but to be useful it should be integrated into a larger application. The dependency upon the assumption that the section handle always has the value four is the weak point of this technique. This, along with the fact that it does not work under Windows 95/98, makes it just another candidate for deleting the executable file rather than the preferred one. Source code for the demo program is available in this months code archive (nebbett.zip within techtips.zip) and compiles with Visual C++ v6.0.
The MAKEINT Macros and the Rich Edit Bug
There are numerous hacks in the Win32 API, and one of the more noticeable, in my opinion, is the MAKEINTRESOURCE macro and its derivatives. This macro converts an integer value into a character pointer (LPTSTR) for use with resource management functions. Its important to understand that MAKEINTRESOURCE does not convert the number to its character representation. Rather it just changes the values type while retaining its bit pattern. This macro is defined as follows:
#define MAKEINTRESOURCE(i) (LPTSTR)((DWORD)((WORD)(i)))
Note that the resulting character pointer has zero in its high-order word. This is actually the only means to differentiate between the result of this macro and a real character pointer.
The reason such a hack was introduced into the Win32 API is because resource management functions need to handle resources that are specified either by name or through a numeric identifier. Instead of implementing two sets of functions to support both types of identifiers, or providing a function that maps the strings to numbers (a la IDispatch::GetIDsOfNames), this trick was used. Thus, the same functions can handle both types through a single LPTSTR parameter.
The reason I view this hack with such disdain is because it undermines static type checking, which is at the core of error detection in C++ and even C. Also, the method for distinguishing between real character pointers and fake ones, based on the value of the high-order word, introduces tight coupling with the structure of the operating system where no such coupling should exist.
A bug I recently discovered in version 2 of the Rich Edit control is a prime example of how truly problematic this macro is. The Rich Edit control is a window in which the user can enter, edit, format, print, and save text. The text can be assigned character and paragraph formatting and include embedded COM objects. The project I was working on utilized this control to display customizable, formatted text messages. It was built using ATL and WTL (Windows Template Library), a set of classes that extend ATL to support more complex user interfaces. WTL provides a wrapper class for the Rich Edit control, CRichEditCtrl, and even includes a sample to demonstrate its capabilities. Everything worked fine when the application was run on systems that had Rich Edit v3 installed. When run on systems with Rich Edit v2, however, the application would crash immediately. When I tested the WTL sample on these systems, it also crashed.
Because it was too late in the projects development cycle to replace the Rich Edit control with some other solution, I set out to determine the cause of the crash and see if I could circumvent it. I quickly discovered that the crash occurred inside the control while handling the WM_NCCREATE window message. This is the first message sent to a window by the Win32 API itself from within CreateWindowEx(). It turned out that the Rich Edit control was calling the wide-character, case-insensitive string comparison function wcscmpi(), passing L"RICHEDIT" as the first parameter and a junk value as the second. More testing and some intuition revealed what you have probably guessed based on the introduction: the second value was actually a WORD value cast to LPWSTR. This happened because CreateWindowEx() accepts a window class name as a null-terminated string or a class atom. A class atom, which is the value returned from RegisterClassEx(), is a WORD value. To pass it to CreateWindowEx(), you must first convert it to a character pointer using the MAKEINTATOM macro, which works just like MAKEINTRESOURCE. This is exactly what ATL does in CWindowImpl, the class that is used as the base for all windowed objects. The handler for WM_NCCREATE receives all the values passed to CreateWindowEx(), including the class identifier, through its LPARAM parameter. Unfortunately, the implementers of Rich Edit v2 forgot to test if the class name pointer is actually just an atom.
Since I did not want to modify the ATL code, there was only one solution I could come up with: intercept the WM_NCCREATE message before it gets passed to the Rich Edit control and replace the atom value with the actual class name. ATL exposes the class name via the CWindow::GetWndClassName() member function. Figure 2 shows the code I used to avoid this problem.
While I could deride the developers of version 2 of the Rich Edit control for forgetting to test for an atom before treating the class name as a string (although they have fixed this bug in version 3) or the authors of WTL for neglecting to test on more OS configurations (although WTL is officially unsupported by Microsoft), I feel the blame lies with the Win32 API itself. A good API should help developers that use it avoid errors, not induce them. Win32 certainly fails this test, at least for the case described above.
Making Device Drivers Install Silently
Normally, when Windows discovers a new device on one of the system buses, it launches the Add New Hardware Wizard to locate the .inf file and driver files for the device. Once Windows knows where the necessary files reside, it creates registry entries associating the device with a driver source directory. If Windows later finds a device of the same type, or if the user later removes and then reinstalls the original device, Windows will not present the Add New Hardware Wizard. Instead, it will use the information in the registry to find the device files.
There are situations where you would like to avoid triggering the Add New Hardware Wizard. For example, I recently worked on a project involving a suite of USB devices some of which might be installed when the application was first installed and some of which would clearly not be installed until several days or weeks later. Even though we put model definitions for all of the devices in the .inf file controlling the install and placed all of the required driver files on the hard disk, we found that the Add New Hardware Wizard would be presented each time a new device was first added to the USB bus. Thus, even though everything necessary was actually on the disk, the user would still need to re-insert the original installation media (if we had done the first install from removable media), or would have to remember where the files had been installed on the hard disk. Since potentially days or even weeks could pass between the initial installation and the additional device installation, we didnt consider this acceptable. We didnt want to force the user to keep the CD-ROM at their side always, and we certainly didnt want to depend on the user to remember where the device files had been placed on the hard disk.
One alternative would be to have the install program write the necessary registry entries during the initial installation, in effect faking the initial install of each device. While this could be made to work, it is relatively complex, platform sensitive, and directly counter to what Microsoft recommends. A better solution is to ask Windows to make the registry entries! Buried in the SetupAPI is a service named SetupCopyOEMInf() that will copy a .inf file to the operating systems driver database and make the associated registry entries on your behalf. The service is documented in the Platform SDK and is available in both Windows 98 and Windows 2000.
If you are using a custom installer, you can, of course, call this method directly from the installer. If you are installing with a scriptable install package (e.g., the Wise installer), you can still use SetupCopyOEMInf(); you just need to package the call in a trivial utility application. infcopy.cpp (Listing 2) contains the source code for a small console program I created for this purpose. To use this utility, you should:
1. Copy all driver-related files to some directory on the users system.
2. Invoke infcopy for each .inf file in the project. The file argument to infcopy must be an absolute path.
Note that even though Windows 98 normally installs oem.inf files in the ..\inf\other directory, when a .inf file is installed using SetupCopyOEMInf(), it will be installed following the Windows 2000 convention: the .inf file will be copied to the systems \inf directory under a name of the form oemX.inf, where X is an ordinal computed by the system.
Once an .inf file has been registered via infcopy, every device listed in the models section will install silently, regardless of when it is actually added to the system. This months code archive (ward.zip within techtips.zip) contains source code for infcopy.
Avoiding Insert the Windows CD Messages
If your .inf file causes the Add New Hardware Wizard to prompt for the Windows distribution media, you should look for and delete the following line from your .inf file:
Many vendors include this line in the example .inf files in their development kits or application notes. It is not uncommon for developers to dutifully copy it into their own .inf files and dismiss the Add New Hardware dialog as just another Windows quirk. In fact, this line should only appear in .inf files that are packaged on the Windows distribution disk (thus, the prompt from the Wizard). If you are distributing your .inf file on separate media (the more common case), you should not reference layout.inf.
Chris Branch is a software engineer at FSCreations, Inc. in Cincinnati, Ohio. He can be reached at ChrisBranch@Compuserve.com.
|To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.|